@dashup/connector 0.1.8 → 0.1.9

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.
Files changed (3) hide show
  1. package/README.md +6 -20
  2. package/dist/index.js +336 -584
  3. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -42,7 +42,7 @@ var require_XMLHttpRequest = __commonJS({
42
42
  "use strict";
43
43
  var fs = __require("fs");
44
44
  var Url = __require("url");
45
- var spawn7 = __require("child_process").spawn;
45
+ var spawn6 = __require("child_process").spawn;
46
46
  module.exports = XMLHttpRequest3;
47
47
  XMLHttpRequest3.XMLHttpRequest = XMLHttpRequest3;
48
48
  function XMLHttpRequest3(opts) {
@@ -338,7 +338,7 @@ var require_XMLHttpRequest = __commonJS({
338
338
  var syncFile = ".node-xmlhttprequest-sync-" + process.pid;
339
339
  fs.writeFileSync(syncFile, "", "utf8");
340
340
  var execString = "var http = require('http'), https = require('https'), fs = require('fs');var doRequest = http" + (ssl ? "s" : "") + ".request;var options = " + JSON.stringify(options) + ";var responseText = '';var responseData = Buffer.alloc(0);var req = doRequest(options, function(response) {response.on('data', function(chunk) { var data = Buffer.from(chunk); responseText += data.toString('utf8'); responseData = Buffer.concat([responseData, data]);});response.on('end', function() {fs.writeFileSync('" + contentFile + "', JSON.stringify({err: null, data: {statusCode: response.statusCode, headers: response.headers, text: responseText, data: responseData.toString('base64')}}), 'utf8');fs.unlinkSync('" + syncFile + "');});response.on('error', function(error) {fs.writeFileSync('" + contentFile + "', 'NODE-XMLHTTPREQUEST-ERROR:' + JSON.stringify(error), 'utf8');fs.unlinkSync('" + syncFile + "');});}).on('error', function(error) {fs.writeFileSync('" + contentFile + "', 'NODE-XMLHTTPREQUEST-ERROR:' + JSON.stringify(error), 'utf8');fs.unlinkSync('" + syncFile + "');});" + (data ? "req.write('" + JSON.stringify(data).slice(1, -1).replace(/'/g, "\\'") + "');" : "") + "req.end();";
341
- var syncProc = spawn7(process.argv[0], ["-e", execString]);
341
+ var syncProc = spawn6(process.argv[0], ["-e", execString]);
342
342
  var statusText;
343
343
  while (fs.existsSync(syncFile)) {
344
344
  }
@@ -1849,7 +1849,7 @@ var require_dist = __commonJS({
1849
1849
  var require_eventemitter3 = __commonJS({
1850
1850
  "../../node_modules/.pnpm/eventemitter3@5.0.4/node_modules/eventemitter3/index.js"(exports, module) {
1851
1851
  "use strict";
1852
- var has2 = Object.prototype.hasOwnProperty;
1852
+ var has = Object.prototype.hasOwnProperty;
1853
1853
  var prefix2 = "~";
1854
1854
  function Events() {
1855
1855
  }
@@ -1884,7 +1884,7 @@ var require_eventemitter3 = __commonJS({
1884
1884
  var names = [], events, name;
1885
1885
  if (this._eventsCount === 0) return names;
1886
1886
  for (name in events = this._events) {
1887
- if (has2.call(events, name)) names.push(prefix2 ? name.slice(1) : name);
1887
+ if (has.call(events, name)) names.push(prefix2 ? name.slice(1) : name);
1888
1888
  }
1889
1889
  if (Object.getOwnPropertySymbols) {
1890
1890
  return names.concat(Object.getOwnPropertySymbols(events));
@@ -2726,6 +2726,7 @@ var PRODUCT = {
2726
2726
  var PAIRING_TTL_MS = 5 * 6e4;
2727
2727
  var PROTOCOL_VERSION = 1;
2728
2728
  var MAX_ENVELOPE_BYTES = 8 * 1024 * 1024;
2729
+ var HISTORY_PAGE_ITEMS = 30;
2729
2730
  var HISTORY_TEXT_CHARS = 4e3;
2730
2731
  var HISTORY_PAGE_BYTES = 256 * 1024;
2731
2732
  var HISTORY_BATCH_ITEMS = 50;
@@ -3615,8 +3616,6 @@ function loadConfig() {
3615
3616
  api: PRODUCT.api,
3616
3617
  secret: "",
3617
3618
  pairs: {},
3618
- screen: false,
3619
- control: false,
3620
3619
  outbox: [],
3621
3620
  localSecret: ""
3622
3621
  };
@@ -4017,7 +4016,7 @@ import * as ui from "@clack/prompts";
4017
4016
  import { z } from "zod";
4018
4017
 
4019
4018
  // package.json
4020
- var version = "0.1.8";
4019
+ var version = "0.1.9";
4021
4020
 
4022
4021
  // src/pair.ts
4023
4022
  var Invitation = z.object({
@@ -4147,282 +4146,19 @@ async function link(config2, account) {
4147
4146
  return true;
4148
4147
  }
4149
4148
 
4150
- // src/viewer.ts
4151
- import { existsSync as existsSync2 } from "node:fs";
4152
- import { fileURLToPath } from "node:url";
4153
- import { createInterface as createInterface2 } from "node:readline";
4154
- import { spawn as spawn2 } from "node:child_process";
4155
-
4156
- // src/screen.ts
4157
- import { promisify as promisify2 } from "node:util";
4158
- import { execFile as execFile2, spawnSync } from "node:child_process";
4159
- var run = promisify2(execFile2);
4160
- var powershell = (source) => run("powershell.exe", [
4161
- "-NoProfile",
4162
- "-NonInteractive",
4163
- "-EncodedCommand",
4164
- Buffer.from(source, "utf16le").toString("base64")
4165
- ]);
4166
- var psString = (value2) => `[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${Buffer.from(value2).toString("base64")}'))`;
4167
- function has(command2) {
4168
- return spawnSync(process.platform === "win32" ? "where.exe" : "which", [command2], { stdio: "ignore" }).status === 0;
4169
- }
4170
- function controlAvailable() {
4171
- return process.platform === "darwin" || process.platform === "win32" || has(process.env.WAYLAND_DISPLAY ? "ydotool" : "xdotool");
4172
- }
4173
- var KEYS = {
4174
- a: { x11: "a", linux: 30, mac: "a", windows: "a" },
4175
- c: { x11: "c", linux: 46, mac: "c", windows: "c" },
4176
- v: { x11: "v", linux: 47, mac: "v", windows: "v" },
4177
- z: { x11: "z", linux: 44, mac: "z", windows: "z" },
4178
- Tab: { x11: "Tab", linux: 15, mac: 48, windows: "{TAB}" },
4179
- Enter: { x11: "Return", linux: 28, mac: 36, windows: "{ENTER}" },
4180
- Escape: { x11: "Escape", linux: 1, mac: 53, windows: "{ESC}" },
4181
- ArrowUp: { x11: "Up", linux: 103, mac: 126, windows: "{UP}" },
4182
- Backspace: { x11: "BackSpace", linux: 14, mac: 51, windows: "{BACKSPACE}" },
4183
- ArrowDown: { x11: "Down", linux: 108, mac: 125, windows: "{DOWN}" },
4184
- ArrowLeft: { x11: "Left", linux: 105, mac: 123, windows: "{LEFT}" },
4185
- ArrowRight: { x11: "Right", linux: 106, mac: 124, windows: "{RIGHT}" }
4186
- };
4187
- var MODIFIERS = {
4188
- alt: { x11: "alt", linux: 56, mac: "option down", windows: "%" },
4189
- meta: { x11: "super", linux: 125, mac: "command down", windows: "" },
4190
- shift: { x11: "shift", linux: 42, mac: "shift down", windows: "+" },
4191
- control: { x11: "ctrl", linux: 29, mac: "control down", windows: "^" }
4192
- };
4193
- var SCROLL_LIMIT = 10;
4194
- var WINDOWS_INPUT = `Add-Type -TypeDefinition 'using System; using System.Runtime.InteropServices; public class DashupInput { [DllImport("user32.dll")] public static extern bool SetCursorPos(int x,int y); [DllImport("user32.dll")] public static extern void mouse_event(uint f,uint x,uint y,int d,UIntPtr e); [DllImport("user32.dll")] public static extern void keybd_event(byte v,byte s,uint f,UIntPtr e); }'; `;
4195
- async function controlScreen(input) {
4196
- if (input.kind === "click" && (!Number.isInteger(input.x) || !Number.isInteger(input.y) || input.x < -32768 || input.y < -32768 || input.x > 32767 || input.y > 32767))
4197
- throw new Error("Invalid screen coordinates");
4198
- if (input.kind === "text" && (typeof input.text !== "string" || input.text.length > 16e3))
4199
- throw new Error("Text is too long");
4200
- if (input.kind === "scroll" && (!Number.isInteger(input.dx) || !Number.isInteger(input.dy) || Math.abs(input.dx) > SCROLL_LIMIT || Math.abs(input.dy) > SCROLL_LIMIT))
4201
- throw new Error("Invalid scroll distance");
4202
- const key = input.kind === "key" && Object.hasOwn(KEYS, input.key) ? KEYS[input.key] : void 0;
4203
- if (input.kind === "key" && !key) throw new Error("This key is not supported");
4204
- const modifiers = input.kind === "key" ? input.modifiers ?? [] : [];
4205
- if (!Array.isArray(modifiers) || modifiers.length > 4 || modifiers.some((modifier) => !Object.hasOwn(MODIFIERS, modifier)))
4206
- throw new Error("This modifier is not supported");
4207
- const chord = [...new Set(modifiers)].map((modifier) => MODIFIERS[modifier]);
4208
- if (process.platform === "darwin") {
4209
- if (input.kind === "scroll") {
4210
- await run("osascript", [
4211
- "-l",
4212
- "JavaScript",
4213
- "-e",
4214
- `ObjC.import('ApplicationServices'); const event = $.CGEventCreateScrollWheelEvent2(null, 0, 2, ${-input.dy}, ${-input.dx}, 0); if (!event) throw Error('Could not create scroll event'); $.CGEventPost(0, event); $.CFRelease(event);`
4215
- ]);
4216
- return;
4217
- }
4218
- const command2 = input.kind === "click" ? `click at {${input.x}, ${input.y}}` : input.kind === "key" ? `${typeof key.mac === "number" ? "key code " + key.mac : "keystroke " + JSON.stringify(key.mac)}${chord.length ? " using {" + chord.map((modifier) => modifier.mac).join(", ") + "}" : ""}` : `keystroke ${JSON.stringify(input.text)}`;
4219
- await run("osascript", ["-e", `tell application "System Events" to ${command2}`]);
4220
- } else if (process.platform === "win32") {
4221
- if (input.kind === "click" || input.kind === "scroll") {
4222
- const mouse = (flags, data = 0) => `[DashupInput]::mouse_event(${flags},0,0,${data},[UIntPtr]::Zero); `;
4223
- await powershell(
4224
- WINDOWS_INPUT + (input.kind === "click" ? `[DashupInput]::SetCursorPos(${input.x},${input.y}); ` + mouse(2) + mouse(4) : mouse(2048, -input.dy * 120) + mouse(4096, input.dx * 120))
4225
- );
4226
- } else {
4227
- const text3 = input.kind === "key" ? chord.map((modifier) => modifier.windows).join("") + key.windows : input.text.replace(/[+^%~(){}\[\]]/g, "{$&}");
4228
- const send = `Add-Type -AssemblyName System.Windows.Forms; [Windows.Forms.SendKeys]::SendWait(${psString(text3)})`;
4229
- await powershell(
4230
- modifiers.includes("meta") ? WINDOWS_INPUT + `[DashupInput]::keybd_event(91,0,0,[UIntPtr]::Zero); try { ${send} } finally { [DashupInput]::keybd_event(91,0,2,[UIntPtr]::Zero) }` : send
4231
- );
4232
- }
4233
- } else if (process.env.WAYLAND_DISPLAY) {
4234
- if (input.kind === "text") await run("ydotool", ["type", "--", input.text]);
4235
- else if (input.kind === "key")
4236
- await run("ydotool", [
4237
- "key",
4238
- ...chord.map((modifier) => `${modifier.linux}:1`),
4239
- `${key.linux}:1`,
4240
- `${key.linux}:0`,
4241
- ...[...chord].reverse().map((modifier) => `${modifier.linux}:0`)
4242
- ]);
4243
- else if (input.kind === "scroll")
4244
- await run("ydotool", [
4245
- "mousemove",
4246
- "--wheel",
4247
- "-x",
4248
- String(input.dx),
4249
- "-y",
4250
- String(-input.dy)
4251
- ]);
4252
- else {
4253
- await run("ydotool", [
4254
- "mousemove",
4255
- "--absolute",
4256
- "-x",
4257
- String(input.x),
4258
- "-y",
4259
- String(input.y)
4260
- ]);
4261
- await run("ydotool", ["click", "0xC0"]);
4262
- }
4263
- } else {
4264
- if (input.kind === "scroll") {
4265
- for (const [distance, negative, positive] of [
4266
- [input.dy, 4, 5],
4267
- [input.dx, 6, 7]
4268
- ])
4269
- if (distance)
4270
- await run("xdotool", [
4271
- "click",
4272
- "--clearmodifiers",
4273
- "--repeat",
4274
- String(Math.abs(distance)),
4275
- "--delay",
4276
- "0",
4277
- String(distance < 0 ? negative : positive)
4278
- ]);
4279
- return;
4280
- }
4281
- await run(
4282
- "xdotool",
4283
- input.kind === "click" ? ["mousemove", String(input.x), String(input.y), "click", "1"] : input.kind === "key" ? [
4284
- "key",
4285
- "--clearmodifiers",
4286
- [...chord.map((modifier) => modifier.x11), key.x11].join("+")
4287
- ] : ["type", "--clearmodifiers", "--", input.text]
4288
- );
4289
- }
4290
- }
4291
-
4292
- // src/viewer.ts
4293
- var DESKTOP_BIN = process.env.DASHUP_DESKTOP_BIN ?? fileURLToPath(
4294
- new URL(
4295
- `../../../packages/desktop/target/release/dashup-desktop${process.platform === "win32" ? ".exe" : ""}`,
4296
- import.meta.url
4297
- )
4298
- );
4299
- var Viewer = class _Viewer {
4300
- constructor(config2, isActive, send) {
4301
- this.config = config2;
4302
- this.isActive = isActive;
4303
- this.send = send;
4304
- }
4305
- config;
4306
- isActive;
4307
- send;
4308
- sessions = /* @__PURE__ */ new Map();
4309
- process = null;
4310
- pending = null;
4311
- static available() {
4312
- return existsSync2(DESKTOP_BIN);
4313
- }
4314
- async start(pair, projectId, session) {
4315
- if (!this.config.screen || !this.isActive(pair))
4316
- throw new Error("Screen access is no longer enabled for this pairing");
4317
- if (!_Viewer.available())
4318
- throw new Error("Build the Dashup desktop viewer helper on this computer first");
4319
- if (this.sessions.has(session) || this.pending?.id === session)
4320
- throw new Error("Screen session already exists");
4321
- const previous = [...this.sessions];
4322
- this.close();
4323
- const pending = { pair, projectId, id: session };
4324
- this.pending = pending;
4325
- try {
4326
- for (const [id, current] of previous) await this.sendClose(id, current);
4327
- if (this.pending !== pending) throw new Error("Screen request was cancelled");
4328
- const { iceServers } = await api(this.config, "/dashup/ice");
4329
- if (this.pending !== pending || !this.config.screen || !this.isActive(pair))
4330
- throw new Error("Screen access is no longer enabled for this pairing");
4331
- const child = spawn2(DESKTOP_BIN, [], { stdio: ["pipe", "pipe", "pipe"], windowsHide: true });
4332
- this.process = child;
4333
- let events = Promise.resolve();
4334
- createInterface2({ input: child.stdout }).on("error", () => this.failed(child)).on("line", (line) => {
4335
- events = events.then(() => {
4336
- if (this.process === child) return this.receive(line);
4337
- }).catch(() => {
4338
- process.stderr.write("Desktop viewer event failed\n");
4339
- });
4340
- });
4341
- child.stderr.resume();
4342
- child.stdin.on("error", () => this.failed(child));
4343
- child.once("error", () => this.failed(child));
4344
- child.once("exit", () => this.failed(child));
4345
- this.sessions.set(session, { pair, projectId });
4346
- this.write({ kind: "start", session, iceServers });
4347
- } finally {
4348
- if (this.pending === pending) this.pending = null;
4349
- }
4350
- }
4351
- signal(pair, projectId, session, signal) {
4352
- const current = this.sessions.get(session);
4353
- const pending = this.pending?.id === session ? this.pending : null;
4354
- const owner = current ?? pending;
4355
- if (!owner || owner.pair.id !== pair.id || owner.projectId !== projectId || !this.config.screen || !this.isActive(pair))
4356
- throw new Error("Screen session is no longer active");
4357
- if (signal.kind === "close") this.stop(pair, session);
4358
- else if (!current) throw new Error("Screen session is not ready");
4359
- else this.write({ kind: "signal", session, signal });
4360
- }
4361
- stop(pair, session) {
4362
- if (this.pending?.id === session && this.pending.pair.id === pair.id) this.pending = null;
4363
- if (this.sessions.get(session)?.pair.id !== pair.id) return;
4364
- this.write({ kind: "stop", session });
4365
- this.sessions.delete(session);
4366
- if (!this.sessions.size) this.close();
4367
- }
4368
- revoke(pairings) {
4369
- if (this.pending && !pairings.has(this.pending.pair.id)) this.pending = null;
4370
- for (const [session, current] of this.sessions)
4371
- if (!pairings.has(current.pair.id)) this.stop(current.pair, session);
4372
- }
4373
- close() {
4374
- const child = this.process;
4375
- this.pending = null;
4376
- this.process = null;
4377
- this.sessions.clear();
4378
- child?.kill();
4379
- }
4380
- sendClose(sessionId, { pair, projectId }) {
4381
- return this.send(pair, { type: "signal", projectId, sessionId, signal: { kind: "close" } });
4382
- }
4383
- write(value2) {
4384
- this.process?.stdin.write(JSON.stringify(value2) + "\n");
4385
- }
4386
- failed(child) {
4387
- if (this.process !== child) return;
4388
- const sessions = [...this.sessions];
4389
- this.close();
4390
- for (const [sessionId, current] of sessions)
4391
- void this.sendClose(sessionId, current).catch(() => process.stderr.write("Desktop viewer closed\n"));
4392
- }
4393
- async receive(line) {
4394
- const event = JSON.parse(line);
4395
- const current = this.sessions.get(event.session);
4396
- if (!current || !this.config.screen || !this.isActive(current.pair)) return;
4397
- if (event.kind === "signal" && event.signal)
4398
- await this.send(current.pair, {
4399
- type: "signal",
4400
- sessionId: event.session,
4401
- projectId: current.projectId,
4402
- signal: event.signal
4403
- });
4404
- else if (event.kind === "input" && event.input && this.config.control)
4405
- await controlScreen(event.input);
4406
- else if (event.kind === "error" || event.state === "failed" || event.state === "closed" || event.state === "disconnected") {
4407
- this.stop(current.pair, event.session);
4408
- await this.sendClose(event.session, current);
4409
- }
4410
- }
4411
- };
4412
-
4413
4149
  // src/discovery.ts
4414
4150
  import { homedir as homedir3 } from "node:os";
4415
4151
  import { createHmac } from "node:crypto";
4416
4152
  import { basename, dirname as dirname3, join as join3 } from "node:path";
4417
4153
  import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
4418
4154
  import { zstdDecompressSync } from "node:zlib";
4419
- import { watch, existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync2 } from "node:fs";
4155
+ import { watch, existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync2 } from "node:fs";
4420
4156
 
4421
4157
  // src/harness/history.ts
4422
4158
  import { homedir as homedir2 } from "node:os";
4423
4159
  import { DatabaseSync } from "node:sqlite";
4424
4160
  import { join as join2, dirname as dirname2, resolve as resolve2, isAbsolute } from "node:path";
4425
- import { closeSync as closeSync2, existsSync as existsSync3, openSync as openSync2, readSync, realpathSync, readdirSync, statSync } from "node:fs";
4161
+ import { closeSync as closeSync2, existsSync as existsSync2, openSync as openSync2, readSync, realpathSync, readdirSync, statSync } from "node:fs";
4426
4162
 
4427
4163
  // src/harness/session.ts
4428
4164
  var EARLIER = {
@@ -4438,7 +4174,7 @@ function agentHarness(name) {
4438
4174
  return void 0;
4439
4175
  }
4440
4176
  var MAX_PREVIEW = 64 * 1024;
4441
- var SCREEN_PREVIEW = "Open Screen to review the full request before approving.";
4177
+ var NO_PREVIEW = "Review the full request on your computer before approving.";
4442
4178
 
4443
4179
  // src/harness/history.ts
4444
4180
  var SAMPLE_BYTES = 128 * 1024;
@@ -4482,10 +4218,10 @@ function readRange(path, start, length) {
4482
4218
  closeSync2(fd);
4483
4219
  }
4484
4220
  }
4485
- function sample(path, until) {
4221
+ function described(path, until, describe2) {
4486
4222
  const stamp = statSync(path);
4487
4223
  const saved = metadata.get(path);
4488
- if (saved && unchanged(saved.stamp, stamp)) return saved.rows;
4224
+ if (saved && unchanged(saved.stamp, stamp)) return saved.value;
4489
4225
  let head = readRange(path, 0, Math.min(stamp.size, SAMPLE_BYTES));
4490
4226
  if (head.length < stamp.size && head.at(-1) !== 10) {
4491
4227
  const chunks = [head];
@@ -4508,9 +4244,9 @@ function sample(path, until) {
4508
4244
  );
4509
4245
  if (start === head.length || tail.some(until)) break;
4510
4246
  }
4511
- const rows = [...records(head), ...tail];
4512
- metadata.set(path, { stamp, rows });
4513
- return rows;
4247
+ const value2 = describe2([...records(head), ...tail], stamp);
4248
+ metadata.set(path, { stamp, value: value2 });
4249
+ return value2;
4514
4250
  }
4515
4251
  var windowed = (path, messages5) => journals.get(path)?.start ? [EARLIER, ...messages5] : messages5;
4516
4252
  function journal(path, latest = false) {
@@ -4552,7 +4288,7 @@ function journal(path, latest = false) {
4552
4288
  return tail ? [...saved.rows, tail] : saved.rows;
4553
4289
  }
4554
4290
  function files(root, scan, depth = Infinity) {
4555
- if (!existsSync3(root)) {
4291
+ if (!existsSync2(root)) {
4556
4292
  watchParent(root, scan);
4557
4293
  return [];
4558
4294
  }
@@ -4569,8 +4305,8 @@ function files(root, scan, depth = Infinity) {
4569
4305
  }
4570
4306
  }
4571
4307
  function watchParent(path, scan) {
4572
- while (!existsSync3(path) && dirname2(path) !== path) path = dirname2(path);
4573
- if (existsSync3(path)) scan.paths.add(path);
4308
+ while (!existsSync2(path) && dirname2(path) !== path) path = dirname2(path);
4309
+ if (existsSync2(path)) scan.paths.add(path);
4574
4310
  }
4575
4311
  function canonical(cwd) {
4576
4312
  if (typeof cwd !== "string" || !isAbsolute(cwd)) return void 0;
@@ -4731,26 +4467,31 @@ function claudeConversation(conversation, rows) {
4731
4467
  function claude(root, scan) {
4732
4468
  for (const path of files(join2(root, "projects"), scan, 1).filter((file) => file.endsWith(".jsonl"))) {
4733
4469
  try {
4734
- const rows = sample(path, claudeMessage);
4735
- const latest = [...rows].reverse();
4736
- const owner = rows.find((row) => typeof row.sessionId === "string" && canonical(row.cwd));
4737
- if (!owner) {
4470
+ const session = described(path, claudeMessage, (rows, stamp) => {
4471
+ const latest = [...rows].reverse();
4472
+ const owner = rows.find((row) => typeof row.sessionId === "string" && canonical(row.cwd));
4473
+ if (!owner) return void 0;
4474
+ const named = latest.find((row) => row.type === "custom-title" || row.type === "ai-title");
4475
+ const first = rows.find(
4476
+ (row) => row.type === "user" && !row.isMeta && !row.isSidechain && !injected(row.message?.content)
4477
+ );
4478
+ return {
4479
+ id: String(owner.sessionId),
4480
+ cwd: canonical(owner.cwd),
4481
+ title: title(
4482
+ named?.customTitle ?? named?.aiTitle ?? text(first?.message?.content),
4483
+ "Claude Code thread"
4484
+ ),
4485
+ updatedAt: date(latest.find(claudeMessage)?.timestamp, stamp.mtimeMs)
4486
+ };
4487
+ });
4488
+ if (!session) {
4738
4489
  scan.warnings.push(`Unrecognized Claude Code session: ${path}`);
4739
4490
  continue;
4740
4491
  }
4741
- const named = latest.find((row) => row.type === "custom-title" || row.type === "ai-title");
4742
- const first = rows.find(
4743
- (row) => row.type === "user" && !row.isMeta && !row.isSidechain && !injected(row.message?.content)
4744
- );
4745
4492
  scan.sessions.push({
4746
4493
  harness: "claude",
4747
- id: owner.sessionId,
4748
- cwd: canonical(owner.cwd),
4749
- title: title(
4750
- named?.customTitle ?? named?.aiTitle ?? text(first?.message?.content),
4751
- "Claude Code thread"
4752
- ),
4753
- updatedAt: date(latest.find(claudeMessage)?.timestamp, statSync(path).mtimeMs),
4494
+ ...session,
4754
4495
  files: [path],
4755
4496
  read: () => claudeMessages(path)
4756
4497
  });
@@ -4822,7 +4563,7 @@ function codexMessages(path) {
4822
4563
  function codex(root, scan) {
4823
4564
  const index = join2(root, "session_index.jsonl");
4824
4565
  const names = /* @__PURE__ */ new Map();
4825
- if (existsSync3(index)) {
4566
+ if (existsSync2(index)) {
4826
4567
  scan.paths.add(root);
4827
4568
  try {
4828
4569
  for (const row of journal(index)) if (typeof row.id === "string") names.set(row.id, row);
@@ -4831,34 +4572,41 @@ function codex(root, scan) {
4831
4572
  }
4832
4573
  }
4833
4574
  const titles = codexTitles(root);
4575
+ const indexed = existsSync2(index);
4834
4576
  for (const path of [
4835
4577
  ...files(join2(root, "sessions"), scan),
4836
4578
  ...files(join2(root, "archived_sessions"), scan)
4837
4579
  ].filter((file) => file.endsWith(".jsonl"))) {
4838
4580
  try {
4839
- const rows = sample(path, codexItem);
4840
- const latest = [...rows].reverse();
4841
- const owner = rows.find((row) => row.type === "session_meta")?.payload;
4842
- const cwd = canonical(owner?.cwd);
4843
- if (typeof owner?.id !== "string" || !cwd) {
4581
+ const session = described(path, codexItem, (rows, stamp) => {
4582
+ const owner = rows.find((row) => row.type === "session_meta")?.payload;
4583
+ const cwd = canonical(owner?.cwd);
4584
+ if (typeof owner?.id !== "string" || !cwd) return void 0;
4585
+ const first = rows.find(
4586
+ (row) => row.type === "response_item" && row.payload?.type === "message" && row.payload.role === "user" && !injected(row.payload.content)
4587
+ );
4588
+ const parent = owner.parent_thread_id;
4589
+ return {
4590
+ id: owner.id,
4591
+ cwd,
4592
+ asked: text(first?.payload?.content),
4593
+ parent: typeof parent === "string" ? parent : void 0,
4594
+ updatedAt: date([...rows].reverse().find(codexItem)?.timestamp, stamp.mtimeMs)
4595
+ };
4596
+ });
4597
+ if (!session) {
4844
4598
  scan.warnings.push(`Unrecognized Codex session: ${path}`);
4845
4599
  continue;
4846
4600
  }
4847
- const named = names.get(owner.id);
4848
- const first = rows.find(
4849
- (row) => row.type === "response_item" && row.payload?.type === "message" && row.payload.role === "user" && !injected(row.payload.content)
4850
- );
4601
+ const { asked, ...found } = session;
4851
4602
  scan.sessions.push({
4852
4603
  harness: "codex",
4853
- id: owner.id,
4854
- cwd,
4604
+ ...found,
4855
4605
  title: title(
4856
- named?.thread_name ?? titles.get(owner.id) ?? text(first?.payload?.content),
4606
+ names.get(found.id)?.thread_name ?? titles.get(found.id) ?? asked,
4857
4607
  "Codex thread"
4858
4608
  ),
4859
- parent: typeof owner.parent_thread_id === "string" ? owner.parent_thread_id : void 0,
4860
- updatedAt: date(latest.find(codexItem)?.timestamp, statSync(path).mtimeMs),
4861
- files: [path, ...existsSync3(index) ? [index] : []],
4609
+ files: [path, ...indexed ? [index] : []],
4862
4610
  read: () => codexMessages(path)
4863
4611
  });
4864
4612
  } catch {
@@ -4925,17 +4673,22 @@ function databaseMessages(path, session) {
4925
4673
  }
4926
4674
  }
4927
4675
  function codexSubagent(row) {
4928
- const spawn7 = object(object(parse(String(row.source ?? "")))?.subagent)?.thread_spawn;
4929
- const task = typeof spawn7?.agent_path === "string" ? spawn7.agent_path.split("/").pop() : "";
4676
+ const spawn6 = object(object(parse(String(row.source ?? "")))?.subagent)?.thread_spawn;
4677
+ const task = typeof spawn6?.agent_path === "string" ? spawn6.agent_path.split("/").pop() : "";
4930
4678
  return [row.agent_nickname, task?.replace(/_/g, " ")].filter(Boolean).join(" \xB7 ");
4931
4679
  }
4680
+ var codexState;
4932
4681
  function codexTitles(root) {
4933
4682
  const titles = /* @__PURE__ */ new Map();
4934
- const state = (existsSync3(root) ? readdirSync(root) : []).filter((name) => /^state_\d+\.sqlite$/.test(name)).sort((a, b) => Number(b.match(/\d+/)[0]) - Number(a.match(/\d+/)[0]))[0];
4683
+ const state = (existsSync2(root) ? readdirSync(root) : []).filter((name) => /^state_\d+\.sqlite$/.test(name)).sort((a, b) => Number(b.match(/\d+/)[0]) - Number(a.match(/\d+/)[0]))[0];
4935
4684
  if (!state) return titles;
4685
+ const path = join2(root, state);
4686
+ const stamp = [path, `${path}-wal`].map((file) => statSync(file, { throwIfNoEntry: false })).map((found) => found ? `${found.ino}:${found.size}:${found.mtimeMs}` : "").join("|");
4687
+ if (codexState?.stamp === stamp) return codexState.titles;
4688
+ codexState = { stamp, titles };
4936
4689
  let db;
4937
4690
  try {
4938
- db = openDatabase(join2(root, state));
4691
+ db = openDatabase(path);
4939
4692
  for (const row of db.prepare("SELECT id, title, first_user_message, source, agent_nickname FROM threads").all()) {
4940
4693
  const name = [row.title, row.first_user_message].find(
4941
4694
  (value2) => typeof value2 === "string" && !!value2.trim() && !injected(value2)
@@ -4949,7 +4702,7 @@ function codexTitles(root) {
4949
4702
  return titles;
4950
4703
  }
4951
4704
  function openCodeDatabase(path, scan) {
4952
- if (!existsSync3(path)) {
4705
+ if (!existsSync2(path)) {
4953
4706
  watchParent(path, scan);
4954
4707
  return;
4955
4708
  }
@@ -5011,7 +4764,7 @@ function legacyMessages(root, session) {
5011
4764
  }
5012
4765
  function openCodeLegacy(root, scan) {
5013
4766
  const parts2 = join2(root, "part");
5014
- if (existsSync3(parts2)) files(parts2, scan, 1);
4767
+ if (existsSync2(parts2)) files(parts2, scan, 1);
5015
4768
  for (const path of files(join2(root, "session"), scan).filter((file) => file.endsWith(".json"))) {
5016
4769
  try {
5017
4770
  const row = jsonFile(path);
@@ -5064,7 +4817,22 @@ function scanHistories() {
5064
4817
  }
5065
4818
 
5066
4819
  // src/discovery.ts
5067
- var opaqueId = (value2, secret) => createHmac("sha256", secret).update(value2).digest("hex").slice(0, 32);
4820
+ var READ_ONLY = {
4821
+ read: true,
4822
+ send: false,
4823
+ queue: false,
4824
+ stop: false,
4825
+ approve: false
4826
+ };
4827
+ var opaqueIds = /* @__PURE__ */ new Map();
4828
+ var opaqueId = (value2, secret) => {
4829
+ const key = `${secret}\0${value2}`;
4830
+ const known = opaqueIds.get(key);
4831
+ if (known) return known;
4832
+ const id = createHmac("sha256", secret).update(value2).digest("hex").slice(0, 32);
4833
+ opaqueIds.set(key, id);
4834
+ return id;
4835
+ };
5068
4836
  var agentThread = (harness, session, secret) => opaqueId(`agent\0${harness}\0${session}`, secret);
5069
4837
  var zedHost = (value2) => typeof value2 === "string" ? value2 : value2 && typeof value2 === "object" ? String(Object.values(value2)[0] ?? "") : "";
5070
4838
  function vscodeHost(authority) {
@@ -5150,13 +4918,11 @@ function contentText(content) {
5150
4918
  return typeof part.text === "string" ? part.text : typeof part.Text === "string" ? part.Text : typeof part.value === "string" ? part.value : contentText(part.content);
5151
4919
  }
5152
4920
  var Discovery = class {
5153
- constructor(deviceId, capabilities, secret) {
4921
+ constructor(deviceId, secret) {
5154
4922
  this.deviceId = deviceId;
5155
- this.capabilities = capabilities;
5156
4923
  this.secret = secret;
5157
4924
  }
5158
4925
  deviceId;
5159
- capabilities;
5160
4926
  secret;
5161
4927
  projects = /* @__PURE__ */ new Map();
5162
4928
  locations = /* @__PURE__ */ new Map();
@@ -5192,7 +4958,7 @@ var Discovery = class {
5192
4958
  editor: "cli",
5193
4959
  status: "unknown",
5194
4960
  updatedAt: session.updatedAt,
5195
- capabilities: { ...this.capabilities },
4961
+ capabilities: { ...READ_ONLY },
5196
4962
  agent: { harness: session.harness, sessionId: session.id }
5197
4963
  };
5198
4964
  project.threads.push(thread);
@@ -5216,7 +4982,7 @@ var Discovery = class {
5216
4982
  const parent = dirname3(project.path);
5217
4983
  counts.set(parent, (counts.get(parent) ?? 0) + 1);
5218
4984
  }
5219
- const folders = [...counts].sort((a, b) => b[1] - a[1]).map(([folder]) => folder).filter((folder) => existsSync4(folder)).slice(0, 3);
4985
+ const folders = [...counts].sort((a, b) => b[1] - a[1]).map(([folder]) => folder).filter((folder) => existsSync3(folder)).slice(0, 3);
5220
4986
  return folders.length ? folders : [homedir3()];
5221
4987
  }
5222
4988
  observe(session) {
@@ -5253,7 +5019,7 @@ var Discovery = class {
5253
5019
  editor: "acp",
5254
5020
  status: "idle",
5255
5021
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
5256
- capabilities: this.capabilities
5022
+ capabilities: READ_ONLY
5257
5023
  };
5258
5024
  project.threads.push(thread);
5259
5025
  this.locations.set(id, { editor: "acp", path: "", session });
@@ -5281,11 +5047,11 @@ var Discovery = class {
5281
5047
  zed(root) {
5282
5048
  const database = join3(root, "db");
5283
5049
  const store = join3(root, "threads/threads.db");
5284
- if (!existsSync4(database)) return;
5050
+ if (!existsSync3(database)) return;
5285
5051
  this.paths.add(database);
5286
5052
  for (const entry of readdirSync2(database)) {
5287
5053
  const path = join3(database, entry, "db.sqlite");
5288
- if (!existsSync4(path)) continue;
5054
+ if (!existsSync3(path)) continue;
5289
5055
  this.paths.add(join3(database, entry));
5290
5056
  try {
5291
5057
  if (!sqlite(
@@ -5294,7 +5060,7 @@ var Discovery = class {
5294
5060
  ).length)
5295
5061
  continue;
5296
5062
  const stamps = new Map(
5297
- existsSync4(store) ? sqlite(store, "SELECT id, updated_at FROM threads").map((row) => [
5063
+ existsSync3(store) ? sqlite(store, "SELECT id, updated_at FROM threads").map((row) => [
5298
5064
  row.id,
5299
5065
  row.updated_at
5300
5066
  ]) : []
@@ -5327,7 +5093,7 @@ var Discovery = class {
5327
5093
  editor: "zed",
5328
5094
  status: "unknown",
5329
5095
  updatedAt,
5330
- capabilities: this.capabilities,
5096
+ capabilities: READ_ONLY,
5331
5097
  ...harness ? { agent: { harness, sessionId: session } } : {}
5332
5098
  };
5333
5099
  project.threads.push(thread);
@@ -5342,17 +5108,17 @@ var Discovery = class {
5342
5108
  }
5343
5109
  }
5344
5110
  const threads = join3(root, "threads");
5345
- if (existsSync4(threads)) this.paths.add(threads);
5111
+ if (existsSync3(threads)) this.paths.add(threads);
5346
5112
  }
5347
5113
  vscode(root) {
5348
5114
  const workspaces = join3(root, "workspaceStorage");
5349
- if (!existsSync4(workspaces)) return;
5115
+ if (!existsSync3(workspaces)) return;
5350
5116
  this.paths.add(workspaces);
5351
5117
  for (const entry of readdirSync2(workspaces)) {
5352
5118
  const directory2 = join3(workspaces, entry);
5353
5119
  const workspace = join3(directory2, "workspace.json");
5354
5120
  const sessions = join3(directory2, "chatSessions");
5355
- if (!existsSync4(workspace)) continue;
5121
+ if (!existsSync3(workspace)) continue;
5356
5122
  this.paths.add(directory2);
5357
5123
  try {
5358
5124
  const data = JSON.parse(readFileSync2(workspace, "utf8"));
@@ -5361,7 +5127,7 @@ var Discovery = class {
5361
5127
  const path = decodeURIComponent(uri.pathname).replace(/^\/([A-Za-z]:\/)/, "$1");
5362
5128
  const host = uri.protocol === "vscode-remote:" ? vscodeHost(uri.host) : "";
5363
5129
  const project = this.project(path, host ? { host, path } : void 0);
5364
- if (!existsSync4(sessions)) continue;
5130
+ if (!existsSync3(sessions)) continue;
5365
5131
  this.paths.add(sessions);
5366
5132
  for (const filename of readdirSync2(sessions).filter((name) => /\.jsonl?$/.test(name))) {
5367
5133
  const file = join3(sessions, filename);
@@ -5373,7 +5139,7 @@ var Discovery = class {
5373
5139
  editor: "vscode",
5374
5140
  status: "unknown",
5375
5141
  updatedAt: new Date(state.lastMessageDate ?? state.creationDate ?? 0).toISOString(),
5376
- capabilities: this.capabilities
5142
+ capabilities: READ_ONLY
5377
5143
  });
5378
5144
  this.locations.set(id, { editor: "vscode", path: file });
5379
5145
  }
@@ -5399,16 +5165,16 @@ var Discovery = class {
5399
5165
  }
5400
5166
  ]).filter((message) => message.text);
5401
5167
  }
5402
- if (!existsSync4(location2.path))
5168
+ if (!existsSync3(location2.path))
5403
5169
  throw new Error(
5404
- "This external agent keeps its conversation in its own storage. Use Screen to view the open thread."
5170
+ "This external agent keeps its conversation in its own storage. View the open thread on your computer."
5405
5171
  );
5406
5172
  const db = new DatabaseSync2(location2.path, { readOnly: true });
5407
5173
  try {
5408
5174
  const row = db.prepare("SELECT data_type, data FROM threads WHERE id = ?").get(location2.session);
5409
5175
  if (!row)
5410
5176
  throw new Error(
5411
- "This agent history is not available on this computer. Open Screen to view the existing session."
5177
+ "This agent history is not available on this computer. View the existing session in Zed."
5412
5178
  );
5413
5179
  const data = row.data_type === "zstd" ? zstdDecompressSync(row.data) : row.data;
5414
5180
  const state = JSON.parse(Buffer.from(data).toString("utf8"));
@@ -10636,20 +10402,20 @@ var deflate$2 = (strm, flush) => {
10636
10402
  let beg = s.pending;
10637
10403
  let left = (s.gzhead.extra.length & 65535) - s.gzindex;
10638
10404
  while (s.pending + left > s.pending_buf_size) {
10639
- let copy = s.pending_buf_size - s.pending;
10640
- s.pending_buf.set(s.gzhead.extra.subarray(s.gzindex, s.gzindex + copy), s.pending);
10405
+ let copy2 = s.pending_buf_size - s.pending;
10406
+ s.pending_buf.set(s.gzhead.extra.subarray(s.gzindex, s.gzindex + copy2), s.pending);
10641
10407
  s.pending = s.pending_buf_size;
10642
10408
  if (s.gzhead.hcrc && s.pending > beg) {
10643
10409
  strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg);
10644
10410
  }
10645
- s.gzindex += copy;
10411
+ s.gzindex += copy2;
10646
10412
  flush_pending(strm);
10647
10413
  if (s.pending !== 0) {
10648
10414
  s.last_flush = -1;
10649
10415
  return Z_OK$3;
10650
10416
  }
10651
10417
  beg = 0;
10652
- left -= copy;
10418
+ left -= copy2;
10653
10419
  }
10654
10420
  let gzhead_extra = new Uint8Array(s.gzhead.extra);
10655
10421
  s.pending_buf.set(gzhead_extra.subarray(s.gzindex, s.gzindex + left), s.pending);
@@ -11959,7 +11725,7 @@ var fixedtables = (state) => {
11959
11725
  state.distcode = distfix;
11960
11726
  state.distbits = 5;
11961
11727
  };
11962
- var updatewindow = (strm, src, end, copy) => {
11728
+ var updatewindow = (strm, src, end, copy2) => {
11963
11729
  let dist;
11964
11730
  const state = strm.state;
11965
11731
  if (state.window === null) {
@@ -11970,20 +11736,20 @@ var updatewindow = (strm, src, end, copy) => {
11970
11736
  state.wnext = 0;
11971
11737
  state.whave = 0;
11972
11738
  }
11973
- if (copy >= state.wsize) {
11739
+ if (copy2 >= state.wsize) {
11974
11740
  state.window.set(src.subarray(end - state.wsize, end), 0);
11975
11741
  state.wnext = 0;
11976
11742
  state.whave = state.wsize;
11977
11743
  } else {
11978
11744
  dist = state.wsize - state.wnext;
11979
- if (dist > copy) {
11980
- dist = copy;
11981
- }
11982
- state.window.set(src.subarray(end - copy, end - copy + dist), state.wnext);
11983
- copy -= dist;
11984
- if (copy) {
11985
- state.window.set(src.subarray(end - copy, end), 0);
11986
- state.wnext = copy;
11745
+ if (dist > copy2) {
11746
+ dist = copy2;
11747
+ }
11748
+ state.window.set(src.subarray(end - copy2, end - copy2 + dist), state.wnext);
11749
+ copy2 -= dist;
11750
+ if (copy2) {
11751
+ state.window.set(src.subarray(end - copy2, end), 0);
11752
+ state.wnext = copy2;
11987
11753
  state.whave = state.wsize;
11988
11754
  } else {
11989
11755
  state.wnext += dist;
@@ -12006,7 +11772,7 @@ var inflate$2 = (strm, flush) => {
12006
11772
  let hold;
12007
11773
  let bits;
12008
11774
  let _in, _out;
12009
- let copy;
11775
+ let copy2;
12010
11776
  let from;
12011
11777
  let from_source;
12012
11778
  let here = 0;
@@ -12205,11 +11971,11 @@ var inflate$2 = (strm, flush) => {
12205
11971
  /* falls through */
12206
11972
  case EXTRA:
12207
11973
  if (state.flags & 1024) {
12208
- copy = state.length;
12209
- if (copy > have) {
12210
- copy = have;
11974
+ copy2 = state.length;
11975
+ if (copy2 > have) {
11976
+ copy2 = have;
12211
11977
  }
12212
- if (copy) {
11978
+ if (copy2) {
12213
11979
  if (state.head) {
12214
11980
  len = state.head.extra_len - state.length;
12215
11981
  if (!state.head.extra) {
@@ -12220,18 +11986,18 @@ var inflate$2 = (strm, flush) => {
12220
11986
  next,
12221
11987
  // extra field is limited to 65536 bytes
12222
11988
  // - no need for additional size check
12223
- next + copy
11989
+ next + copy2
12224
11990
  ),
12225
11991
  /*len + copy > state.head.extra_max - len ? state.head.extra_max : copy,*/
12226
11992
  len
12227
11993
  );
12228
11994
  }
12229
11995
  if (state.flags & 512 && state.wrap & 4) {
12230
- state.check = crc32_1(state.check, input, copy, next);
11996
+ state.check = crc32_1(state.check, input, copy2, next);
12231
11997
  }
12232
- have -= copy;
12233
- next += copy;
12234
- state.length -= copy;
11998
+ have -= copy2;
11999
+ next += copy2;
12000
+ state.length -= copy2;
12235
12001
  }
12236
12002
  if (state.length) {
12237
12003
  break inf_leave;
@@ -12245,18 +12011,18 @@ var inflate$2 = (strm, flush) => {
12245
12011
  if (have === 0) {
12246
12012
  break inf_leave;
12247
12013
  }
12248
- copy = 0;
12014
+ copy2 = 0;
12249
12015
  do {
12250
- len = input[next + copy++];
12016
+ len = input[next + copy2++];
12251
12017
  if (state.head && len && state.length < 65536) {
12252
12018
  state.head.name += String.fromCharCode(len);
12253
12019
  }
12254
- } while (len && copy < have);
12020
+ } while (len && copy2 < have);
12255
12021
  if (state.flags & 512 && state.wrap & 4) {
12256
- state.check = crc32_1(state.check, input, copy, next);
12022
+ state.check = crc32_1(state.check, input, copy2, next);
12257
12023
  }
12258
- have -= copy;
12259
- next += copy;
12024
+ have -= copy2;
12025
+ next += copy2;
12260
12026
  if (len) {
12261
12027
  break inf_leave;
12262
12028
  }
@@ -12271,18 +12037,18 @@ var inflate$2 = (strm, flush) => {
12271
12037
  if (have === 0) {
12272
12038
  break inf_leave;
12273
12039
  }
12274
- copy = 0;
12040
+ copy2 = 0;
12275
12041
  do {
12276
- len = input[next + copy++];
12042
+ len = input[next + copy2++];
12277
12043
  if (state.head && len && state.length < 65536) {
12278
12044
  state.head.comment += String.fromCharCode(len);
12279
12045
  }
12280
- } while (len && copy < have);
12046
+ } while (len && copy2 < have);
12281
12047
  if (state.flags & 512 && state.wrap & 4) {
12282
- state.check = crc32_1(state.check, input, copy, next);
12048
+ state.check = crc32_1(state.check, input, copy2, next);
12283
12049
  }
12284
- have -= copy;
12285
- next += copy;
12050
+ have -= copy2;
12051
+ next += copy2;
12286
12052
  if (len) {
12287
12053
  break inf_leave;
12288
12054
  }
@@ -12417,23 +12183,23 @@ var inflate$2 = (strm, flush) => {
12417
12183
  state.mode = COPY;
12418
12184
  /* falls through */
12419
12185
  case COPY:
12420
- copy = state.length;
12421
- if (copy) {
12422
- if (copy > have) {
12423
- copy = have;
12186
+ copy2 = state.length;
12187
+ if (copy2) {
12188
+ if (copy2 > have) {
12189
+ copy2 = have;
12424
12190
  }
12425
- if (copy > left) {
12426
- copy = left;
12191
+ if (copy2 > left) {
12192
+ copy2 = left;
12427
12193
  }
12428
- if (copy === 0) {
12194
+ if (copy2 === 0) {
12429
12195
  break inf_leave;
12430
12196
  }
12431
- output.set(input.subarray(next, next + copy), put);
12432
- have -= copy;
12433
- next += copy;
12434
- left -= copy;
12435
- put += copy;
12436
- state.length -= copy;
12197
+ output.set(input.subarray(next, next + copy2), put);
12198
+ have -= copy2;
12199
+ next += copy2;
12200
+ left -= copy2;
12201
+ put += copy2;
12202
+ state.length -= copy2;
12437
12203
  break;
12438
12204
  }
12439
12205
  state.mode = TYPE;
@@ -12534,7 +12300,7 @@ var inflate$2 = (strm, flush) => {
12534
12300
  break;
12535
12301
  }
12536
12302
  len = state.lens[state.have - 1];
12537
- copy = 3 + (hold & 3);
12303
+ copy2 = 3 + (hold & 3);
12538
12304
  hold >>>= 2;
12539
12305
  bits -= 2;
12540
12306
  } else if (here_val === 17) {
@@ -12550,7 +12316,7 @@ var inflate$2 = (strm, flush) => {
12550
12316
  hold >>>= here_bits;
12551
12317
  bits -= here_bits;
12552
12318
  len = 0;
12553
- copy = 3 + (hold & 7);
12319
+ copy2 = 3 + (hold & 7);
12554
12320
  hold >>>= 3;
12555
12321
  bits -= 3;
12556
12322
  } else {
@@ -12566,16 +12332,16 @@ var inflate$2 = (strm, flush) => {
12566
12332
  hold >>>= here_bits;
12567
12333
  bits -= here_bits;
12568
12334
  len = 0;
12569
- copy = 11 + (hold & 127);
12335
+ copy2 = 11 + (hold & 127);
12570
12336
  hold >>>= 7;
12571
12337
  bits -= 7;
12572
12338
  }
12573
- if (state.have + copy > state.nlen + state.ndist) {
12339
+ if (state.have + copy2 > state.nlen + state.ndist) {
12574
12340
  strm.msg = "invalid bit length repeat";
12575
12341
  state.mode = BAD;
12576
12342
  break;
12577
12343
  }
12578
- while (copy--) {
12344
+ while (copy2--) {
12579
12345
  state.lens[state.have++] = len;
12580
12346
  }
12581
12347
  }
@@ -12794,39 +12560,39 @@ var inflate$2 = (strm, flush) => {
12794
12560
  if (left === 0) {
12795
12561
  break inf_leave;
12796
12562
  }
12797
- copy = _out - left;
12798
- if (state.offset > copy) {
12799
- copy = state.offset - copy;
12800
- if (copy > state.whave) {
12563
+ copy2 = _out - left;
12564
+ if (state.offset > copy2) {
12565
+ copy2 = state.offset - copy2;
12566
+ if (copy2 > state.whave) {
12801
12567
  if (state.sane) {
12802
12568
  strm.msg = "invalid distance too far back";
12803
12569
  state.mode = BAD;
12804
12570
  break;
12805
12571
  }
12806
12572
  }
12807
- if (copy > state.wnext) {
12808
- copy -= state.wnext;
12809
- from = state.wsize - copy;
12573
+ if (copy2 > state.wnext) {
12574
+ copy2 -= state.wnext;
12575
+ from = state.wsize - copy2;
12810
12576
  } else {
12811
- from = state.wnext - copy;
12577
+ from = state.wnext - copy2;
12812
12578
  }
12813
- if (copy > state.length) {
12814
- copy = state.length;
12579
+ if (copy2 > state.length) {
12580
+ copy2 = state.length;
12815
12581
  }
12816
12582
  from_source = state.window;
12817
12583
  } else {
12818
12584
  from_source = output;
12819
12585
  from = put - state.offset;
12820
- copy = state.length;
12586
+ copy2 = state.length;
12821
12587
  }
12822
- if (copy > left) {
12823
- copy = left;
12588
+ if (copy2 > left) {
12589
+ copy2 = left;
12824
12590
  }
12825
- left -= copy;
12826
- state.length -= copy;
12591
+ left -= copy2;
12592
+ state.length -= copy2;
12827
12593
  do {
12828
12594
  output[put++] = from_source[from++];
12829
- } while (--copy);
12595
+ } while (--copy2);
12830
12596
  if (state.length === 0) {
12831
12597
  state.mode = LEN;
12832
12598
  }
@@ -14717,12 +14483,12 @@ var Model = class _Model extends import_index2.default {
14717
14483
  });
14718
14484
  return value2;
14719
14485
  } catch {
14720
- const copy = { ...value2 };
14721
- Object.defineProperty(copy, SYM_EXPANDED_REF, {
14486
+ const copy2 = { ...value2 };
14487
+ Object.defineProperty(copy2, SYM_EXPANDED_REF, {
14722
14488
  enumerable: true,
14723
14489
  value: true
14724
14490
  });
14725
- return copy;
14491
+ return copy2;
14726
14492
  }
14727
14493
  }
14728
14494
  /**
@@ -16410,6 +16176,23 @@ function sealHistory(key, routing, message, random) {
16410
16176
  // src/history.ts
16411
16177
  var HISTORY_PATH = "/dashup/history";
16412
16178
  var HISTORY_KEPT = 300;
16179
+ var opaque = (key, value2) => createHmac2("sha256", Buffer.from(key, "hex")).update(JSON.stringify(value2)).digest("hex");
16180
+ var historyMessages = (key, project, thread, messages5) => splitHistoryMessages(messages5, (id) => opaque(key, ["history-message", project, thread, id]));
16181
+ var copy = (messages5) => new Map(
16182
+ messages5.slice(-HISTORY_PAGE_ITEMS).map((message) => [message.id, JSON.stringify(message)])
16183
+ );
16184
+ function changes(copy2, messages5) {
16185
+ const latest = messages5.slice(-HISTORY_PAGE_ITEMS);
16186
+ let known = latest.length;
16187
+ while (known > 0 && !copy2.has(latest[known - 1].id)) known--;
16188
+ if (!known && copy2.size) return [];
16189
+ return latest.filter((message, at) => {
16190
+ const json = JSON.stringify(message);
16191
+ if (copy2.get(message.id) === json || at < known && !copy2.has(message.id)) return false;
16192
+ copy2.set(message.id, json);
16193
+ return true;
16194
+ });
16195
+ }
16413
16196
  async function upload(config2, batch) {
16414
16197
  if (batch.records.length > HISTORY_BATCH_ITEMS || Buffer.byteLength(JSON.stringify(batch)) > HISTORY_BATCH_BYTES)
16415
16198
  throw new Error("The encrypted history upload exceeds the batch limit");
@@ -16424,15 +16207,10 @@ async function publishHistory(config2, pair, project, thread, messages5) {
16424
16207
  const local = config2.pairs[pair.id];
16425
16208
  const generation = ++local.sequence;
16426
16209
  saveConfig(config2);
16427
- const opaque = (value2) => createHmac2("sha256", Buffer.from(local.key, "hex")).update(JSON.stringify(value2)).digest("hex");
16428
- const named = (id) => opaque(["history-message", project, thread.id, id]);
16429
- const all = splitHistoryMessages(messages5, named);
16210
+ const all = historyMessages(local.key, project, thread.id, messages5);
16430
16211
  let start = Math.max(0, all.length - HISTORY_KEPT);
16431
16212
  while (start < all.length && (all[start].part ?? 0) > 0) start++;
16432
- const fragments = start ? [
16433
- ...splitHistoryMessages([EARLIER], named),
16434
- ...all.slice(start)
16435
- ] : all;
16213
+ const fragments = start ? [...historyMessages(local.key, project, thread.id, [EARLIER]), ...all.slice(start)] : all;
16436
16214
  const summary = { generation, total: fragments.length, updatedAt: thread.updatedAt };
16437
16215
  const batch = {
16438
16216
  pairing: pair.id,
@@ -16448,7 +16226,7 @@ async function publishHistory(config2, pair, project, thread, messages5) {
16448
16226
  const record = sealHistory(
16449
16227
  local.key,
16450
16228
  {
16451
- id: opaque(["history-record", project, thread.id, generation, position]),
16229
+ id: opaque(local.key, ["history-record", project, thread.id, generation, position]),
16452
16230
  pairing: pair.id,
16453
16231
  sender: pair.device,
16454
16232
  recipient: pair.mobile,
@@ -16477,16 +16255,16 @@ async function publishHistory(config2, pair, project, thread, messages5) {
16477
16255
  }
16478
16256
 
16479
16257
  // src/harness/live.ts
16480
- import { promisify as promisify6 } from "node:util";
16481
- import { execFile as execFile6 } from "node:child_process";
16258
+ import { promisify as promisify5 } from "node:util";
16259
+ import { execFile as execFile5 } from "node:child_process";
16482
16260
 
16483
16261
  // src/harness/codex.ts
16484
16262
  import WebSocket2 from "ws";
16485
16263
  import { homedir as homedir4 } from "node:os";
16486
16264
  import { connect } from "node:net";
16487
- import { promisify as promisify3 } from "node:util";
16265
+ import { promisify as promisify2 } from "node:util";
16488
16266
  import { randomUUID as randomUUID2 } from "node:crypto";
16489
- import { execFile as execFile3 } from "node:child_process";
16267
+ import { execFile as execFile2 } from "node:child_process";
16490
16268
  import { join as join4, isAbsolute as isAbsolute2 } from "node:path";
16491
16269
  import { stat, readdir, readFile } from "node:fs/promises";
16492
16270
  var REQUEST_MS = 4e3;
@@ -16510,7 +16288,7 @@ function approval(id, method, params) {
16510
16288
  const oversized = Buffer.byteLength(detail) > MAX_PREVIEW;
16511
16289
  const unavailable = !command2 || params.kind !== void 0 && params.kind !== "command" || typeof params.command !== "string" || !params.command.trim() || oversized;
16512
16290
  if (oversized) detail = Buffer.from(detail).subarray(0, MAX_PREVIEW).toString("utf8");
16513
- if (unavailable) detail = [detail, SCREEN_PREVIEW].filter(Boolean).join("\n\n");
16291
+ if (unavailable) detail = [detail, NO_PREVIEW].filter(Boolean).join("\n\n");
16514
16292
  return {
16515
16293
  id,
16516
16294
  title: command2 ? "Allow command?" : "Allow file changes?",
@@ -16806,7 +16584,7 @@ var Codex = class {
16806
16584
  // Codex's own daemon runs the threads Dashup starts or resumes, so they outlive this connector.
16807
16585
  async start(cwd, text3, saved) {
16808
16586
  const path = process.env.DASHUP_CODEX_SOCKET || join4(codexHome(), ...SOCKET_PARTS);
16809
- await promisify3(execFile3)("codex", ["app-server", "daemon", "start"], {
16587
+ await promisify2(execFile2)("codex", ["app-server", "daemon", "start"], {
16810
16588
  timeout: 6e4,
16811
16589
  shell: process.platform === "win32"
16812
16590
  });
@@ -16849,8 +16627,8 @@ var Codex = class {
16849
16627
  // src/harness/headless.ts
16850
16628
  import { randomUUID as randomUUID3 } from "node:crypto";
16851
16629
  import { readFileSync as readFileSync3 } from "node:fs";
16852
- import { createInterface as createInterface3 } from "node:readline";
16853
- import { spawn as spawn3 } from "node:child_process";
16630
+ import { createInterface as createInterface2 } from "node:readline";
16631
+ import { spawn as spawn2 } from "node:child_process";
16854
16632
  var CHOICES = [
16855
16633
  { id: "accept", label: "Allow once" },
16856
16634
  { id: "decline", label: "Reject" }
@@ -16973,7 +16751,7 @@ var Session = class {
16973
16751
  this.changed();
16974
16752
  return Promise.resolve();
16975
16753
  }
16976
- const turn = this.turn = spawn3(
16754
+ const turn = this.turn = spawn2(
16977
16755
  "claude",
16978
16756
  [
16979
16757
  "-p",
@@ -16996,7 +16774,7 @@ var Session = class {
16996
16774
  errors = (errors + String(chunk)).slice(-2e3);
16997
16775
  });
16998
16776
  const started = new Promise((resolve3, reject) => {
16999
- createInterface3({ input: turn.stdout }).on("line", (line) => {
16777
+ createInterface2({ input: turn.stdout }).on("line", (line) => {
17000
16778
  if (this.receive(line)) resolve3();
17001
16779
  });
17002
16780
  const end = (reason) => {
@@ -17028,7 +16806,7 @@ var Session = class {
17028
16806
  else if (message.type === "assistant") {
17029
16807
  const text3 = (Array.isArray(message.message?.content) ? message.message.content : []).filter((part) => part?.type === "text" && typeof part.text === "string").map((part) => part.text).join("\n\n");
17030
16808
  if (!text3) return false;
17031
- this.messages.push({ id: String(message.message?.id ?? randomUUID3()), role: "assistant", text: text3 });
16809
+ this.messages.push({ id: String(message.uuid ?? randomUUID3()), role: "assistant", text: text3 });
17032
16810
  } else if (message.type === "control_request" && message.request?.subtype === "can_use_tool") {
17033
16811
  const id = randomUUID3();
17034
16812
  this.approvals.set(id, {
@@ -17250,9 +17028,9 @@ var Claude = class {
17250
17028
  import Socket3 from "ws";
17251
17029
  import { homedir as homedir5 } from "node:os";
17252
17030
  import { connect as connect3 } from "node:net";
17253
- import { promisify as promisify4 } from "node:util";
17254
- import { fileURLToPath as fileURLToPath2 } from "node:url";
17255
- import { execFile as execFile4 } from "node:child_process";
17031
+ import { promisify as promisify3 } from "node:util";
17032
+ import { fileURLToPath } from "node:url";
17033
+ import { execFile as execFile3 } from "node:child_process";
17256
17034
  import { createHash as createHash3, randomUUID as randomUUID4 } from "node:crypto";
17257
17035
  import { stat as stat2, readdir as readdir3, readFile as readFile3 } from "node:fs/promises";
17258
17036
  import { join as join6, dirname as dirname4, basename as basename2, isAbsolute as isAbsolute3 } from "node:path";
@@ -17265,7 +17043,7 @@ var MAX_METADATA = 64 * 1024;
17265
17043
  var MAX_HOSTS = 16;
17266
17044
  var MAX_SESSIONS3 = 100;
17267
17045
  var ROOT_CHANNEL = "ahp-root://";
17268
- var run2 = promisify4(execFile4);
17046
+ var run = promisify3(execFile3);
17269
17047
  var CONFIRMATION_CHOICES = [
17270
17048
  { id: "approve", label: "Allow once", kind: "approve" },
17271
17049
  { id: "deny", label: "Reject", kind: "deny" }
@@ -17285,7 +17063,7 @@ async function privateFile(path, limit) {
17285
17063
  const encodedPath = Buffer.from(path).toString("base64");
17286
17064
  const script = `$p=[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${encodedPath}')); $a=Get-Acl -LiteralPath $p -ErrorAction Stop; $sid=[Security.Principal.SecurityIdentifier]; $me=[Security.Principal.WindowsIdentity]::GetCurrent().User.Value; $ok=$a.GetOwner($sid).Value -eq $me; foreach($r in $a.Access){if($r.AccessControlType -eq 'Allow' -and $r.IdentityReference.Translate($sid).Value -notin @($me,'S-1-5-18','S-1-5-32-544')){$ok=$false}}; $ok`;
17287
17065
  try {
17288
- const { stdout } = await run2(
17066
+ const { stdout } = await run(
17289
17067
  "powershell.exe",
17290
17068
  [
17291
17069
  "-NoProfile",
@@ -17354,7 +17132,7 @@ function directory(state) {
17354
17132
  const uri = state.workingDirectories?.[0];
17355
17133
  if (typeof uri !== "string" || !uri.startsWith("file:")) return;
17356
17134
  try {
17357
- return fileURLToPath2(uri);
17135
+ return fileURLToPath(uri);
17358
17136
  } catch {
17359
17137
  return;
17360
17138
  }
@@ -17375,7 +17153,7 @@ function confirmation(toolCall) {
17375
17153
  const paths = [before, after].filter((uri) => typeof uri === "string");
17376
17154
  const names = [...new Set(paths)].map((uri) => {
17377
17155
  try {
17378
- return fileURLToPath2(uri);
17156
+ return fileURLToPath(uri);
17379
17157
  } catch {
17380
17158
  return uri;
17381
17159
  }
@@ -17402,8 +17180,7 @@ function confirmation(toolCall) {
17402
17180
  })) : [];
17403
17181
  if (unavailable) choices = choices.filter((choice) => choice.kind === "deny");
17404
17182
  if (oversized) detail = Buffer.from(detail).subarray(0, MAX_PREVIEW).toString("utf8");
17405
- if (unavailable || !choices.length)
17406
- detail = [detail, SCREEN_PREVIEW].filter(Boolean).join("\n\n");
17183
+ if (unavailable || !choices.length) detail = [detail, NO_PREVIEW].filter(Boolean).join("\n\n");
17407
17184
  return { detail, choices };
17408
17185
  }
17409
17186
  function messages3(chat) {
@@ -17747,11 +17524,11 @@ var VSCode = class {
17747
17524
 
17748
17525
  // src/harness/opencode.ts
17749
17526
  import { join as join7 } from "node:path";
17750
- import { promisify as promisify5 } from "node:util";
17527
+ import { promisify as promisify4 } from "node:util";
17751
17528
  import { createHash as createHash4 } from "node:crypto";
17752
- import { execFile as execFile5, spawn as spawn4 } from "node:child_process";
17529
+ import { execFile as execFile4, spawn as spawn3 } from "node:child_process";
17753
17530
  import { stat as stat3, readlink, readdir as readdir4, readFile as readFile4 } from "node:fs/promises";
17754
- var run3 = promisify5(execFile5);
17531
+ var run2 = promisify4(execFile4);
17755
17532
  var REQUEST_MS4 = 4e3;
17756
17533
  var MAX_BYTES3 = 8 * 1024 * 1024;
17757
17534
  var MAX_SESSIONS4 = 100;
@@ -17831,7 +17608,7 @@ async function endpoints() {
17831
17608
  if (process.env.DASHUP_LIVE_DISCOVERY === "0") return [];
17832
17609
  if (process.platform === "linux") return linuxEndpoints();
17833
17610
  if (process.platform === "darwin") {
17834
- const { stdout } = await run3(
17611
+ const { stdout } = await run2(
17835
17612
  "lsof",
17836
17613
  ["-nP", "-a", "-c", "opencode", "-iTCP", "-sTCP:LISTEN", "-Fn"],
17837
17614
  { timeout: REQUEST_MS4, maxBuffer: MAX_BYTES3 }
@@ -17848,7 +17625,7 @@ async function endpoints() {
17848
17625
  }
17849
17626
  if (process.platform === "win32") {
17850
17627
  const script = "$ids = @(Get-Process opencode -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Id); Get-NetTCPConnection -State Listen -ErrorAction SilentlyContinue | Where-Object { $ids -contains $_.OwningProcess } | Select-Object -ExpandProperty LocalPort";
17851
- const { stdout } = await run3(
17628
+ const { stdout } = await run2(
17852
17629
  "powershell.exe",
17853
17630
  ["-NoProfile", "-NonInteractive", "-Command", script],
17854
17631
  { timeout: REQUEST_MS4, maxBuffer: MAX_BYTES3 }
@@ -17929,7 +17706,7 @@ ${always.join("\n")}` : ""
17929
17706
  }
17930
17707
  if (unavailable) detail += `
17931
17708
 
17932
- ${SCREEN_PREVIEW}`;
17709
+ ${NO_PREVIEW}`;
17933
17710
  return {
17934
17711
  id: permission.id,
17935
17712
  title: `Allow ${String(permission.permission ?? "tool")}?`,
@@ -18130,7 +17907,7 @@ var Server = class {
18130
17907
  if (!permissionApproval(permission, history2, cwd).choices.some(
18131
17908
  (value2) => value2.id === choice
18132
17909
  ))
18133
- throw new Error(SCREEN_PREVIEW);
17910
+ throw new Error(NO_PREVIEW);
18134
17911
  }
18135
17912
  await this.request(`/permission/${encodeURIComponent(approvalId)}/reply`, cwd, {
18136
17913
  reply: choice
@@ -18217,7 +17994,7 @@ var Server = class {
18217
17994
  }
18218
17995
  };
18219
17996
  function serve(cwd) {
18220
- const child = spawn4("opencode", ["serve", "--hostname", "127.0.0.1", "--port", "0"], {
17997
+ const child = spawn3("opencode", ["serve", "--hostname", "127.0.0.1", "--port", "0"], {
18221
17998
  cwd,
18222
17999
  stdio: ["ignore", "pipe", "ignore"],
18223
18000
  shell: process.platform === "win32"
@@ -18342,7 +18119,7 @@ var LiveHarnesses = class {
18342
18119
  async agents() {
18343
18120
  const harnesses = ["claude", "codex", "opencode"];
18344
18121
  const runs = await Promise.all(
18345
- harnesses.map((harness) => promisify6(execFile6)(harness, ["--version"], {
18122
+ harnesses.map((harness) => promisify5(execFile5)(harness, ["--version"], {
18346
18123
  timeout: 1e4,
18347
18124
  shell: process.platform === "win32"
18348
18125
  }).then(
@@ -18395,10 +18172,10 @@ var LiveHarnesses = class {
18395
18172
 
18396
18173
  // src/harness/queue.ts
18397
18174
  import { randomUUID as randomUUID5 } from "node:crypto";
18398
- import { spawn as spawn5 } from "node:child_process";
18175
+ import { spawn as spawn4 } from "node:child_process";
18399
18176
  import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
18400
18177
  import { join as join8, dirname as dirname5, basename as basename3 } from "node:path";
18401
- import { statSync as statSync2, existsSync as existsSync5, readdirSync as readdirSync3, readFileSync as readFileSync4, readlinkSync, realpathSync as realpathSync2 } from "node:fs";
18178
+ import { statSync as statSync2, existsSync as existsSync4, readdirSync as readdirSync3, readFileSync as readFileSync4, readlinkSync, realpathSync as realpathSync2 } from "node:fs";
18402
18179
  var CODEX_VERSION = "0.160.1";
18403
18180
  var REQUEST_MS5 = 1e4;
18404
18181
  var MAX_BYTES4 = 1024 * 1024;
@@ -18419,7 +18196,7 @@ function packageVersion(executable) {
18419
18196
  let directory2 = dirname5(executable);
18420
18197
  for (let depth = 0; depth < 8; depth++, directory2 = dirname5(directory2)) {
18421
18198
  const path = join8(directory2, "package.json");
18422
- if (!existsSync5(path)) continue;
18199
+ if (!existsSync4(path)) continue;
18423
18200
  const value2 = JSON.parse(readFileSync4(path, "utf8"));
18424
18201
  if (value2.name?.startsWith("@openai/codex-")) return value2.version;
18425
18202
  }
@@ -18520,7 +18297,7 @@ function validateEvidence(owner) {
18520
18297
  if (realpathSync2.native(owner.home) !== owner.home || process.getuid && statSync2(owner.home).uid !== process.getuid() || !sameFile(identity(owner.queue.path), owner.queue) || !sameFile(identity(owner.rollout.path), owner.rollout))
18521
18298
  throw new Error("The original native history storage changed");
18522
18299
  const path = join8(owner.home, "thread_history_1.sqlite");
18523
- if (existsSync5(path) && identity(path).path !== path)
18300
+ if (existsSync4(path) && identity(path).path !== path)
18524
18301
  throw new Error("Native history is outside the original home");
18525
18302
  }
18526
18303
  function nativeReceipt(owner, clientId) {
@@ -18537,7 +18314,7 @@ function nativeReceipt(owner, clientId) {
18537
18314
  queue.close();
18538
18315
  }
18539
18316
  const path = join8(owner.home, "thread_history_1.sqlite");
18540
- if (!existsSync5(path)) return { queueId };
18317
+ if (!existsSync4(path)) return { queueId };
18541
18318
  const history = new DatabaseSync3(path, { readOnly: true });
18542
18319
  try {
18543
18320
  const rows = history.prepare(
@@ -18592,7 +18369,7 @@ var Writer = class {
18592
18369
  tail = "";
18593
18370
  pending = /* @__PURE__ */ new Map();
18594
18371
  constructor(owner) {
18595
- this.process = spawn5(owner.executable, ["app-server", "--listen", "stdio://"], {
18372
+ this.process = spawn4(owner.executable, ["app-server", "--listen", "stdio://"], {
18596
18373
  cwd: owner.cwd,
18597
18374
  env: { ...process.env, CODEX_HOME: owner.home },
18598
18375
  stdio: ["pipe", "pipe", "ignore"]
@@ -18751,6 +18528,8 @@ var NativeQueue = class {
18751
18528
  // src/connector.ts
18752
18529
  var EPHEMERAL_TTL_MS = 6e4;
18753
18530
  var HOME_RECENT = 20;
18531
+ var FOLLOW_MS = 5 * 6e4;
18532
+ var RESCAN_MS = 1e3;
18754
18533
  var Command = z2.object({
18755
18534
  type: z2.literal("command"),
18756
18535
  requestId: z2.string().min(1),
@@ -18763,8 +18542,6 @@ var Command = z2.object({
18763
18542
  z2.object({ kind: z2.literal("thread.queue"), text: z2.string().min(1).max(1e5) }).strict(),
18764
18543
  z2.object({ kind: z2.literal("thread.stop") }).strict(),
18765
18544
  z2.object({ kind: z2.literal("approval.respond"), approvalId: z2.string(), choice: z2.string() }).strict(),
18766
- z2.object({ kind: z2.literal("screen.start"), sessionId: z2.string().min(1).max(128) }).strict(),
18767
- z2.object({ kind: z2.literal("screen.stop"), sessionId: z2.string().min(1).max(128) }).strict(),
18768
18545
  z2.object({
18769
18546
  kind: z2.literal("session.start"),
18770
18547
  harness: z2.enum(["claude", "codex", "opencode"]),
@@ -18790,31 +18567,13 @@ var Connector = class {
18790
18567
  autoConnect: false
18791
18568
  });
18792
18569
  provideClient(() => this.client);
18793
- this.viewer = new Viewer(
18794
- config2,
18795
- (pair) => this.isActive(pair.id),
18796
- (pair, content) => this.send(pair, content)
18797
- );
18798
- this.discovery = new Discovery(
18799
- config2.device.id,
18800
- {
18801
- read: true,
18802
- send: false,
18803
- queue: false,
18804
- stop: false,
18805
- approve: false,
18806
- screen: config2.screen && Viewer.available(),
18807
- control: config2.control && controlAvailable()
18808
- },
18809
- config2.localSecret
18810
- );
18570
+ this.discovery = new Discovery(config2.device.id, config2.localSecret);
18811
18571
  this.pairings = createLiveQuery(() => this.client.bind(Pairing).where({ device: config2.device.id }).whereNull("revokedAt").clearLimit());
18812
18572
  this.relays = createLiveQuery(() => this.client.bind(Relay).where({ recipient: config2.device.id }).orderBy("createdAt", "desc").orderBy("id", "desc").clearLimit());
18813
18573
  }
18814
18574
  config;
18815
18575
  client;
18816
18576
  discovery;
18817
- viewer;
18818
18577
  pairings;
18819
18578
  relays;
18820
18579
  nativeQueue;
@@ -18825,8 +18584,17 @@ var Connector = class {
18825
18584
  retry;
18826
18585
  stopWatching = () => {
18827
18586
  };
18828
- rescan = () => void this.queueChange().catch(() => {
18829
- });
18587
+ rescanning;
18588
+ stopped = false;
18589
+ rescan = () => {
18590
+ if (this.rescanning) return;
18591
+ this.rescanning = setTimeout(() => {
18592
+ this.rescanning = void 0;
18593
+ void this.queueChange().catch(() => {
18594
+ });
18595
+ }, RESCAN_MS);
18596
+ this.rescanning.unref();
18597
+ };
18830
18598
  releases = [];
18831
18599
  announced = /* @__PURE__ */ new Set();
18832
18600
  listed = /* @__PURE__ */ new Map();
@@ -18838,6 +18606,8 @@ var Connector = class {
18838
18606
  snapshots = /* @__PURE__ */ new Set();
18839
18607
  nativeSnapshots = /* @__PURE__ */ new Map();
18840
18608
  queueReceipts = /* @__PURE__ */ new Map();
18609
+ // The conversations phones are reading, by pairing and thread.
18610
+ follows = /* @__PURE__ */ new Map();
18841
18611
  // The content key behind each pairing code on show, until a phone pairs with it.
18842
18612
  invitations = /* @__PURE__ */ new Map();
18843
18613
  async start() {
@@ -18854,8 +18624,6 @@ var Connector = class {
18854
18624
  this.releases.push(this.harnesses.watch(this.rescan));
18855
18625
  this.releases.push(
18856
18626
  this.pairings.retain(() => {
18857
- if (this.pairings.snapshot().status !== "ready") this.viewer.close();
18858
- else this.viewer.revoke(new Set(this.activePairs().map((pair) => pair.id)));
18859
18627
  this.work = this.work.then(async () => {
18860
18628
  if (this.pairings.snapshot().status !== "ready") return;
18861
18629
  for (const pair of this.activePairs()) {
@@ -18935,7 +18703,6 @@ var Connector = class {
18935
18703
  this.client.send("dashup:presence", { device: this.config.device.id });
18936
18704
  });
18937
18705
  this.client.on("error", () => this.report(new Error("Connection interrupted. Dashup will reconnect.")));
18938
- this.client.on("disconnected", () => this.viewer.close());
18939
18706
  await this.client.reconnect();
18940
18707
  process.stdout.write(
18941
18708
  `Dashup is connected. ${this.discovery.projects.size} existing projects found.
@@ -18949,29 +18716,16 @@ var Connector = class {
18949
18716
  return invite;
18950
18717
  }
18951
18718
  stop() {
18719
+ this.stopped = true;
18952
18720
  this.stopWatching();
18953
18721
  for (const release of this.releases.splice(0)) release();
18954
18722
  clearTimeout(this.retry);
18723
+ clearTimeout(this.rescanning);
18955
18724
  this.client.dispose();
18956
- this.viewer.close();
18957
18725
  this.nativeQueue.stop();
18958
18726
  this.harnesses.stop();
18959
18727
  for (const agent of this.agents.values()) agent.socket.destroy();
18960
18728
  }
18961
- async permissions(screen, control) {
18962
- this.config.screen = screen;
18963
- this.config.control = screen && control;
18964
- this.discovery.capabilities = {
18965
- ...this.discovery.capabilities,
18966
- screen: screen && Viewer.available(),
18967
- control: this.config.control && controlAvailable()
18968
- };
18969
- const sessions = screen ? [] : [...this.viewer.sessions];
18970
- if (!screen) this.viewer.close();
18971
- saveConfig(this.config);
18972
- for (const [id, current] of sessions) await this.viewer.sendClose(id, current);
18973
- await this.queueChange();
18974
- }
18975
18729
  activePairs() {
18976
18730
  return this.pairings.snapshot().items;
18977
18731
  }
@@ -19000,7 +18754,7 @@ var Connector = class {
19000
18754
  );
19001
18755
  if (envelope.ciphertext.length > MAX_ENVELOPE_BYTES * 2)
19002
18756
  throw new Error(
19003
- "This update exceeds the encrypted message limit. Open Screen to view the full conversation."
18757
+ "This update exceeds the encrypted message limit. View the full conversation on your computer."
19004
18758
  );
19005
18759
  this.config.outbox.push(envelope);
19006
18760
  saveConfig(this.config);
@@ -19013,7 +18767,7 @@ var Connector = class {
19013
18767
  saveConfig(this.config);
19014
18768
  }
19015
18769
  flush() {
19016
- const run5 = this.drain.catch(() => {
18770
+ const run4 = this.drain.catch(() => {
19017
18771
  }).then(async () => {
19018
18772
  this.discardLegacySnapshots();
19019
18773
  while (this.config.outbox.length && this.client.isConnected) {
@@ -19023,8 +18777,8 @@ var Connector = class {
19023
18777
  saveConfig(this.config);
19024
18778
  }
19025
18779
  });
19026
- this.drain = run5;
19027
- return run5.catch((error) => {
18780
+ this.drain = run4;
18781
+ return run4.catch((error) => {
19028
18782
  this.report(error);
19029
18783
  clearTimeout(this.retry);
19030
18784
  this.retry = setTimeout(() => {
@@ -19065,7 +18819,6 @@ var Connector = class {
19065
18819
  return {
19066
18820
  type: "projects",
19067
18821
  version,
19068
- screen: this.discovery.capabilities.screen,
19069
18822
  agents: this.startable,
19070
18823
  folders: this.discovery.folders(),
19071
18824
  projects: [...this.listedProjects.values()],
@@ -19099,15 +18852,16 @@ var Connector = class {
19099
18852
  // transcript costs one rescan rather than one each.
19100
18853
  queueChange() {
19101
18854
  if (this.pendingChange) return this.pendingChange;
19102
- const run5 = this.work.then(() => {
18855
+ const run4 = this.work.then(() => {
19103
18856
  this.pendingChange = void 0;
19104
18857
  return this.changed();
19105
18858
  });
19106
- this.pendingChange = run5;
19107
- this.work = run5.catch((error) => this.report(error));
19108
- return run5;
18859
+ this.pendingChange = run4;
18860
+ this.work = run4.catch((error) => this.report(error));
18861
+ return run4;
19109
18862
  }
19110
18863
  async changed() {
18864
+ if (this.stopped) return;
19111
18865
  const previous = new Map(
19112
18866
  [...this.discovery.projects.values()].map((project) => [project.id, project])
19113
18867
  );
@@ -19142,9 +18896,19 @@ var Connector = class {
19142
18896
  const projects = [...this.discovery.projects.values()];
19143
18897
  this.stopWatching();
19144
18898
  this.stopWatching = this.discovery.watch(this.rescan);
18899
+ await this.stream();
19145
18900
  for (const project of projects) {
19146
- if (JSON.stringify(previous.get(project.id)) === JSON.stringify(project)) continue;
19147
- for (const pair of this.activePairs()) await this.send(pair, { type: "project", project });
18901
+ const before = previous.get(project.id);
18902
+ const sent = new Map(before?.threads.map((thread) => [thread.id, JSON.stringify(thread)]));
18903
+ const changed = project.threads.filter(
18904
+ (thread) => sent.get(thread.id) !== JSON.stringify(thread)
18905
+ );
18906
+ const whole = !before || project.threads.filter((thread) => sent.has(thread.id)).length < sent.size || JSON.stringify({ ...before, threads: [] }) !== JSON.stringify({ ...project, threads: [] });
18907
+ for (const pair of this.activePairs())
18908
+ if (whole) await this.send(pair, { type: "project", project });
18909
+ else
18910
+ for (const thread of changed)
18911
+ if (!current.has(thread.id)) await this.thread(pair, project, thread);
19148
18912
  }
19149
18913
  for (const { project, thread, session } of live) {
19150
18914
  const snapshot = this.threadState(project, thread);
@@ -19167,6 +18931,40 @@ var Connector = class {
19167
18931
  for (const pair of this.activePairs()) await this.listProjects(pair);
19168
18932
  await this.publishQueueReceipts();
19169
18933
  }
18934
+ // A phone reading a conversation gets its messages as they change, before anything that says the
18935
+ // conversation changed, so it never asks for a copy it already has.
18936
+ async stream() {
18937
+ for (const [key, follow] of this.follows) {
18938
+ const thread = this.discovery.projects.get(follow.project)?.threads.find((item) => item.id === follow.thread);
18939
+ let messages5;
18940
+ try {
18941
+ if (thread && follow.until > Date.now() && activePair(this.config, follow.pair))
18942
+ messages5 = changes(
18943
+ follow.copy,
18944
+ this.discovery.read(thread, this.attached(thread)?.messages)
18945
+ );
18946
+ } catch {
18947
+ }
18948
+ if (!thread || !messages5) {
18949
+ this.follows.delete(key);
18950
+ continue;
18951
+ }
18952
+ if (!messages5.length && thread.updatedAt === follow.updatedAt) continue;
18953
+ follow.updatedAt = thread.updatedAt;
18954
+ await this.send(follow.pair, {
18955
+ type: "thread-tail",
18956
+ projectId: follow.project,
18957
+ threadId: follow.thread,
18958
+ updatedAt: follow.updatedAt,
18959
+ messages: historyMessages(
18960
+ this.config.pairs[follow.pair.id].key,
18961
+ follow.project,
18962
+ follow.thread,
18963
+ messages5
18964
+ )
18965
+ });
18966
+ }
18967
+ }
19170
18968
  // A saved session nothing else has open continues headless when its agent runs here. One a
19171
18969
  // native process owns takes queued messages instead.
19172
18970
  refreshResumes() {
@@ -19218,38 +19016,23 @@ var Connector = class {
19218
19016
  }
19219
19017
  async receive(envelope) {
19220
19018
  const pair = this.activePairs().find((item) => item.id === envelope.pairing);
19221
- if (!pair || !activePair(this.config, pair) || envelope.sender !== pair.mobile || envelope.recipient !== pair.device || !["command", "signal"].includes(envelope.channel))
19019
+ if (!pair || !activePair(this.config, pair) || envelope.sender !== pair.mobile || envelope.recipient !== pair.device || envelope.channel !== "command")
19222
19020
  return;
19223
19021
  const key = this.config.pairs[pair.id];
19224
19022
  if (envelope.sequence <= key.received) return;
19225
19023
  const content = open(key.key, envelope);
19226
- if (content.type !== "command" && content.type !== "signal" || content.projectId !== envelope.project)
19024
+ if (content.type !== "command" || content.projectId !== envelope.project || content.requestId !== envelope.id)
19227
19025
  return;
19228
- if (content.type === "command" && content.requestId !== envelope.id) return;
19229
- if (content.type === "command") Command.parse(content);
19026
+ Command.parse(content);
19230
19027
  key.received = envelope.sequence;
19231
- if (content.type === "command")
19232
- this.config.unconfirmed[content.requestId] = {
19233
- pairing: pair.id,
19234
- project: content.projectId
19235
- };
19028
+ this.config.unconfirmed[content.requestId] = { pairing: pair.id, project: content.projectId };
19236
19029
  saveConfig(this.config);
19237
- if (content.type === "signal") {
19238
- this.viewer.signal(pair, content.projectId, content.sessionId, content.signal);
19239
- return;
19240
- }
19241
- if (content.action.kind === "screen.start") {
19242
- void this.execute(pair, content, envelope).catch((error) => this.report(error));
19243
- return;
19244
- }
19245
19030
  await this.execute(pair, content, envelope);
19246
19031
  }
19247
19032
  async execute(pair, content, envelope) {
19248
19033
  let queue = void 0;
19249
19034
  try {
19250
- if (["screen.start", "approval.respond", "thread.stop", "session.start"].includes(
19251
- content.action.kind
19252
- ) && Math.abs(Date.now() - Date.parse(envelope.sentAt)) > EPHEMERAL_TTL_MS)
19035
+ if (["approval.respond", "thread.stop", "session.start"].includes(content.action.kind) && Math.abs(Date.now() - Date.parse(envelope.sentAt)) > EPHEMERAL_TTL_MS)
19253
19036
  throw new Error("Command expired. Try again while this computer is online.");
19254
19037
  const outcome = await this.command(pair, content);
19255
19038
  delete this.config.unconfirmed[content.requestId];
@@ -19402,12 +19185,20 @@ var Connector = class {
19402
19185
  if (!thread) throw new Error("Thread not found");
19403
19186
  const messages5 = this.discovery.read(thread, this.attached(thread)?.messages);
19404
19187
  const history = await publishHistory(this.config, pair, project.id, thread, messages5);
19188
+ this.follows.set(`${pair.id}:${thread.id}`, {
19189
+ pair,
19190
+ project: project.id,
19191
+ thread: thread.id,
19192
+ until: Date.now() + FOLLOW_MS,
19193
+ copy: copy(messages5),
19194
+ updatedAt: thread.updatedAt
19195
+ });
19405
19196
  return this.thread(pair, project, thread, history);
19406
19197
  }
19407
19198
  case "thread.send": {
19408
19199
  if (!thread || !thread.capabilities.send)
19409
19200
  throw new Error(
19410
- "This session has no live reply connection. Open Screen to use the existing agent."
19201
+ "This session has no live reply connection. Use the existing agent on your computer."
19411
19202
  );
19412
19203
  const location2 = this.discovery.locations.get(thread.id);
19413
19204
  if (location2?.live?.send) return location2.live.send(command2.requestId, command2.action.text);
@@ -19428,7 +19219,7 @@ var Connector = class {
19428
19219
  const agent = thread && this.attached(thread);
19429
19220
  if (!agent || agent.socket.destroyed)
19430
19221
  throw new Error(
19431
- "This session has no live connection that can stop it. Open Screen to stop the agent."
19222
+ "This session has no live connection that can stop it. Stop the agent on your computer."
19432
19223
  );
19433
19224
  return commandAcp(agent, command2.requestId, { kind: "cancel" });
19434
19225
  }
@@ -19462,7 +19253,8 @@ var Connector = class {
19462
19253
  return native.approve(command2.requestId, approval4.id, action.choice);
19463
19254
  }
19464
19255
  const agent = this.attached(thread);
19465
- if (!agent) throw new Error("Approval requires an attached ACP session. Use Screen.");
19256
+ if (!agent)
19257
+ throw new Error("Approval requires an attached ACP session. Answer it on your computer.");
19466
19258
  const [request2, approval3] = [...agent.approvals].find(([, item]) => item.id === action.approvalId) ?? [];
19467
19259
  if (!approval3?.choices.some((choice) => choice.id === action.choice))
19468
19260
  throw new Error("Approval is no longer available");
@@ -19472,12 +19264,6 @@ var Connector = class {
19472
19264
  choice: action.choice
19473
19265
  });
19474
19266
  }
19475
- case "screen.start":
19476
- if (!this.config.screen) throw new Error("Enable screen access on this computer first");
19477
- return this.viewer.start(pair, project.id, command2.action.sessionId);
19478
- case "screen.stop":
19479
- this.viewer.stop(pair, command2.action.sessionId);
19480
- return;
19481
19267
  }
19482
19268
  }
19483
19269
  // Starts an agent in a project, or in a new folder inside one this computer offers. Commands run
@@ -19518,24 +19304,24 @@ var Connector = class {
19518
19304
  };
19519
19305
 
19520
19306
  // src/install.ts
19521
- import { existsSync as existsSync6 } from "node:fs";
19307
+ import { existsSync as existsSync5 } from "node:fs";
19522
19308
  import { homedir as homedir6, userInfo } from "node:os";
19523
- import { promisify as promisify7 } from "node:util";
19524
- import { fileURLToPath as fileURLToPath3 } from "node:url";
19525
- import { execFile as execFile7, spawn as spawn6 } from "node:child_process";
19309
+ import { promisify as promisify6 } from "node:util";
19310
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
19311
+ import { execFile as execFile6, spawn as spawn5 } from "node:child_process";
19526
19312
  import { cp, mkdir as mkdir2, rm, writeFile } from "node:fs/promises";
19527
19313
  import { dirname as dirname6, join as join10, relative, sep } from "node:path";
19528
- var run4 = promisify7(execFile7);
19314
+ var run3 = promisify6(execFile6);
19529
19315
  var xml = (value2) => value2.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
19530
19316
  var NODE_FLAGS = ["--disable-warning=ExperimentalWarning"];
19531
- var systemctl = (...args2) => run4("systemctl", ["--user", ...args2], {
19317
+ var systemctl = (...args2) => run3("systemctl", ["--user", ...args2], {
19532
19318
  env: { ...process.env, XDG_RUNTIME_DIR: process.env.XDG_RUNTIME_DIR ?? `/run/user/${userInfo().uid}` }
19533
19319
  });
19534
19320
  var serviceFile = () => process.platform === "darwin" ? join10(homedir6(), "Library/LaunchAgents/com.dashup.connector.plist") : process.platform === "win32" ? join10(
19535
19321
  process.env.APPDATA ?? join10(homedir6(), "AppData/Roaming"),
19536
19322
  "Microsoft/Windows/Start Menu/Programs/Startup/Dashup.vbs"
19537
19323
  ) : join10(homedir6(), ".config/systemd/user/dashup.service");
19538
- var startupInstalled = () => existsSync6(serviceFile());
19324
+ var startupInstalled = () => existsSync5(serviceFile());
19539
19325
  function service(platform, node, entry, config2) {
19540
19326
  const command2 = [node, ...NODE_FLAGS, entry];
19541
19327
  if (platform === "darwin")
@@ -19563,7 +19349,7 @@ WantedBy=default.target
19563
19349
  `;
19564
19350
  }
19565
19351
  async function durableEntry() {
19566
- const entry = fileURLToPath3(import.meta.url);
19352
+ const entry = fileURLToPath2(import.meta.url);
19567
19353
  if (!entry.endsWith(`${sep}dist${sep}index.js`))
19568
19354
  throw new Error(
19569
19355
  "Build the connector before installing startup: pnpm --filter @dashup/connector build"
@@ -19586,13 +19372,13 @@ async function installStartup() {
19586
19372
  service(process.platform, process.execPath, await durableEntry(), configPath())
19587
19373
  );
19588
19374
  if (process.platform === "darwin") {
19589
- await run4("launchctl", ["unload", path]).catch(() => {
19375
+ await run3("launchctl", ["unload", path]).catch(() => {
19590
19376
  });
19591
- await run4("launchctl", ["load", path]);
19377
+ await run3("launchctl", ["load", path]);
19592
19378
  } else if (process.platform === "win32") {
19593
- spawn6("wscript.exe", [path], { detached: true, stdio: "ignore" }).unref();
19379
+ spawn5("wscript.exe", [path], { detached: true, stdio: "ignore" }).unref();
19594
19380
  } else {
19595
- const note = process.env.DISPLAY || process.env.WAYLAND_DISPLAY ? void 0 : await run4("loginctl", ["enable-linger"]).then(
19381
+ const note = process.env.DISPLAY || process.env.WAYLAND_DISPLAY ? void 0 : await run3("loginctl", ["enable-linger"]).then(
19596
19382
  () => void 0,
19597
19383
  () => `It stops when you log out. To keep it running, run sudo loginctl enable-linger ${userInfo().username}.`
19598
19384
  );
@@ -19604,7 +19390,7 @@ async function installStartup() {
19604
19390
  }
19605
19391
  async function uninstallStartup() {
19606
19392
  const path = serviceFile();
19607
- if (process.platform === "darwin") await run4("launchctl", ["unload", path]).catch(() => {
19393
+ if (process.platform === "darwin") await run3("launchctl", ["unload", path]).catch(() => {
19608
19394
  });
19609
19395
  else if (process.platform !== "win32")
19610
19396
  await systemctl("disable", "dashup.service").catch(() => {
@@ -19709,22 +19495,6 @@ async function main() {
19709
19495
  if (!response.ok) throw new Error("Could not stop Dashup");
19710
19496
  return;
19711
19497
  }
19712
- if (command === "permissions") {
19713
- config.screen = args.includes("--screen");
19714
- config.control = config.screen && args.includes("--control");
19715
- const remote = await running("/permissions", {
19716
- method: "POST",
19717
- body: JSON.stringify({ screen: config.screen, control: config.control }),
19718
- signal: AbortSignal.timeout(5e3)
19719
- }).catch(refused);
19720
- if (remote && !remote.ok) throw new Error("Could not change connector permissions");
19721
- if (!remote) saveConfig({ ...loadConfig(), screen: config.screen, control: config.control });
19722
- process.stdout.write(
19723
- `Screen ${config.screen ? "enabled" : "disabled"}, control ${config.control ? "enabled" : "disabled"}.
19724
- `
19725
- );
19726
- return;
19727
- }
19728
19498
  if (command === "acp") {
19729
19499
  const native = args[1] === "--" ? args.slice(2) : args.slice(1);
19730
19500
  const [executable, ...agentArgs] = native;
@@ -19733,18 +19503,7 @@ async function main() {
19733
19503
  return proxyAcp(config, executable, agentArgs);
19734
19504
  }
19735
19505
  if (command === "doctor" || command === "projects") {
19736
- const support = { screen: Viewer.available(), control: controlAvailable() };
19737
- const discovery = new Discovery(
19738
- config.device?.id ?? "local",
19739
- {
19740
- read: true,
19741
- send: false,
19742
- approve: false,
19743
- screen: config.screen && support.screen,
19744
- control: config.control && support.control
19745
- },
19746
- config.localSecret
19747
- );
19506
+ const discovery = new Discovery(config.device?.id ?? "local", config.localSecret);
19748
19507
  discovery.scan();
19749
19508
  const live = new LiveHarnesses();
19750
19509
  try {
@@ -19759,7 +19518,6 @@ async function main() {
19759
19518
  node: process.version,
19760
19519
  paired: Boolean(config.secret),
19761
19520
  platform: process.platform,
19762
- screen: support,
19763
19521
  projectCount: projects.length,
19764
19522
  harnesses: Object.fromEntries(
19765
19523
  Object.keys(HARNESSES).filter((harness) => harness !== "other").map((harness) => [
@@ -19793,7 +19551,7 @@ async function main() {
19793
19551
  }
19794
19552
  if (!["pair", "watch"].includes(command)) {
19795
19553
  process.stdout.write(
19796
- "Dashup: pair [--account <email>] | watch | projects | alert --project <id> | doctor | permissions --screen --control | stop | acp -- <original-agent-command> | install | uninstall\n"
19554
+ "Dashup: pair [--account <email>] | watch | projects | alert --project <id> | doctor | stop | acp -- <original-agent-command> | install | uninstall\n"
19797
19555
  );
19798
19556
  return;
19799
19557
  }
@@ -19866,7 +19624,7 @@ async function main() {
19866
19624
  response.end(JSON.stringify(connector.invite()));
19867
19625
  return;
19868
19626
  }
19869
- if (!["/attention", "/permissions"].includes(request2.url ?? "") || request2.method !== "POST") {
19627
+ if (request2.url !== "/attention" || request2.method !== "POST") {
19870
19628
  response.writeHead(404).end();
19871
19629
  return;
19872
19630
  }
@@ -19875,12 +19633,6 @@ async function main() {
19875
19633
  value2 += String(chunk);
19876
19634
  if (value2.length > 2e4) throw new Error("Alert too large");
19877
19635
  }
19878
- if (request2.url === "/permissions") {
19879
- const input2 = z3.object({ screen: z3.boolean(), control: z3.boolean() }).strict().parse(JSON.parse(value2));
19880
- await connector.permissions(input2.screen, input2.control);
19881
- response.end("{}");
19882
- return;
19883
- }
19884
19636
  const input = Attention.parse(JSON.parse(value2));
19885
19637
  await connector.attention(
19886
19638
  input.projectId,