haltija 1.12.8 → 1.13.0-beta.1
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 +257 -2
- package/README.md +9 -4
- package/apps/desktop/isolation.js +8 -2
- package/apps/desktop/machine-channel.js +105 -0
- package/apps/desktop/machine-response.global.js +74 -0
- package/apps/desktop/main.js +68 -25
- package/apps/desktop/package.json +4 -1
- package/apps/desktop/ports.js +78 -0
- package/apps/desktop/preload.js +3 -1
- package/apps/desktop/renderer/isolation.js +6 -2
- package/apps/desktop/renderer/machine-relay.js +39 -18
- package/apps/desktop/renderer/machine-response.js +33 -0
- package/apps/desktop/renderer/tabs.js +9 -1
- package/apps/desktop/renderer.js +5 -5
- package/apps/desktop/resources/component.js +52 -52
- package/apps/desktop/server-env.js +8 -0
- package/apps/desktop/terminal.html +46 -34
- package/bin/hj.mjs +6 -2
- package/bin/tosijs-dev.mjs +14 -7
- package/bin/version.mjs +1 -1
- package/dist/client.js +0 -17
- package/dist/component.d.ts +1 -1
- package/dist/component.esm.js +52 -52
- package/dist/component.js +52 -52
- package/dist/desktop-isolation.d.ts +11 -5
- package/dist/desktop-server-env.d.ts +2 -0
- package/dist/hj-install.d.ts +9 -0
- package/dist/hj.js +22 -146
- package/dist/index.js +1356 -1116
- package/dist/loader-snippet.d.ts +37 -0
- package/dist/machine-channel.d.ts +21 -2
- package/dist/machine-log.d.ts +5 -0
- package/dist/machine-response.d.ts +30 -0
- package/dist/playwright-server.d.ts +4 -2
- package/dist/ports.d.ts +109 -0
- package/dist/server.d.ts +20 -4
- package/dist/server.js +1353 -1113
- package/dist/test-in-browser.d.ts +99 -0
- package/dist/test-ports.d.ts +15 -0
- package/dist/test.d.ts +30 -0
- package/dist/test.js +159 -25
- package/dist/transports.d.ts +207 -0
- package/dist/version.d.ts +1 -1
- package/dist/ws-origin.d.ts +55 -0
- package/docs/AGENTIC-IDE.md +7 -4
- package/docs/DECISIONS.md +168 -0
- package/docs/EXECUTIVE-SUMMARY.md +2 -2
- package/docs/README.md +1 -1
- package/docs/ROADMAP-HISTORY.md +533 -0
- package/llms.txt +25 -12
- package/package.json +29 -3
- package/bin/build-bookmarklet.ts +0 -107
- package/bin/server.ts +0 -9
- package/bin/tosijs-dev.ts +0 -74
- package/dist/.metadata_never_index +0 -0
- package/dist/machine-socket.d.ts +0 -71
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,261 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 1.
|
|
3
|
+
## 1.13.0-beta.1 (2026-09-26)
|
|
4
|
+
|
|
5
|
+
Beta (`npm i haltija@beta`); `latest` stays 1.12.9. Breaking changes are listed first.
|
|
6
|
+
|
|
7
|
+
### Known issue: any web page can call the REST API (#44)
|
|
8
|
+
|
|
9
|
+
The REST API answers any origin (`Access-Control-Allow-Origin: *`) and needs no token by default,
|
|
10
|
+
so a page you visit can call `/eval` in your connected tabs, or `/shutdown`, and read the results.
|
|
11
|
+
This predates 1.13. What 1.13 changes: with HTTPS on by default, https pages in **Safari** can now
|
|
12
|
+
reach it once the certificate is trusted, where mixed-content blocking used to stop them. Until an
|
|
13
|
+
origin gate lands (planned before 1.13.0), start the server with `--token <secret>` (or set
|
|
14
|
+
`HALTIJA_TOKEN`) on any machine where that matters, and don't leave a server running you aren't
|
|
15
|
+
using.
|
|
16
|
+
|
|
17
|
+
### Changed (breaking)
|
|
18
|
+
|
|
19
|
+
- **A server now opens BOTH transports by default** (#32a). `DEV_CHANNEL_MODE` defaults to `both`
|
|
20
|
+
instead of `http`, so HTTP 8700 *and* HTTPS 8701 come up whichever project starts the channel.
|
|
21
|
+
|
|
22
|
+
#32 reported this as a missing-certificate problem — `foresight/node_modules/haltija/certs`
|
|
23
|
+
didn't exist, so that instance came up HTTP-only. The causation runs the other way: certs are
|
|
24
|
+
only generated when HTTPS is *wanted*, and the mode defaulted to `http`. The certs were absent
|
|
25
|
+
**because** the transport was, so shipping certs in the package would have fixed nothing.
|
|
26
|
+
|
|
27
|
+
The default is a cross-project decision, which is what makes it worth changing rather than
|
|
28
|
+
documenting. The channel is shared, so an HTTP-only default is not a choice a project makes for
|
|
29
|
+
itself — it decides, for every neighbour on the machine, whether `https://` pages can connect at
|
|
30
|
+
all. In #32 that cost tosijs-3d its entire channel while every indicator said healthy: `hj where`
|
|
31
|
+
reported "haltija 1.12.2, 1 tab", the dev server logged "dev-channel ready", and every command
|
|
32
|
+
routed into an unrelated project's page. An unrecognised `DEV_CHANNEL_MODE` now falls back to
|
|
33
|
+
`both` rather than `http`, so a typo cannot produce the half-open channel either.
|
|
34
|
+
|
|
35
|
+
**`--http` now actually does something.** It was listed in `--help` but never parsed — it
|
|
36
|
+
"worked" only because the `else` branch made HTTP the default. It is a real flag now, and
|
|
37
|
+
choosing it (or `--https`) explicitly on a shared server prints what it costs your neighbours
|
|
38
|
+
(#32d). The default is never warned about; a warning that fires routinely is one nobody reads.
|
|
39
|
+
|
|
40
|
+
- **The TLS certificate moved to `~/.haltija/certs`** — machine-level, not per-install. This is a
|
|
41
|
+
consequence of the above: per-install certs were nearly invisible while almost nobody had any,
|
|
42
|
+
but with HTTPS on by default they mean the browser's "I trust this" memory is invalidated every
|
|
43
|
+
time a different install wins the race to bind 8701, and again after every `npm install` that
|
|
44
|
+
wipes `node_modules`. The user would be asked to trust localhost repeatedly with no way to make
|
|
45
|
+
it stop. An existing per-install certificate is **adopted** (copied forward) rather than
|
|
46
|
+
regenerated, so trust you have already granted survives the move. The private key is written
|
|
47
|
+
`0600` into a `0700` directory. Override with `HALTIJA_CERTS_DIR`.
|
|
48
|
+
|
|
49
|
+
If neither `mkcert` nor `openssl` is present, HTTPS is skipped with an explanation and HTTP still
|
|
50
|
+
serves — a missing tool is not a broken haltija.
|
|
51
|
+
|
|
52
|
+
- **The desktop app's internal chrome-widget port moved from 8701 to 8710** (#32a). `8701` was
|
|
53
|
+
*also* the default for the public HTTPS listener — one number with two independent literals, in
|
|
54
|
+
`src/server.ts` and `apps/desktop/main.js`, neither file aware of the other. They cannot both bind.
|
|
55
|
+
|
|
56
|
+
This was live, not latent. Verified on a developer machine mid-fix: one process (a project's
|
|
57
|
+
`haltija --both`) held **8700 and 8701 together**, because both transports share a process. Launch
|
|
58
|
+
the desktop app in `serverMode: 'builtin'` against that and `killZombieServer()` POSTs `/shutdown`
|
|
59
|
+
to 8701 to claim it for a private widget — shutting down that project's **HTTP** channel as
|
|
60
|
+
collateral. It also blocked #32's headline request outright: "both transports by default" cannot
|
|
61
|
+
ship while the desktop app claims one of them.
|
|
62
|
+
|
|
63
|
+
**The internal port moved rather than HTTPS**, because HTTPS 8701 has callers we do not ship —
|
|
64
|
+
the injected loader and bookmarklet hardcode `https://localhost:8701` (see #38 for the last time
|
|
65
|
+
that string drifted), adopters' dev servers import from it, and self-signed-cert trust is
|
|
66
|
+
per-origin, so moving it would silently un-trust the cert and present as a dead channel. The
|
|
67
|
+
internal port's only client is the app's own renderer, updated in the same binary.
|
|
68
|
+
|
|
69
|
+
**Who is affected:** anyone who targeted the chrome widget as `HALTIJA_PORT=8701 hj tree` should
|
|
70
|
+
now use `8710`. A desktop app older than 1.13.0 still holds 8701, so a new `--both` server may
|
|
71
|
+
fail to bind HTTPS until that app restarts. Setting `HALTIJA_INTERNAL_PORT=8701` by hand now
|
|
72
|
+
prints what will break instead of surfacing as a bind failure in a process you forgot was
|
|
73
|
+
involved.
|
|
74
|
+
|
|
75
|
+
Every well-known port and its role now lives in **`src/ports.ts`** (8700 public HTTP / 8701 public
|
|
76
|
+
HTTPS / 8710 internal HTTP / 8711 reserved), compiled to a CJS twin for Electron main the same way
|
|
77
|
+
`server-env.js` is. `src/ports.test.ts` asserts the public and internal blocks stay disjoint — and
|
|
78
|
+
asserts the *roles*, not the literals, since two literals agreeing is precisely what the bug was.
|
|
79
|
+
Verified against a real `--private --app` launch: chrome widget connected, ports released on
|
|
80
|
+
teardown, and the unrelated `--both` server on 8700/8701 untouched throughout.
|
|
81
|
+
|
|
82
|
+
- **Only the server holding 8700 takes the well-known HTTPS port 8701.** A server started with
|
|
83
|
+
`--port`/`--name`, or one that fell back off a busy 8700 onto an ephemeral port, now opens HTTPS
|
|
84
|
+
on an ephemeral port shown in its banner and `/status`, unless given `--https-port`. Before
|
|
85
|
+
this, with `both` as the default, the first 1.13 server started in any project could capture
|
|
86
|
+
every project's https pages while the real channel and `hj` saw 0 tabs.
|
|
87
|
+
|
|
88
|
+
- **The startup banner and the `server` export prefer HTTP.** They preferred HTTPS, which with
|
|
89
|
+
`both` as the default pointed `curl` at a self-signed certificate and made the DEV MODE
|
|
90
|
+
one-liner fail silently on http pages. `server` is the HTTP listener whenever HTTP is up; use
|
|
91
|
+
`httpsServer` for the HTTPS one.
|
|
92
|
+
|
|
93
|
+
- **A server run from a source checkout installs `hj` by copy, never by symlink,** and only a
|
|
94
|
+
bundle whose stamped version matches the server's. The old symlink fallback pinned
|
|
95
|
+
`~/.local/bin/hj` to that checkout permanently, because a symlinked `hj` is treated as a
|
|
96
|
+
deliberate choice and never repaired.
|
|
97
|
+
|
|
98
|
+
### Added
|
|
99
|
+
|
|
100
|
+
- **`testInBrowser`** — jest-shaped tests whose assertions run on the host and whose probes run in a
|
|
101
|
+
real browser (#51). The body stays on the host with its real `expect`; only the probe crosses.
|
|
102
|
+
Coordinates (`clickAt`, `dragFrom`) are first-class alongside selectors, because SVG-based UI has
|
|
103
|
+
no selectable element to name. Design credit tosijs-3d; corpus-liveness requirement from
|
|
104
|
+
tosijs-3d-ensemble; the numbers that made the case from tosijs-ui.
|
|
105
|
+
|
|
106
|
+
- **The embed snippet falls back to the other transport** (#32c). It tries the transport matching
|
|
107
|
+
the page, then the other; an https page drops to http only on `localhost`. If neither works it
|
|
108
|
+
logs `haltija: no channel reachable` in the page console, naming the HTTPS address to accept,
|
|
109
|
+
instead of failing silently. The snippet now has one source (`src/loader-snippet.ts`) shared by
|
|
110
|
+
the `/docs` copy and its Playwright suite, and `bun run test:engines` runs that suite in
|
|
111
|
+
Chromium, Firefox and WebKit.
|
|
112
|
+
|
|
113
|
+
### Fixed
|
|
114
|
+
|
|
115
|
+
- **Whether an https page can reach `http://localhost` depends on the browser, and the docs now
|
|
116
|
+
say so.** Chromium and Firefox allow it; **Safari (WebKit) blocks it as mixed content**. The docs
|
|
117
|
+
had said it was always blocked. Earlier in this release they were changed to say it was never
|
|
118
|
+
blocked, based on a Chromium-only measurement. Both were wrong. The effect is that the fallback
|
|
119
|
+
above helps Chromium and Firefox, but https pages in Safari still need the HTTPS transport. That
|
|
120
|
+
is the strongest reason for `both` being the default. #33's original "mixed content" diagnosis
|
|
121
|
+
was correct for Safari.
|
|
122
|
+
|
|
123
|
+
- **`haltija/server`'s types no longer require `@types/bun`.** Its `.d.ts` exported Bun's own
|
|
124
|
+
server type, so a TypeScript project importing it failed to compile with "Cannot find name
|
|
125
|
+
'Bun'". `server`, `httpServer` and `httpsServer` are now typed as a small `ListeningServer`
|
|
126
|
+
interface (`port`, `hostname`, `url`, `stop()`); the runtime values are unchanged. Found by the
|
|
127
|
+
new publish workflow's consumer smoke test.
|
|
128
|
+
|
|
129
|
+
- **The machine-level certificate is renewed before it expires**, and one that can't be read is
|
|
130
|
+
replaced. It used to be regenerated by every `npm install`; kept in `~/.haltija/certs` it would
|
|
131
|
+
have lapsed after the openssl fallback's 365 days, while `/status` still reported HTTPS up.
|
|
132
|
+
Writing, adopting or renewing it now leaves a receipt in `~/.haltija/machine-actions.log` and a
|
|
133
|
+
line on stderr. A replaced pair is kept as `*.bak`, since `HALTIJA_CERTS_DIR` may point at a
|
|
134
|
+
directory you own. A key that doesn't match its cert is replaced too, so a pair left
|
|
135
|
+
mismatched by two servers renewing at once heals on the next start. A failed renewal keeps
|
|
136
|
+
using the current cert while it is still valid.
|
|
137
|
+
|
|
138
|
+
- **A bad certificate costs HTTPS only.** A mismatched or corrupt pair in `~/.haltija/certs` used
|
|
139
|
+
to stop every haltija server on the machine from starting, HTTP included. `/status` (and so
|
|
140
|
+
`hj where`) now names the real reason HTTPS is down; it used to blame "port N is held by
|
|
141
|
+
another process" for certificate problems.
|
|
142
|
+
|
|
143
|
+
- **`testInBrowser`: `clickAt` and `dragFrom` send pointer events** as well as mouse events, with
|
|
144
|
+
`buttons` set, and moves go to the element under the pointer. Pointer-driven controls (3D camera
|
|
145
|
+
orbits) ignored them before, and the drag still reported success. `clickAt` also accepts a real
|
|
146
|
+
point above the fold on a scrolled page, which it rejected as "outside the document", and
|
|
147
|
+
`click`/`type`/`press` now fail on a `{ success: false }` reply from a bridge instead of passing.
|
|
148
|
+
|
|
149
|
+
- **The test suites no longer touch the real 8701 or `~/.haltija/certs`.** The Playwright helper
|
|
150
|
+
opted out of HTTPS with a variable nothing had read since v0.1.7, which stopped mattering only
|
|
151
|
+
while `http` was the default. CI now fails if a suite writes anything under `~/.haltija`.
|
|
152
|
+
|
|
153
|
+
- **The README no longer says "localhost only by default".** The server listens on all
|
|
154
|
+
interfaces, and without `--token` any page or anything on the network can call its REST API.
|
|
155
|
+
|
|
156
|
+
## 1.12.9
|
|
157
|
+
|
|
158
|
+
Remediation of a Tier 1 review that returned **BLOCK** on the already-published 1.12.7/1.12.8.
|
|
159
|
+
Full report: `reviews/1.12.9-tier1-v1.12.6-to-v1.12.8.md`.
|
|
160
|
+
|
|
161
|
+
### Security
|
|
162
|
+
|
|
163
|
+
- **Babylon.js is pinned and integrity-checked (review B1).** The 3D preview added in 1.12.7 loaded
|
|
164
|
+
`cdn.babylonjs.com/babylon.js` — rolling latest, no pin, no SRI — into the one frame holding a
|
|
165
|
+
postMessage relay that reaches `spawn('sh','-c')` and the filesystem. The app strips CSP headers
|
|
166
|
+
globally and the iframe has no sandbox, so a CDN compromise, a bad publish, or a TLS-intercepting
|
|
167
|
+
proxy meant arbitrary code execution on the developer's machine. Shipped, ironically, in the
|
|
168
|
+
release that removed a *network*-reachable RCE. Now pinned to `v9.25.0` with `integrity` +
|
|
169
|
+
`crossorigin`, so substituted bytes cannot execute at all.
|
|
170
|
+
- **The renderer relay now default-denies its sender.** It validated message *shape* only, so any
|
|
171
|
+
frame in the renderer could reach shell and filesystem. It now requires `event.source` to be a
|
|
172
|
+
known terminal iframe. This is what turned "third-party code in a preview pane" into "third-party
|
|
173
|
+
code with a shell", and it would have handed shell access to any plain `<iframe>` added later.
|
|
174
|
+
- **`/ws/terminal` upgrades are restricted (review B3).** It accepted unauthenticated
|
|
175
|
+
cross-origin upgrades and volunteers `{shellId, cwd}` on connect, streaming the developer's
|
|
176
|
+
working directory and absolute file paths to any web page. It now admits: a **loopback hostname**
|
|
177
|
+
origin, an opaque/`file:` origin that presents a per-launch nonce the desktop app gives only to
|
|
178
|
+
its own terminal frame, or a request with **no `Origin` header at all** (a non-browser client — a
|
|
179
|
+
browser always sends one cross-origin).
|
|
180
|
+
|
|
181
|
+
**Stated precisely, because an earlier draft of this bullet was false in three directions and a
|
|
182
|
+
pre-publish review caught it.** What is *not* claimed: `/ws/agent` and `/ws/browser` are
|
|
183
|
+
deliberately **not** gated — the REST surface already answers any origin
|
|
184
|
+
(`Access-Control-Allow-Origin: *`), so any page can already `POST /tree` and `/eval`; gating the
|
|
185
|
+
socket alone would close nothing and would break non-browser agents, which is why an attempt to
|
|
186
|
+
do so was reverted during this release. And the `/ws/terminal` rule is not "local machine only":
|
|
187
|
+
a loopback match is **hostname-only, any port**, so a third-party script on your own
|
|
188
|
+
`localhost:3000` dev server qualifies, and an absent `Origin` is admitted from anywhere on the
|
|
189
|
+
LAN because the server binds `0.0.0.0`. **`--token` is what addresses those**, not this gate.
|
|
190
|
+
- **The channel grant no longer leaks to child processes.** `HALTIJA_MACHINE_CHANNEL=1` was spread
|
|
191
|
+
into every command the terminal or an agent spawned, so a nested `bunx haltija` would start
|
|
192
|
+
treating its own stdin as an authenticated control channel.
|
|
193
|
+
|
|
194
|
+
### `haltija/test` no longer mutates a browser nobody chose (#42)
|
|
195
|
+
|
|
196
|
+
`resolveTestServerUrl` falls back to `http://localhost:8700` — the *shared* interactive server — and
|
|
197
|
+
the only gate was a stderr warning. This project proved on itself that a warning is not a boundary:
|
|
198
|
+
on 2026-09-03 our own suite adopted another project's server and called `navigate`/`click` against
|
|
199
|
+
its six live tabs. We fixed our lane and left the published library adopting, and **a hazard fixed
|
|
200
|
+
in your own lane but left in the library you ship is not fixed**.
|
|
201
|
+
|
|
202
|
+
Mutating calls (`navigate`, `click`, `type`, `press`, `refresh`) now **throw** when no
|
|
203
|
+
`HALTIJA_URL`/`HALTIJA_PORT` was set, with a message naming the remedy. Read-only calls (`status`,
|
|
204
|
+
`query`, `eval`) are unaffected — reading from a shared server harms nobody, and a guard that blocks
|
|
205
|
+
harmless calls gets disabled wholesale. `HALTIJA_TEST_ALLOW_SHARED=1` opts back in deliberately.
|
|
206
|
+
|
|
207
|
+
**This is a behaviour change in a published API.** Anyone whose suite relied on the 8700 fallback
|
|
208
|
+
for mutating calls will now get a loud error instead of silently driving whichever browser answered
|
|
209
|
+
— which is the point.
|
|
210
|
+
|
|
211
|
+
### Dependencies
|
|
212
|
+
|
|
213
|
+
- **`tosijs-schema` was pinned to an exact `1.5.1`, four minors behind.** Surfaced by a new Tier 0
|
|
214
|
+
check (from tosijs-ui#61's finding #2: *declared ranges whose window excludes what a consumer
|
|
215
|
+
installs today*). An exact pin in a regular dependency also invites a duplicate install alongside
|
|
216
|
+
a consumer's own copy. Moved to `^1.9.0` after verifying the upgrade rather than assuming it:
|
|
217
|
+
977 unit + 125 e2e green, and every schema-derived generated artifact (`API.md`, `DOCS.md`,
|
|
218
|
+
`llms.txt`, `bin/hints.json`, `apps/mcp/src/endpoints.json`) rebuilt **byte-identical**.
|
|
219
|
+
|
|
220
|
+
### Coverage and structure (the two gaps the reviews left open)
|
|
221
|
+
|
|
222
|
+
- **Nothing exercised the terminal's WebSocket**, which is why a regression that broke it was
|
|
223
|
+
invisible to 977 green tests: `/terminal/command` travels over the stdio pipe and never needed
|
|
224
|
+
the socket, so commands kept working. Two guards now: a live upgrade test against a real private
|
|
225
|
+
server (raw HTTP upgrade, so it can set `Origin` — which the browser WebSocket API forbids), and
|
|
226
|
+
a wiring test asserting `main.js` actually *passes* what `buildServerEnv` can grant. The second
|
|
227
|
+
is the one that matters: the function was already tested; the defect was a caller that silently
|
|
228
|
+
dropped an argument. Both verified by mutation.
|
|
229
|
+
- **The base64 Response shim existed twice and had already diverged** (only one copy had
|
|
230
|
+
`dataUrl()`). Now one source, `src/machine-response.ts`, built twice — ESM for the renderer's
|
|
231
|
+
modules, a global for `terminal.html`, which is an inline-script page with no import mechanism.
|
|
232
|
+
|
|
233
|
+
### Fixes
|
|
234
|
+
|
|
235
|
+
- **Window recreation was dead code (review B2).** The 1.12.7 stdout refactor tested
|
|
236
|
+
`__NEED_WINDOW__` against the *residual line buffer*; the marker always arrives newline-terminated,
|
|
237
|
+
so the check was always false and the correct one below it unreachable. Every `hj` command against
|
|
238
|
+
a windowless-but-alive desktop app hung 8s and failed.
|
|
239
|
+
- **Root cause of the above: the wire protocol was implemented twice.** `main.js` hand-rolled the
|
|
240
|
+
prefixes, the line split and the parse while `src/machine-channel.ts` owned and tested them. It is
|
|
241
|
+
now a generated twin (`apps/desktop/machine-channel.js`), like `server-env.js`.
|
|
242
|
+
- **Timeout layering was inverted**, so a ~30s command (`npm ci`, a slow build) reported "Timed out
|
|
243
|
+
on the machine channel" and discarded the server's real output. Each outer deadline is now
|
|
244
|
+
strictly longer than the one it wraps (server 30s → main 45s → iframe 50s), so a timeout at a
|
|
245
|
+
layer means that layer genuinely lost the answer.
|
|
246
|
+
- **The desktop guard test only recognised template-literal fetches**, so
|
|
247
|
+
`fetch(getServerUrl() + '/files/read')` passed green and 410'd at runtime — the exact silent
|
|
248
|
+
breakage it exists to prevent. It now matches the call and the path independently (verified by
|
|
249
|
+
mutation), skips comments, and states its remaining line-scoped limit rather than implying none.
|
|
250
|
+
- Bun's Unix-socket defect is now **filed upstream** (oven-sh/bun#41381) and mirrored in
|
|
251
|
+
`UPSTREAM.md`, per "we file, we don't fix". The stdio channel stays regardless — it is good on its
|
|
252
|
+
own merits.
|
|
253
|
+
- Docs corrected: `docs/AGENTIC-IDE.md` no longer advertises two routes that now return 410;
|
|
254
|
+
`CLAUDE.md` no longer contradicts itself about whether the tabs were restored; published versions
|
|
255
|
+
are no longer marked `(unreleased)`; and the absolute "no machine-control surface on any
|
|
256
|
+
transport" claim is stated precisely, since an earlier version of it was false.
|
|
257
|
+
|
|
258
|
+
## 1.12.8
|
|
4
259
|
|
|
5
260
|
- **Electron discovery assumed macOS, so Linux re-downloaded Electron every run (#43).** The npx-cache
|
|
6
261
|
search looked for `Electron.app` — a macOS bundle — behind a `platform() !== 'win32'` guard, so
|
|
@@ -14,7 +269,7 @@
|
|
|
14
269
|
right all along — decide the binary name, so there is one authority instead of two to drift.
|
|
15
270
|
Reported by snowfox with the diagnosis and the fix.
|
|
16
271
|
|
|
17
|
-
## 1.12.7
|
|
272
|
+
## 1.12.7
|
|
18
273
|
|
|
19
274
|
### File preview: 3D models, video, audio and fonts
|
|
20
275
|
|
package/README.md
CHANGED
|
@@ -331,8 +331,13 @@ app also exists but isn't required for either.
|
|
|
331
331
|
|
|
332
332
|
- Widget is always visible (no silent snooping)
|
|
333
333
|
- User can pause or kill connection anytime
|
|
334
|
-
-
|
|
335
|
-
|
|
334
|
+
- **Not localhost-only.** The server listens on all interfaces (HTTP 8700 and HTTPS 8701 by
|
|
335
|
+
default), and without `--token` any page you visit, or anything on your network, can call its
|
|
336
|
+
REST API — including `/eval` in your connected tabs. Use `--token <secret>` (or
|
|
337
|
+
`HALTIJA_TOKEN`) on any network you don't trust, and don't leave a server running you aren't
|
|
338
|
+
using.
|
|
339
|
+
- Machine-level HTTPS certificate in `~/.haltija/certs`, auto-generated (mkcert if installed,
|
|
340
|
+
otherwise openssl) and renewed before it expires
|
|
336
341
|
|
|
337
342
|
---
|
|
338
343
|
|
|
@@ -419,10 +424,10 @@ HALTIJA_NAME=dashboard hj tree # any shell can address it by name
|
|
|
419
424
|
|
|
420
425
|
What this means for you, depending on how you used 1.3.0-beta.7:
|
|
421
426
|
|
|
422
|
-
- **`bunx haltija` desktop app** — works the same; no migration needed. The outer "chrome" widget that lets the app inspect itself now lives on a separate internal port (8701) so it never appears in agent-facing window listings.
|
|
427
|
+
- **`bunx haltija` desktop app** — works the same; no migration needed. The outer "chrome" widget that lets the app inspect itself now lives on a separate internal port (8701 then, 8710 since 1.13.0) so it never appears in agent-facing window listings.
|
|
423
428
|
- **`HALTIJA_SESSION` / `--session` / `--secure` / the click-to-copy session badge** — gone. If you were setting `HALTIJA_SESSION` in your shell, replace it with `HALTIJA_NAME` (and start the server with `--name <foo>`) or `HALTIJA_PORT`.
|
|
424
429
|
- **`inject(url, { session })`** — the `session` option is removed. If you need auth, use `inject(url, { token })` (matches `haltija --token <secret>`); for embedding without auth, just `inject(url)` or `inject(url, { mode: 'headless' })`.
|
|
425
|
-
- **`hj-chrome` exclusion logic** — gone from the public REST API. To inspect the desktop app's outer UI from `hj`, target the internal port directly: `HALTIJA_PORT=
|
|
430
|
+
- **`hj-chrome` exclusion logic** — gone from the public REST API. To inspect the desktop app's outer UI from `hj`, target the internal port directly: `HALTIJA_PORT=8710 hj tree`. *(That port was 8701 in 1.4.0–1.12.x; it moved in 1.13.0 because 8701 is the public HTTPS listener — see [#32](https://github.com/tonioloewald/haltija/issues/32).)*
|
|
426
431
|
|
|
427
432
|
Net code change: ~830 lines removed, ~150 added back for the simpler model. Test count went up (we now have integration coverage for the token stub, named instances, and auto-port fallback that the previous betas lacked).
|
|
428
433
|
|
|
@@ -48,8 +48,14 @@ __export(exports_desktop_isolation, {
|
|
|
48
48
|
resolveServerUrl: () => resolveServerUrl
|
|
49
49
|
});
|
|
50
50
|
module.exports = __toCommonJS(exports_desktop_isolation);
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
|
|
52
|
+
// src/ports.ts
|
|
53
|
+
var DEFAULT_HTTP_PORT = 8700;
|
|
54
|
+
var DEFAULT_INTERNAL_PORT = 8710;
|
|
55
|
+
|
|
56
|
+
// src/desktop-isolation.ts
|
|
57
|
+
var SHARED_PUBLIC_URL = `http://localhost:${DEFAULT_HTTP_PORT}`;
|
|
58
|
+
var SHARED_INTERNAL_PORT = DEFAULT_INTERNAL_PORT;
|
|
53
59
|
function resolveInternalPort(env) {
|
|
54
60
|
const raw = env.HALTIJA_INTERNAL_PORT;
|
|
55
61
|
if (raw === undefined || raw === "")
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/** ⚠️ AUTO-GENERATED FROM src/machine-channel.ts — DO NOT EDIT. Run: bun run build */
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
function __accessProp(key) {
|
|
7
|
+
return this[key];
|
|
8
|
+
}
|
|
9
|
+
var __toCommonJS = (from) => {
|
|
10
|
+
var entry = (__moduleCache ??= new WeakMap).get(from), desc;
|
|
11
|
+
if (entry)
|
|
12
|
+
return entry;
|
|
13
|
+
entry = __defProp({}, "__esModule", { value: true });
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (var key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(entry, key))
|
|
17
|
+
__defProp(entry, key, {
|
|
18
|
+
get: __accessProp.bind(from, key),
|
|
19
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
__moduleCache.set(from, entry);
|
|
23
|
+
return entry;
|
|
24
|
+
};
|
|
25
|
+
var __moduleCache;
|
|
26
|
+
var __returnValue = (v) => v;
|
|
27
|
+
function __exportSetter(name, newValue) {
|
|
28
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
29
|
+
}
|
|
30
|
+
var __export = (target, all) => {
|
|
31
|
+
for (var name in all)
|
|
32
|
+
__defProp(target, name, {
|
|
33
|
+
get: all[name],
|
|
34
|
+
enumerable: true,
|
|
35
|
+
configurable: true,
|
|
36
|
+
set: __exportSetter.bind(all, name)
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// src/machine-channel.ts
|
|
41
|
+
var exports_machine_channel = {};
|
|
42
|
+
__export(exports_machine_channel, {
|
|
43
|
+
MACHINE_CHANNEL_ENV: () => MACHINE_CHANNEL_ENV,
|
|
44
|
+
MACHINE_REQ_PREFIX: () => MACHINE_REQ_PREFIX,
|
|
45
|
+
MACHINE_RES_PREFIX: () => MACHINE_RES_PREFIX,
|
|
46
|
+
childEnvWithoutChannel: () => childEnvWithoutChannel,
|
|
47
|
+
formatRequest: () => formatRequest,
|
|
48
|
+
formatResponse: () => formatResponse,
|
|
49
|
+
machineChannelEnabled: () => machineChannelEnabled,
|
|
50
|
+
parseRequestLine: () => parseRequestLine,
|
|
51
|
+
parseResponseLine: () => parseResponseLine,
|
|
52
|
+
splitLines: () => splitLines
|
|
53
|
+
});
|
|
54
|
+
module.exports = __toCommonJS(exports_machine_channel);
|
|
55
|
+
var MACHINE_REQ_PREFIX = "HJ_MACHINE_REQ ";
|
|
56
|
+
var MACHINE_RES_PREFIX = "HJ_MACHINE_RES ";
|
|
57
|
+
var MACHINE_CHANNEL_ENV = "HALTIJA_MACHINE_CHANNEL";
|
|
58
|
+
function machineChannelEnabled(env) {
|
|
59
|
+
return env[MACHINE_CHANNEL_ENV] === "1";
|
|
60
|
+
}
|
|
61
|
+
function formatRequest(req) {
|
|
62
|
+
return MACHINE_REQ_PREFIX + JSON.stringify(req) + `
|
|
63
|
+
`;
|
|
64
|
+
}
|
|
65
|
+
function formatResponse(res) {
|
|
66
|
+
return MACHINE_RES_PREFIX + JSON.stringify(res) + `
|
|
67
|
+
`;
|
|
68
|
+
}
|
|
69
|
+
function parseRequestLine(line) {
|
|
70
|
+
if (!line.startsWith(MACHINE_REQ_PREFIX))
|
|
71
|
+
return null;
|
|
72
|
+
try {
|
|
73
|
+
const parsed = JSON.parse(line.slice(MACHINE_REQ_PREFIX.length));
|
|
74
|
+
if (typeof parsed?.id !== "string" || typeof parsed?.path !== "string")
|
|
75
|
+
return null;
|
|
76
|
+
if (!parsed.path.startsWith("/"))
|
|
77
|
+
return null;
|
|
78
|
+
return { method: "GET", ...parsed };
|
|
79
|
+
} catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function parseResponseLine(line) {
|
|
84
|
+
if (!line.startsWith(MACHINE_RES_PREFIX))
|
|
85
|
+
return null;
|
|
86
|
+
try {
|
|
87
|
+
const parsed = JSON.parse(line.slice(MACHINE_RES_PREFIX.length));
|
|
88
|
+
if (typeof parsed?.id !== "string" || typeof parsed?.status !== "number")
|
|
89
|
+
return null;
|
|
90
|
+
return { headers: {}, bodyB64: "", ...parsed };
|
|
91
|
+
} catch {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function splitLines(buffer) {
|
|
96
|
+
const parts = buffer.split(`
|
|
97
|
+
`);
|
|
98
|
+
const rest = parts.pop() ?? "";
|
|
99
|
+
return { lines: parts, rest };
|
|
100
|
+
}
|
|
101
|
+
function childEnvWithoutChannel(env = process.env) {
|
|
102
|
+
const copy = { ...env };
|
|
103
|
+
delete copy[MACHINE_CHANNEL_ENV];
|
|
104
|
+
return copy;
|
|
105
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/** ⚠️ AUTO-GENERATED FROM src/machine-response.ts — DO NOT EDIT. Run: bun run build */
|
|
2
|
+
(() => {
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
function __accessProp(key) {
|
|
8
|
+
return this[key];
|
|
9
|
+
}
|
|
10
|
+
var __toCommonJS = (from) => {
|
|
11
|
+
var entry = (__moduleCache ??= new WeakMap).get(from), desc;
|
|
12
|
+
if (entry)
|
|
13
|
+
return entry;
|
|
14
|
+
entry = __defProp({}, "__esModule", { value: true });
|
|
15
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
16
|
+
for (var key of __getOwnPropNames(from))
|
|
17
|
+
if (!__hasOwnProp.call(entry, key))
|
|
18
|
+
__defProp(entry, key, {
|
|
19
|
+
get: __accessProp.bind(from, key),
|
|
20
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
__moduleCache.set(from, entry);
|
|
24
|
+
return entry;
|
|
25
|
+
};
|
|
26
|
+
var __moduleCache;
|
|
27
|
+
var __returnValue = (v) => v;
|
|
28
|
+
function __exportSetter(name, newValue) {
|
|
29
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
30
|
+
}
|
|
31
|
+
var __export = (target, all) => {
|
|
32
|
+
for (var name in all)
|
|
33
|
+
__defProp(target, name, {
|
|
34
|
+
get: all[name],
|
|
35
|
+
enumerable: true,
|
|
36
|
+
configurable: true,
|
|
37
|
+
set: __exportSetter.bind(all, name)
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// src/machine-response.ts
|
|
42
|
+
var exports_machine_response = {};
|
|
43
|
+
__export(exports_machine_response, {
|
|
44
|
+
machineResponseError: () => machineResponseError,
|
|
45
|
+
makeMachineResponse: () => makeMachineResponse
|
|
46
|
+
});
|
|
47
|
+
function makeMachineResponse(status, bodyB64, headers = {}) {
|
|
48
|
+
let decoded = null;
|
|
49
|
+
const decode = () => {
|
|
50
|
+
if (decoded === null) {
|
|
51
|
+
const bytes = Uint8Array.from(atob(bodyB64 || ""), (c) => c.charCodeAt(0));
|
|
52
|
+
decoded = new TextDecoder().decode(bytes);
|
|
53
|
+
}
|
|
54
|
+
return decoded;
|
|
55
|
+
};
|
|
56
|
+
return {
|
|
57
|
+
ok: status >= 200 && status < 300,
|
|
58
|
+
status,
|
|
59
|
+
headers,
|
|
60
|
+
b64: bodyB64 || "",
|
|
61
|
+
dataUrl: () => `data:${headers["content-type"] || "application/octet-stream"};base64,${bodyB64 || ""}`,
|
|
62
|
+
json: async () => JSON.parse(decode() || "{}"),
|
|
63
|
+
text: async () => decode()
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function machineResponseError(status, message) {
|
|
67
|
+
return makeMachineResponse(status, btoa(JSON.stringify({ success: false, error: message })), {
|
|
68
|
+
"content-type": "application/json"
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
if (typeof globalThis !== "undefined" && typeof globalThis.document !== "undefined") {
|
|
72
|
+
globalThis.__hjMachineResponse = { makeMachineResponse, machineResponseError };
|
|
73
|
+
}
|
|
74
|
+
})();
|