haltija 1.5.0 → 1.5.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 CHANGED
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.5.1
4
+
5
+ Low-risk follow-ups from the 1.5.0 pre-release review — the two new "instrument must not lie"
6
+ warnings now reach more of the surfaces where they matter.
7
+
8
+ ### Fixed
9
+
10
+ - **The hidden-tab / focus-ambiguity warning is no longer dropped on the paths where it's most
11
+ useful.** It's now attached on the **timeout** path — a hidden tab whose rAF-driven `eval` never
12
+ resolves now returns a `Timeout` that *explains* it may be asleep, instead of a bare timeout —
13
+ and preserved by the `hj find` / `hj form` handlers, which previously reshaped the response and
14
+ lost it. (`hj screenshot` already carried it; `hj call` intentionally still returns the raw value
15
+ with no envelope.)
16
+
17
+ ### Internal
18
+
19
+ - Extracted the `hj --window <id>` argument handling into a pure, unit-tested helper
20
+ (`bin/arg-utils.mjs`), covering both the leading and trailing positions — the leading form was
21
+ the escape hatch that broke in 1.4.0, and it now has a regression test.
22
+ - A private-app startup that fails to learn its ephemeral port no longer leaves its temp port-files
23
+ behind.
24
+
3
25
  ## 1.5.0
4
26
 
5
27
  Completes the **private-automation** feature (`--private`) begun in 1.4.1 — now for the Electron
@@ -1350,7 +1350,12 @@ async function startEmbeddedServer() {
1350
1350
  }
1351
1351
  const pubPort = await readPort(pubFile)
1352
1352
  const intPort = await readPort(intFile)
1353
- if (!pubPort) { console.error('[Haltija Desktop] Private public server did not report its port'); return false }
1353
+ if (!pubPort) {
1354
+ // Don't leak the pid-scoped tmp port-files on the failure path (the success path cleans below).
1355
+ try { fs.rmSync(pubFile, { force: true }); fs.rmSync(intFile, { force: true }) } catch {}
1356
+ console.error('[Haltija Desktop] Private public server did not report its port')
1357
+ return false
1358
+ }
1354
1359
 
1355
1360
  HALTIJA_PORT = pubPort
1356
1361
  HALTIJA_SERVER = `http://localhost:${pubPort}`
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "haltija-desktop",
3
- "version": "1.5.0",
3
+ "version": "1.5.1",
4
4
  "private": true,
5
5
  "description": "Haltija Desktop - God Mode Browser for AI Agents",
6
6
  "homepage": "https://github.com/tonioloewald/haltija",
@@ -46,7 +46,7 @@
46
46
  });
47
47
 
48
48
  // src/version.ts
49
- var VERSION = "1.5.0";
49
+ var VERSION = "1.5.1";
50
50
 
51
51
  // src/text-selector.ts
