@addai/node 0.30.3 → 0.30.5

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.
@@ -115,6 +115,38 @@ const FIRST_RUN = ' --no-first-run --no-default-browser-check --hide-crash-resto
115
115
  /** The in-container script that signs the browser in. Beside the extension,
116
116
  * under the same bind mount, so it arrives by the same route. */
117
117
  exports.SEED_SCRIPT = '/conf/vault/vault-seed.mjs';
118
+ /**
119
+ * Chrome's profile, on the mount, and named.
120
+ *
121
+ * ⚠️ This one flag fixes three separate things, which is why it is worth the
122
+ * one-off cost below.
123
+ *
124
+ * THE DEBUG PORT. Chrome has refused --remote-debugging-port on the DEFAULT
125
+ * user data directory since M136. We have always passed the port and never
126
+ * passed a directory, so the port has never listened — verified on a live
127
+ * container, where Chrome's own command line carried
128
+ * --remote-debugging-port=9222 while nothing was bound to 9222. Every reason
129
+ * the wrapper exists — +Ai Learn watching which control was clicked, the vault
130
+ * seeder attaching — has therefore been quietly dead on modern Chrome.
131
+ *
132
+ * THE PROFILE. Chrome's default lives in the container's writable layer, which
133
+ * a rebuild destroys. So every login an entity has ever made was thrown away
134
+ * the next time its desktop was rebuilt. /conf is bind-mounted from the host
135
+ * (provider.ts publishes it), so a profile here SURVIVES — which is the first
136
+ * time that has been true.
137
+ *
138
+ * THE BOT WALL. A profile with no history and no cookies is a brand-new
139
+ * stranger, and Google treats it like one. That is a large part of why an
140
+ * entity meets a CAPTCHA on an ordinary search. A profile that persists earns
141
+ * reputation instead of resetting it daily.
142
+ *
143
+ * ⚠️ THE ONE-OFF COST, stated plainly: this abandons whatever is in the
144
+ * current default profile, so an entity signed into something appears signed
145
+ * out, once. That profile was already destroyed on every rebuild, so nothing
146
+ * durable is being lost — the loss has simply been happening quietly, and this
147
+ * is the last time.
148
+ */
149
+ const PROFILE_DIR = '/conf/browser-profile';
118
150
  /**
119
151
  * The wrapper, exactly.
120
152
  *
@@ -139,10 +171,33 @@ function wrapperScript(realBrowser, extensionDir) {
139
171
  const seed = extensionDir
140
172
  ? `[ -f ${exports.SEED_SCRIPT} ] && (node ${exports.SEED_SCRIPT} >/dev/null 2>&1 &)\n`
141
173
  : '';
174
+ // ⚠️ A lock that now outlives the machine that made it.
175
+ //
176
+ // Chrome writes SingletonLock as a symlink to "<hostname>-<pid>" and refuses
177
+ // to open a profile locked by somebody else. That was harmless while the
178
+ // profile lived in the container's writable layer, because a rebuild
179
+ // destroyed the lock along with everything else. Putting the profile on the
180
+ // mount to make it SURVIVE means the lock survives too — and a container's
181
+ // hostname is its id, which changes every rebuild. So the next container
182
+ // finds a lock owned by a machine that no longer exists and shows
183
+ //
184
+ // The profile appears to be in use by another Google Chrome process
185
+ // (674) on another computer (7fd0c26e0fdf)
186
+ //
187
+ // instead of the page it was asked for. Persistence brought this with it;
188
+ // it is the cost of the same change, and it needed paying here.
189
+ //
190
+ // Guarded on nothing being alive, which is what makes this safe rather than
191
+ // reckless: two Chromes on one profile corrupts it. If one IS running, the
192
+ // lock is real and must stand — the singleton then does its actual job and
193
+ // hands the url to the running browser, which is the behaviour we want.
194
+ const unlock = `pgrep -f '${realBrowser}' >/dev/null 2>&1 || `
195
+ + `rm -f ${PROFILE_DIR}/SingletonLock ${PROFILE_DIR}/SingletonCookie `
196
+ + `${PROFILE_DIR}/SingletonSocket\n`;
142
197
  return `#!/bin/sh
143
198
  # Written by @addai/node so +Ai Learn can watch this browser${extensionDir ? ' and the +Ai Vault can fill logins in it' : ''}.
144
199
  # Removing it costs you the page detail in a lesson${extensionDir ? ' and vault autofill' : ''} and nothing else.
145
- ${seed}exec ${realBrowser} --remote-debugging-port=${exports.DEBUG_PORT} --remote-debugging-address=127.0.0.1${FIRST_RUN}${ext} "$@"
200
+ ${unlock}${seed}exec ${realBrowser} --user-data-dir=${PROFILE_DIR} --remote-debugging-port=${exports.DEBUG_PORT} --remote-debugging-address=127.0.0.1${FIRST_RUN}${ext} "$@"
146
201
  `;
147
202
  }
148
203
  /** Is what is on disk already what we would write? Exact match, so a change to
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@addai/node",
3
- "version": "0.30.3",
3
+ "version": "0.30.5",
4
4
  "description": "Daemon that pairs a machine with your +Ai account and runs Claude / Codex / Kimi / Gemini agents on its behalf. Reachable via Supabase from Vault, Entity Studio, or any other +Ai surface.",
5
5
  "license": "MIT",
6
6
  "keywords": [