52
52
  var TEXT_PSEUDO_RE = /:(?:text-is|has-text|text)\(/;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Pure arg helpers for the `hj` CLI, extracted so they can be unit-tested (see
3
+ * src/hj-args.test.ts) — the leading-`--window` form was fully broken and shipped without a test.
4
+ */
5
+
6
+ /**
7
+ * Pull a `--window <id>` flag out of `args` from ANY position, returning the id and the remaining
8
+ * args (input is not mutated). Mirrors how `--port`/`--name`/`--token` are pre-parsed, so both
9
+ * `hj --window <id> <cmd>` (leading) and `hj <cmd> --window <id>` (trailing) resolve the same tab.
10
+ * A `--window` with no following value is left in place (treated as not-a-target) so it surfaces as
11
+ * a normal unknown-flag rather than silently swallowing the next real arg.
12
+ */
13
+ export function extractWindowTarget(args) {
14
+ const i = args.indexOf('--window')
15
+ if (i === -1 || args[i + 1] === undefined) {
16
+ return { windowTarget: null, args: [...args] }
17
+ }
18
+ const rest = [...args]
19
+ rest.splice(i, 2)
20
+ return { windowTarget: args[i + 1], args: rest }
21
+ }
package/bin/hj.mjs CHANGED
@@ -11,6 +11,7 @@
11
11
  */
12
12
 
13
13
  import { runSubcommand, isSubcommand, getSuggestion, listSubcommands, COMMAND_HINTS } from './cli-subcommand.mjs'
14
+ import { extractWindowTarget } from './arg-utils.mjs'
14
15
  import { HJ_VERSION } from './version.mjs'
15
16
  import { differsBeyondPatch } from './semver.mjs'
16
17
  import { existsSync, readFileSync, readdirSync } from 'node:fs'
@@ -313,12 +314,11 @@ if (noLaunchIdx !== -1) {
313
314
  // died with "Unknown command: '--window'" — i.e. the escape hatch we tell people to use for a
314
315
  // hidden/wrong tab didn't work in the shape the docs gave. Pulled out here and re-appended to
315
316
  // the subcommand args below, so BOTH positions work.
316
- let windowTarget = null
317
- const windowIdx = args.indexOf('--window')
318
- if (windowIdx !== -1 && args[windowIdx + 1]) {
319
- windowTarget = args[windowIdx + 1]
320
- args.splice(windowIdx, 2)
321
- }
317
+ const { windowTarget, args: argsWithoutWindow } = extractWindowTarget(args)
318
+ // Preserve the `const args` reference (downstream code mutates it in place) while dropping the
319
+ // consumed --window <id> pair.
320
+ args.length = 0
321
+ args.push(...argsWithoutWindow)
322
322
 
323
323
  // Did the shell explicitly target a private instance (--port / --name /
324
324
  // HALTIJA_PORT / HALTIJA_NAME / DEV_CHANNEL_PORT)? If so, this is a
package/bin/version.mjs CHANGED
@@ -3,4 +3,4 @@
3
3
  * ⚠️ To change the version, update package.json and run: bun run build
4
4
  */
5
5
 
6
- export const HJ_VERSION = '1.5.0'
6
+ export const HJ_VERSION = '1.5.1'
@@ -16,6 +16,8 @@ export interface DevResponse {
16
16
  data?: any;
17
17
  error?: string;
18
18
  timestamp: number;
19
+ /** Hidden-tab / focus-ambiguity caveat attached by the server (see requestFromBrowser). */
20
+ warning?: string;
19
21
  }
20
22
  /** Function to send request to browser widget */
21
23
  export type RequestFromBrowserFn = (channel: string, action: string, payload: any, timeoutMs?: number, windowId?: string) => Promise<DevResponse>;
@@ -20,7 +20,7 @@
20
20
  * - Option+Tab toggles visibility (but active state always shows briefly)
21
21
  * - Localhost only by default
22
22
  */
23
- export declare const VERSION = "1.5.0";
23
+ export declare const VERSION = "1.5.1";
24
24
  export declare class DevChannel extends HTMLElement {
25
25
  static get tagName(): string;
26
26
  static elementCreator(): () => DevChannel;
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var VERSION = "1.5.0";
2
+ var VERSION = "1.5.1";
3
3
 
4
4
  // src/text-selector.ts
5
5
  var TEXT_PSEUDO_RE = /:(?:text-is|has-text|text)\(/;
package/dist/component.js CHANGED
@@ -46,7 +46,7 @@
46
46
  });
47
47
 
48
48
  // src/version.ts
49
- var VERSION = "1.5.0";
49
+ var VERSION = "1.5.1";
50
50
 
51
51
  // src/text-selector.ts
52
52
  var TEXT_PSEUDO_RE = /:(?:text-is|has-text|text)\(/;
package/dist/hj.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env bun
2
- // haltija-cli:do-not-edit v1.5.0
2
+ // haltija-cli:do-not-edit v1.5.1
3
3
  import { createRequire } from "node:module";
4
4
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
5
5
 
@@ -756,7 +756,7 @@ function substituteGeneratedVars(text, seed) {
756
756
  }
757
757
 
758
758
  // bin/version.mjs
759
- var HJ_VERSION = "1.5.0";
759
+ var HJ_VERSION = "1.5.1";
760
760
 
761
761
  // bin/semver.mjs
762
762
  function parseVersion(v) {
@@ -1906,6 +1906,17 @@ function dim2(s) {
1906
1906
  return `\x1B[2m${s}\x1B[0m`;
1907
1907
  }
1908
1908
 
1909
+ // bin/arg-utils.mjs
1910
+ function extractWindowTarget(args) {
1911
+ const i = args.indexOf("--window");
1912
+ if (i === -1 || args[i + 1] === undefined) {
1913
+ return { windowTarget: null, args: [...args] };
1914
+ }
1915
+ const rest = [...args];
1916
+ rest.splice(i, 2);
1917
+ return { windowTarget: args[i + 1], args: rest };
1918
+ }
1919
+
1909
1920
  // bin/hj.mjs
1910
1921
  import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync as readdirSync2 } from "node:fs";
1911
1922
  import { homedir as homedir2 } from "node:os";
@@ -2137,12 +2148,9 @@ if (noLaunchIdx !== -1) {
2137
2148
  noLaunch = true;
2138
2149
  args.splice(noLaunchIdx, 1);
2139
2150
  }
2140
- var windowTarget = null;
2141
- var windowIdx = args.indexOf("--window");
2142
- if (windowIdx !== -1 && args[windowIdx + 1]) {
2143
- windowTarget = args[windowIdx + 1];
2144
- args.splice(windowIdx, 2);
2145
- }
2151
+ var { windowTarget, args: argsWithoutWindow } = extractWindowTarget(args);
2152
+ args.length = 0;
2153
+ args.push(...argsWithoutWindow);
2146
2154
  var explicitTarget = portSource !== "8700 (default)";
2147
2155
  if (args.length >= 2 && isSubcommand(`${args[0]}-${args[1]}`)) {
2148
2156
  args.splice(0, 2, `${args[0]}-${args[1]}`);
package/dist/index.js CHANGED
@@ -674,7 +674,7 @@ var injectorCode = `
674
674
  `;
675
675
 
676
676
  // src/version.ts
677
- var VERSION = "1.5.0";
677
+ var VERSION = "1.5.1";
678
678
 
679
679
  // src/embedded-assets.ts
680
680
  var APP_MD = `# Haltija App
@@ -3097,7 +3097,7 @@ var COMPONENT_JS = `(() => {
3097
3097
  });
3098
3098
 
3099
3099
  // src/version.ts
3100
- var VERSION = "1.5.0";
3100
+ var VERSION = "1.5.1";
3101
3101
 
3102
3102
  // src/text-selector.ts
3103
3103
  var TEXT_PSEUDO_RE = /:(?:text-is|has-text|text)\\(/;
@@ -13727,6 +13727,9 @@ function inferSuggestion(step, pageContext) {
13727
13727
  }
13728
13728
 
13729
13729
  // src/api-handlers.ts
13730
+ function withWarning(body, response) {
13731
+ return response.warning ? { ...body, warning: response.warning } : body;
13732
+ }
13730
13733
  var handlers = new Map;
13731
13734
  function registerHandler(endpoint2, handler) {
13732
13735
  handlers.set(endpoint2.path, handler);
@@ -14557,7 +14560,7 @@ registerHandler(find, async (body, ctx) => {
14557
14560
  })()`;
14558
14561
  const response = await ctx.requestFromBrowser("eval", "exec", { code: findCode }, 5000, windowId);
14559
14562
  if (response.success && response.data) {
14560
- return Response.json({ success: true, ...response.data }, { headers: ctx.headers });
14563
+ return Response.json(withWarning({ success: true, ...response.data }, response), { headers: ctx.headers });
14561
14564
  }
14562
14565
  return Response.json(response, { headers: ctx.headers });
14563
14566
  });
@@ -14663,7 +14666,7 @@ registerHandler(formData, async (body, ctx) => {
14663
14666
  })()`;
14664
14667
  const response = await ctx.requestFromBrowser("eval", "exec", { code: formCode }, 5000, windowId);
14665
14668
  if (response.success && response.data) {
14666
- return Response.json(response.data, { headers: ctx.headers });
14669
+ return Response.json(withWarning(response.data, response), { headers: ctx.headers });
14667
14670
  }
14668
14671
  return Response.json(response, { headers: ctx.headers });
14669
14672
  });
@@ -16438,12 +16441,8 @@ async function requestFromBrowser(channel, action, payload, timeoutMs = 5000, wi
16438
16441
  source: "agent"
16439
16442
  };
16440
16443
  return new Promise((resolve) => {
16441
- const timeout = setTimeout(() => {
16442
- pendingResponses.delete(id);
16443
- resolve({ id, success: false, error: "Timeout", timestamp: Date.now() });
16444
- }, timeoutMs);
16445
16444
  let sentTo = null;
16446
- const resolveWithLiveness = (res) => {
16445
+ const attachWarning = (res) => {
16447
16446
  const hidden = hiddenTabWarning(sentTo);
16448
16447
  const ambiguous = ambiguousFocusWarning({
16449
16448
  windows: Array.from(windows2.values()),
@@ -16453,8 +16452,13 @@ async function requestFromBrowser(channel, action, payload, timeoutMs = 5000, wi
16453
16452
  const warning = [hidden, ambiguous].filter(Boolean).join(`
16454
16453
 
16455
16454
  `);
16456
- resolve(warning ? { ...res, warning } : res);
16455
+ return warning ? { ...res, warning } : res;
16457
16456
  };
16457
+ const timeout = setTimeout(() => {
16458
+ pendingResponses.delete(id);
16459
+ resolve(attachWarning({ id, success: false, error: "Timeout", timestamp: Date.now() }));
16460
+ }, timeoutMs);
16461
+ const resolveWithLiveness = (res) => resolve(attachWarning(res));
16458
16462
  pendingResponses.set(id, { resolve: resolveWithLiveness, timeout });
16459
16463
  if (windowId) {
16460
16464
  const win = windows2.get(windowId);
package/dist/server.js CHANGED
@@ -674,7 +674,7 @@ var injectorCode = `
674
674
  `;
675
675
 
676
676
  // src/version.ts
677
- var VERSION = "1.5.0";
677
+ var VERSION = "1.5.1";
678
678
 
679
679
  // src/embedded-assets.ts
680
680
  var APP_MD = `# Haltija App
@@ -3097,7 +3097,7 @@ var COMPONENT_JS = `(() => {
3097
3097
  });
3098
3098
 
3099
3099
  // src/version.ts
3100
- var VERSION = "1.5.0";
3100
+ var VERSION = "1.5.1";
3101
3101
 
3102
3102
  // src/text-selector.ts
3103
3103
  var TEXT_PSEUDO_RE = /:(?:text-is|has-text|text)\\(/;
@@ -13727,6 +13727,9 @@ function inferSuggestion(step, pageContext) {
13727
13727
  }
13728
13728
 
13729
13729
  // src/api-handlers.ts
13730
+ function withWarning(body, response) {
13731
+ return response.warning ? { ...body, warning: response.warning } : body;
13732
+ }
13730
13733
  var handlers = new Map;
13731
13734
  function registerHandler(endpoint2, handler) {
13732
13735
  handlers.set(endpoint2.path, handler);
@@ -14557,7 +14560,7 @@ registerHandler(find, async (body, ctx) => {
14557
14560
  })()`;
14558
14561
  const response = await ctx.requestFromBrowser("eval", "exec", { code: findCode }, 5000, windowId);
14559
14562
  if (response.success && response.data) {
14560
- return Response.json({ success: true, ...response.data }, { headers: ctx.headers });
14563
+ return Response.json(withWarning({ success: true, ...response.data }, response), { headers: ctx.headers });
14561
14564
  }
14562
14565
  return Response.json(response, { headers: ctx.headers });
14563
14566
  });
@@ -14663,7 +14666,7 @@ registerHandler(formData, async (body, ctx) => {
14663
14666
  })()`;
14664
14667
  const response = await ctx.requestFromBrowser("eval", "exec", { code: formCode }, 5000, windowId);
14665
14668
  if (response.success && response.data) {
14666
- return Response.json(response.data, { headers: ctx.headers });
14669
+ return Response.json(withWarning(response.data, response), { headers: ctx.headers });
14667
14670
  }
14668
14671
  return Response.json(response, { headers: ctx.headers });
14669
14672
  });
@@ -16438,12 +16441,8 @@ async function requestFromBrowser(channel, action, payload, timeoutMs = 5000, wi
16438
16441
  source: "agent"
16439
16442
  };
16440
16443
  return new Promise((resolve) => {
16441
- const timeout = setTimeout(() => {
16442
- pendingResponses.delete(id);
16443
- resolve({ id, success: false, error: "Timeout", timestamp: Date.now() });
16444
- }, timeoutMs);
16445
16444
  let sentTo = null;
16446
- const resolveWithLiveness = (res) => {
16445
+ const attachWarning = (res) => {
16447
16446
  const hidden = hiddenTabWarning(sentTo);
16448
16447
  const ambiguous = ambiguousFocusWarning({
16449
16448
  windows: Array.from(windows2.values()),
@@ -16453,8 +16452,13 @@ async function requestFromBrowser(channel, action, payload, timeoutMs = 5000, wi
16453
16452
  const warning = [hidden, ambiguous].filter(Boolean).join(`
16454
16453
 
16455
16454
  `);
16456
- resolve(warning ? { ...res, warning } : res);
16455
+ return warning ? { ...res, warning } : res;
16457
16456
  };
16457
+ const timeout = setTimeout(() => {
16458
+ pendingResponses.delete(id);
16459
+ resolve(attachWarning({ id, success: false, error: "Timeout", timestamp: Date.now() }));
16460
+ }, timeoutMs);
16461
+ const resolveWithLiveness = (res) => resolve(attachWarning(res));
16458
16462
  pendingResponses.set(id, { resolve: resolveWithLiveness, timeout });
16459
16463
  if (windowId) {
16460
16464
  const win = windows2.get(windowId);
package/dist/version.d.ts CHANGED
@@ -8,4 +8,4 @@
8
8
  * ⚠️ AUTO-GENERATED FROM package.json - DO NOT EDIT THIS FILE
9
9
  * ⚠️ To change the version, update package.json and run: bun run build
10
10
  */
11
- export declare const VERSION = "1.5.0";
11
+ export declare const VERSION = "1.5.1";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "haltija",
3
- "version": "1.5.0",
3
+ "version": "1.5.1",
4
4
  "description": "Browser control for AI agents - query DOM, click, type, run JS, watch mutations",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",