@dashup/connector 0.1.7 → 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 +389 -597
  3. package/package.json +5 -5
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.7";
4019
+ var version = "0.1.9";
4021
4020
 
4022
4021
  // src/pair.ts
4023
4022
  var Invitation = z.object({
@@ -4147,284 +4146,26 @@ 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
4164
+ var EARLIER = {
4165
+ id: "earlier",
4166
+ role: "system",
4167
+ text: "Earlier messages stay on your computer."
4168
+ };
4428
4169
  var sessionKey = (session) => `${session.harness}\0${session.id}`;
4429
4170
  function agentHarness(name) {
4430
4171
  if (/claude/i.test(name)) return "claude";
@@ -4433,12 +4174,14 @@ function agentHarness(name) {
4433
4174
  return void 0;
4434
4175
  }
4435
4176
  var MAX_PREVIEW = 64 * 1024;
4436
- 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.";
4437
4178
 
4438
4179
  // src/harness/history.ts
4439
4180
  var SAMPLE_BYTES = 128 * 1024;
4440
4181
  var READ_BYTES = 64 * 1024;
4441
4182
  var TITLE_LIMIT = 120;
4183
+ var LATEST_BYTES = 8 * 1024 * 1024;
4184
+ var JOURNALS = 16;
4442
4185
  var journals = /* @__PURE__ */ new Map();
4443
4186
  var metadata = /* @__PURE__ */ new Map();
4444
4187
  function object(value2) {
@@ -4475,10 +4218,10 @@ function readRange(path, start, length) {
4475
4218
  closeSync2(fd);
4476
4219
  }
4477
4220
  }
4478
- function sample(path, until) {
4221
+ function described(path, until, describe2) {
4479
4222
  const stamp = statSync(path);
4480
4223
  const saved = metadata.get(path);
4481
- if (saved && unchanged(saved.stamp, stamp)) return saved.rows;
4224
+ if (saved && unchanged(saved.stamp, stamp)) return saved.value;
4482
4225
  let head = readRange(path, 0, Math.min(stamp.size, SAMPLE_BYTES));
4483
4226
  if (head.length < stamp.size && head.at(-1) !== 10) {
4484
4227
  const chunks = [head];
@@ -4501,42 +4244,51 @@ function sample(path, until) {
4501
4244
  );
4502
4245
  if (start === head.length || tail.some(until)) break;
4503
4246
  }
4504
- const rows = [...records(head), ...tail];
4505
- metadata.set(path, { stamp, rows });
4506
- return rows;
4247
+ const value2 = describe2([...records(head), ...tail], stamp);
4248
+ metadata.set(path, { stamp, value: value2 });
4249
+ return value2;
4507
4250
  }
4508
- function journal(path) {
4251
+ var windowed = (path, messages5) => journals.get(path)?.start ? [EARLIER, ...messages5] : messages5;
4252
+ function journal(path, latest = false) {
4509
4253
  const stamp = statSync(path);
4510
4254
  let saved = journals.get(path);
4511
4255
  if (saved && unchanged(saved.stamp, stamp)) {
4256
+ journals.delete(path);
4257
+ journals.set(path, saved);
4512
4258
  const tail2 = parse(saved.tail.toString("utf8"));
4513
4259
  return tail2 ? [...saved.rows, tail2] : saved.rows;
4514
4260
  }
4515
- if (!saved || saved.stamp.ino !== stamp.ino || stamp.size <= saved.offset)
4516
- saved = { stamp, offset: 0, tail: Buffer.alloc(0), rows: [] };
4261
+ if (!saved || saved.stamp.ino !== stamp.ino || stamp.size <= saved.offset || latest && stamp.size - saved.start > 2 * LATEST_BYTES) {
4262
+ const start = latest ? Math.max(0, stamp.size - LATEST_BYTES) : 0;
4263
+ saved = { stamp, start, offset: start, tail: Buffer.alloc(0), rows: [] };
4264
+ }
4517
4265
  while (saved.offset < stamp.size) {
4518
4266
  const chunk = readRange(path, saved.offset, Math.min(READ_BYTES, stamp.size - saved.offset));
4519
4267
  if (!chunk.length) break;
4268
+ const first = saved.offset === saved.start && saved.start > 0;
4520
4269
  saved.offset += chunk.length;
4521
4270
  const buffer = Buffer.concat([saved.tail, chunk]);
4522
4271
  const end = buffer.lastIndexOf(10);
4523
4272
  if (end < 0) {
4524
- saved.tail = buffer;
4273
+ saved.tail = first ? Buffer.alloc(0) : buffer;
4525
4274
  continue;
4526
4275
  }
4527
- for (const line of buffer.subarray(0, end).toString("utf8").split("\n")) {
4276
+ const lines = buffer.subarray(0, end).toString("utf8").split("\n");
4277
+ for (const line of first ? lines.slice(1) : lines) {
4528
4278
  const row = parse(line);
4529
4279
  if (row) saved.rows.push(row);
4530
4280
  }
4531
4281
  saved.tail = Buffer.from(buffer.subarray(end + 1));
4532
4282
  }
4533
4283
  saved.stamp = stamp;
4284
+ journals.delete(path);
4534
4285
  journals.set(path, saved);
4286
+ if (journals.size > JOURNALS) journals.delete(journals.keys().next().value);
4535
4287
  const tail = parse(saved.tail.toString("utf8"));
4536
4288
  return tail ? [...saved.rows, tail] : saved.rows;
4537
4289
  }
4538
4290
  function files(root, scan, depth = Infinity) {
4539
- if (!existsSync3(root)) {
4291
+ if (!existsSync2(root)) {
4540
4292
  watchParent(root, scan);
4541
4293
  return [];
4542
4294
  }
@@ -4553,8 +4305,8 @@ function files(root, scan, depth = Infinity) {
4553
4305
  }
4554
4306
  }
4555
4307
  function watchParent(path, scan) {
4556
- while (!existsSync3(path) && dirname2(path) !== path) path = dirname2(path);
4557
- 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);
4558
4310
  }
4559
4311
  function canonical(cwd) {
4560
4312
  if (typeof cwd !== "string" || !isAbsolute(cwd)) return void 0;
@@ -4659,7 +4411,7 @@ function claudeChange(change2) {
4659
4411
  ].join("\n");
4660
4412
  }
4661
4413
  function claudeMessages(path) {
4662
- const rows = journal(path);
4414
+ const rows = journal(path, true);
4663
4415
  const chain = /* @__PURE__ */ new Map();
4664
4416
  let leaf;
4665
4417
  for (const row of rows) {
@@ -4679,6 +4431,9 @@ function claudeMessages(path) {
4679
4431
  leaf = typeof row.parentUuid === "string" ? row.parentUuid : void 0;
4680
4432
  }
4681
4433
  const conversation = selected.length ? selected.reverse() : rows;
4434
+ return windowed(path, claudeConversation(conversation, rows));
4435
+ }
4436
+ function claudeConversation(conversation, rows) {
4682
4437
  const results = /* @__PURE__ */ new Map();
4683
4438
  for (const row of rows)
4684
4439
  for (const part of row.type === "user" ? list(row.message?.content) : [])
@@ -4712,26 +4467,31 @@ function claudeMessages(path) {
4712
4467
  function claude(root, scan) {
4713
4468
  for (const path of files(join2(root, "projects"), scan, 1).filter((file) => file.endsWith(".jsonl"))) {
4714
4469
  try {
4715
- const rows = sample(path, claudeMessage);
4716
- const latest = [...rows].reverse();
4717
- const owner = rows.find((row) => typeof row.sessionId === "string" && canonical(row.cwd));
4718
- 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) {
4719
4489
  scan.warnings.push(`Unrecognized Claude Code session: ${path}`);
4720
4490
  continue;
4721
4491
  }
4722
- const named = latest.find((row) => row.type === "custom-title" || row.type === "ai-title");
4723
- const first = rows.find(
4724
- (row) => row.type === "user" && !row.isMeta && !row.isSidechain && !injected(row.message?.content)
4725
- );
4726
4492
  scan.sessions.push({
4727
4493
  harness: "claude",
4728
- id: owner.sessionId,
4729
- cwd: canonical(owner.cwd),
4730
- title: title(
4731
- named?.customTitle ?? named?.aiTitle ?? text(first?.message?.content),
4732
- "Claude Code thread"
4733
- ),
4734
- updatedAt: date(latest.find(claudeMessage)?.timestamp, statSync(path).mtimeMs),
4494
+ ...session,
4735
4495
  files: [path],
4736
4496
  read: () => claudeMessages(path)
4737
4497
  });
@@ -4759,7 +4519,7 @@ function applyPatch(patch) {
4759
4519
  ).replace(/^\*\*\* .*\n?/gm, "");
4760
4520
  }
4761
4521
  function codexMessages(path) {
4762
- const rows = journal(path);
4522
+ const rows = journal(path, true);
4763
4523
  const outputs = /* @__PURE__ */ new Map();
4764
4524
  for (const row of rows)
4765
4525
  if (["function_call_output", "custom_tool_call_output"].includes(row.payload?.type))
@@ -4788,8 +4548,8 @@ function codexMessages(path) {
4788
4548
  )
4789
4549
  ];
4790
4550
  });
4791
- if (result.length) return result;
4792
- return rows.flatMap((row, index) => {
4551
+ if (result.length) return windowed(path, result);
4552
+ return windowed(path, rows.flatMap((row, index) => {
4793
4553
  if (row.type !== "event_msg") return [];
4794
4554
  const item = object(row.payload);
4795
4555
  if (!item || !["user_message", "agent_message"].includes(item.type)) return [];
@@ -4798,12 +4558,12 @@ function codexMessages(path) {
4798
4558
  item.type === "user_message" ? "user" : "assistant",
4799
4559
  item.message
4800
4560
  );
4801
- });
4561
+ }));
4802
4562
  }
4803
4563
  function codex(root, scan) {
4804
4564
  const index = join2(root, "session_index.jsonl");
4805
4565
  const names = /* @__PURE__ */ new Map();
4806
- if (existsSync3(index)) {
4566
+ if (existsSync2(index)) {
4807
4567
  scan.paths.add(root);
4808
4568
  try {
4809
4569
  for (const row of journal(index)) if (typeof row.id === "string") names.set(row.id, row);
@@ -4812,34 +4572,41 @@ function codex(root, scan) {
4812
4572
  }
4813
4573
  }
4814
4574
  const titles = codexTitles(root);
4575
+ const indexed = existsSync2(index);
4815
4576
  for (const path of [
4816
4577
  ...files(join2(root, "sessions"), scan),
4817
4578
  ...files(join2(root, "archived_sessions"), scan)
4818
4579
  ].filter((file) => file.endsWith(".jsonl"))) {
4819
4580
  try {
4820
- const rows = sample(path, codexItem);
4821
- const latest = [...rows].reverse();
4822
- const owner = rows.find((row) => row.type === "session_meta")?.payload;
4823
- const cwd = canonical(owner?.cwd);
4824
- 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) {
4825
4598
  scan.warnings.push(`Unrecognized Codex session: ${path}`);
4826
4599
  continue;
4827
4600
  }
4828
- const named = names.get(owner.id);
4829
- const first = rows.find(
4830
- (row) => row.type === "response_item" && row.payload?.type === "message" && row.payload.role === "user" && !injected(row.payload.content)
4831
- );
4601
+ const { asked, ...found } = session;
4832
4602
  scan.sessions.push({
4833
4603
  harness: "codex",
4834
- id: owner.id,
4835
- cwd,
4604
+ ...found,
4836
4605
  title: title(
4837
- named?.thread_name ?? titles.get(owner.id) ?? text(first?.payload?.content),
4606
+ names.get(found.id)?.thread_name ?? titles.get(found.id) ?? asked,
4838
4607
  "Codex thread"
4839
4608
  ),
4840
- parent: typeof owner.parent_thread_id === "string" ? owner.parent_thread_id : void 0,
4841
- updatedAt: date(latest.find(codexItem)?.timestamp, statSync(path).mtimeMs),
4842
- files: [path, ...existsSync3(index) ? [index] : []],
4609
+ files: [path, ...indexed ? [index] : []],
4843
4610
  read: () => codexMessages(path)
4844
4611
  });
4845
4612
  } catch {
@@ -4906,17 +4673,22 @@ function databaseMessages(path, session) {
4906
4673
  }
4907
4674
  }
4908
4675
  function codexSubagent(row) {
4909
- const spawn7 = object(object(parse(String(row.source ?? "")))?.subagent)?.thread_spawn;
4910
- 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() : "";
4911
4678
  return [row.agent_nickname, task?.replace(/_/g, " ")].filter(Boolean).join(" \xB7 ");
4912
4679
  }
4680
+ var codexState;
4913
4681
  function codexTitles(root) {
4914
4682
  const titles = /* @__PURE__ */ new Map();
4915
- 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];
4916
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 };
4917
4689
  let db;
4918
4690
  try {
4919
- db = openDatabase(join2(root, state));
4691
+ db = openDatabase(path);
4920
4692
  for (const row of db.prepare("SELECT id, title, first_user_message, source, agent_nickname FROM threads").all()) {
4921
4693
  const name = [row.title, row.first_user_message].find(
4922
4694
  (value2) => typeof value2 === "string" && !!value2.trim() && !injected(value2)
@@ -4930,7 +4702,7 @@ function codexTitles(root) {
4930
4702
  return titles;
4931
4703
  }
4932
4704
  function openCodeDatabase(path, scan) {
4933
- if (!existsSync3(path)) {
4705
+ if (!existsSync2(path)) {
4934
4706
  watchParent(path, scan);
4935
4707
  return;
4936
4708
  }
@@ -4992,7 +4764,7 @@ function legacyMessages(root, session) {
4992
4764
  }
4993
4765
  function openCodeLegacy(root, scan) {
4994
4766
  const parts2 = join2(root, "part");
4995
- if (existsSync3(parts2)) files(parts2, scan, 1);
4767
+ if (existsSync2(parts2)) files(parts2, scan, 1);
4996
4768
  for (const path of files(join2(root, "session"), scan).filter((file) => file.endsWith(".json"))) {
4997
4769
  try {
4998
4770
  const row = jsonFile(path);
@@ -5045,7 +4817,22 @@ function scanHistories() {
5045
4817
  }
5046
4818
 
5047
4819
  // src/discovery.ts
5048
- 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
+ };
5049
4836
  var agentThread = (harness, session, secret) => opaqueId(`agent\0${harness}\0${session}`, secret);
5050
4837
  var zedHost = (value2) => typeof value2 === "string" ? value2 : value2 && typeof value2 === "object" ? String(Object.values(value2)[0] ?? "") : "";
5051
4838
  function vscodeHost(authority) {
@@ -5131,21 +4918,23 @@ function contentText(content) {
5131
4918
  return typeof part.text === "string" ? part.text : typeof part.Text === "string" ? part.Text : typeof part.value === "string" ? part.value : contentText(part.content);
5132
4919
  }
5133
4920
  var Discovery = class {
5134
- constructor(deviceId, capabilities, secret) {
4921
+ constructor(deviceId, secret) {
5135
4922
  this.deviceId = deviceId;
5136
- this.capabilities = capabilities;
5137
4923
  this.secret = secret;
5138
4924
  }
5139
4925
  deviceId;
5140
- capabilities;
5141
4926
  secret;
5142
4927
  projects = /* @__PURE__ */ new Map();
5143
4928
  locations = /* @__PURE__ */ new Map();
4929
+ // Each native agent session's thread, so finding it costs the same however many sessions a scan
4930
+ // sees. The first project to list a thread keeps it.
4931
+ agents = /* @__PURE__ */ new Map();
5144
4932
  paths = /* @__PURE__ */ new Set();
5145
4933
  warnings = [];
5146
4934
  scan() {
5147
4935
  this.projects.clear();
5148
4936
  this.locations.clear();
4937
+ this.agents.clear();
5149
4938
  this.paths.clear();
5150
4939
  this.warnings.length = 0;
5151
4940
  const defaults = dataPaths();
@@ -5160,12 +4949,8 @@ var Discovery = class {
5160
4949
  return [...this.projects.values()];
5161
4950
  }
5162
4951
  native(session) {
5163
- for (const project2 of this.projects.values()) {
5164
- const thread2 = project2.threads.find(
5165
- (item) => item.agent?.harness === session.harness && item.agent.sessionId === session.id
5166
- );
5167
- if (thread2) return { project: project2, thread: thread2 };
5168
- }
4952
+ const known = this.agents.get(`${session.harness}\0${session.id}`);
4953
+ if (known) return known;
5169
4954
  const project = this.project(session.cwd);
5170
4955
  const thread = {
5171
4956
  id: agentThread(session.harness, session.id, this.secret),
@@ -5173,11 +4958,12 @@ var Discovery = class {
5173
4958
  editor: "cli",
5174
4959
  status: "unknown",
5175
4960
  updatedAt: session.updatedAt,
5176
- capabilities: { ...this.capabilities },
4961
+ capabilities: { ...READ_ONLY },
5177
4962
  agent: { harness: session.harness, sessionId: session.id }
5178
4963
  };
5179
4964
  project.threads.push(thread);
5180
4965
  this.locations.set(thread.id, { editor: "cli", path: "" });
4966
+ this.agents.set(`${session.harness}\0${session.id}`, { project, thread });
5181
4967
  return { project, thread };
5182
4968
  }
5183
4969
  // The native history owns the conversation's last message time, whichever editor lists it.
@@ -5196,7 +4982,7 @@ var Discovery = class {
5196
4982
  const parent = dirname3(project.path);
5197
4983
  counts.set(parent, (counts.get(parent) ?? 0) + 1);
5198
4984
  }
5199
- 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);
5200
4986
  return folders.length ? folders : [homedir3()];
5201
4987
  }
5202
4988
  observe(session) {
@@ -5233,7 +5019,7 @@ var Discovery = class {
5233
5019
  editor: "acp",
5234
5020
  status: "idle",
5235
5021
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
5236
- capabilities: this.capabilities
5022
+ capabilities: READ_ONLY
5237
5023
  };
5238
5024
  project.threads.push(thread);
5239
5025
  this.locations.set(id, { editor: "acp", path: "", session });
@@ -5261,11 +5047,11 @@ var Discovery = class {
5261
5047
  zed(root) {
5262
5048
  const database = join3(root, "db");
5263
5049
  const store = join3(root, "threads/threads.db");
5264
- if (!existsSync4(database)) return;
5050
+ if (!existsSync3(database)) return;
5265
5051
  this.paths.add(database);
5266
5052
  for (const entry of readdirSync2(database)) {
5267
5053
  const path = join3(database, entry, "db.sqlite");
5268
- if (!existsSync4(path)) continue;
5054
+ if (!existsSync3(path)) continue;
5269
5055
  this.paths.add(join3(database, entry));
5270
5056
  try {
5271
5057
  if (!sqlite(
@@ -5274,7 +5060,7 @@ var Discovery = class {
5274
5060
  ).length)
5275
5061
  continue;
5276
5062
  const stamps = new Map(
5277
- 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) => [
5278
5064
  row.id,
5279
5065
  row.updated_at
5280
5066
  ]) : []
@@ -5300,16 +5086,20 @@ var Discovery = class {
5300
5086
  ...ssh.port ? { port: Number(ssh.port) } : {}
5301
5087
  } : void 0;
5302
5088
  const project = this.project(folder, remote);
5303
- if (!project.threads.some((thread) => thread.id === id))
5304
- project.threads.push({
5089
+ if (!project.threads.some((thread) => thread.id === id)) {
5090
+ const thread = {
5305
5091
  id,
5306
5092
  title: row.title_override || row.title,
5307
5093
  editor: "zed",
5308
5094
  status: "unknown",
5309
5095
  updatedAt,
5310
- capabilities: this.capabilities,
5096
+ capabilities: READ_ONLY,
5311
5097
  ...harness ? { agent: { harness, sessionId: session } } : {}
5312
- });
5098
+ };
5099
+ project.threads.push(thread);
5100
+ if (harness && !this.agents.has(`${harness}\0${session}`))
5101
+ this.agents.set(`${harness}\0${session}`, { project, thread });
5102
+ }
5313
5103
  this.locations.set(id, { editor: "zed", path: store, session });
5314
5104
  }
5315
5105
  }
@@ -5318,17 +5108,17 @@ var Discovery = class {
5318
5108
  }
5319
5109
  }
5320
5110
  const threads = join3(root, "threads");
5321
- if (existsSync4(threads)) this.paths.add(threads);
5111
+ if (existsSync3(threads)) this.paths.add(threads);
5322
5112
  }
5323
5113
  vscode(root) {
5324
5114
  const workspaces = join3(root, "workspaceStorage");
5325
- if (!existsSync4(workspaces)) return;
5115
+ if (!existsSync3(workspaces)) return;
5326
5116
  this.paths.add(workspaces);
5327
5117
  for (const entry of readdirSync2(workspaces)) {
5328
5118
  const directory2 = join3(workspaces, entry);
5329
5119
  const workspace = join3(directory2, "workspace.json");
5330
5120
  const sessions = join3(directory2, "chatSessions");
5331
- if (!existsSync4(workspace)) continue;
5121
+ if (!existsSync3(workspace)) continue;
5332
5122
  this.paths.add(directory2);
5333
5123
  try {
5334
5124
  const data = JSON.parse(readFileSync2(workspace, "utf8"));
@@ -5337,7 +5127,7 @@ var Discovery = class {
5337
5127
  const path = decodeURIComponent(uri.pathname).replace(/^\/([A-Za-z]:\/)/, "$1");
5338
5128
  const host = uri.protocol === "vscode-remote:" ? vscodeHost(uri.host) : "";
5339
5129
  const project = this.project(path, host ? { host, path } : void 0);
5340
- if (!existsSync4(sessions)) continue;
5130
+ if (!existsSync3(sessions)) continue;
5341
5131
  this.paths.add(sessions);
5342
5132
  for (const filename of readdirSync2(sessions).filter((name) => /\.jsonl?$/.test(name))) {
5343
5133
  const file = join3(sessions, filename);
@@ -5349,7 +5139,7 @@ var Discovery = class {
5349
5139
  editor: "vscode",
5350
5140
  status: "unknown",
5351
5141
  updatedAt: new Date(state.lastMessageDate ?? state.creationDate ?? 0).toISOString(),
5352
- capabilities: this.capabilities
5142
+ capabilities: READ_ONLY
5353
5143
  });
5354
5144
  this.locations.set(id, { editor: "vscode", path: file });
5355
5145
  }
@@ -5375,16 +5165,16 @@ var Discovery = class {
5375
5165
  }
5376
5166
  ]).filter((message) => message.text);
5377
5167
  }
5378
- if (!existsSync4(location2.path))
5168
+ if (!existsSync3(location2.path))
5379
5169
  throw new Error(
5380
- "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."
5381
5171
  );
5382
5172
  const db = new DatabaseSync2(location2.path, { readOnly: true });
5383
5173
  try {
5384
5174
  const row = db.prepare("SELECT data_type, data FROM threads WHERE id = ?").get(location2.session);
5385
5175
  if (!row)
5386
5176
  throw new Error(
5387
- "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."
5388
5178
  );
5389
5179
  const data = row.data_type === "zstd" ? zstdDecompressSync(row.data) : row.data;
5390
5180
  const state = JSON.parse(Buffer.from(data).toString("utf8"));
@@ -10612,20 +10402,20 @@ var deflate$2 = (strm, flush) => {
10612
10402
  let beg = s.pending;
10613
10403
  let left = (s.gzhead.extra.length & 65535) - s.gzindex;
10614
10404
  while (s.pending + left > s.pending_buf_size) {
10615
- let copy = s.pending_buf_size - s.pending;
10616
- 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);
10617
10407
  s.pending = s.pending_buf_size;
10618
10408
  if (s.gzhead.hcrc && s.pending > beg) {
10619
10409
  strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg);
10620
10410
  }
10621
- s.gzindex += copy;
10411
+ s.gzindex += copy2;
10622
10412
  flush_pending(strm);
10623
10413
  if (s.pending !== 0) {
10624
10414
  s.last_flush = -1;
10625
10415
  return Z_OK$3;
10626
10416
  }
10627
10417
  beg = 0;
10628
- left -= copy;
10418
+ left -= copy2;
10629
10419
  }
10630
10420
  let gzhead_extra = new Uint8Array(s.gzhead.extra);
10631
10421
  s.pending_buf.set(gzhead_extra.subarray(s.gzindex, s.gzindex + left), s.pending);
@@ -11935,7 +11725,7 @@ var fixedtables = (state) => {
11935
11725
  state.distcode = distfix;
11936
11726
  state.distbits = 5;
11937
11727
  };
11938
- var updatewindow = (strm, src, end, copy) => {
11728
+ var updatewindow = (strm, src, end, copy2) => {
11939
11729
  let dist;
11940
11730
  const state = strm.state;
11941
11731
  if (state.window === null) {
@@ -11946,20 +11736,20 @@ var updatewindow = (strm, src, end, copy) => {
11946
11736
  state.wnext = 0;
11947
11737
  state.whave = 0;
11948
11738
  }
11949
- if (copy >= state.wsize) {
11739
+ if (copy2 >= state.wsize) {
11950
11740
  state.window.set(src.subarray(end - state.wsize, end), 0);
11951
11741
  state.wnext = 0;
11952
11742
  state.whave = state.wsize;
11953
11743
  } else {
11954
11744
  dist = state.wsize - state.wnext;
11955
- if (dist > copy) {
11956
- dist = copy;
11957
- }
11958
- state.window.set(src.subarray(end - copy, end - copy + dist), state.wnext);
11959
- copy -= dist;
11960
- if (copy) {
11961
- state.window.set(src.subarray(end - copy, end), 0);
11962
- 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;
11963
11753
  state.whave = state.wsize;
11964
11754
  } else {
11965
11755
  state.wnext += dist;
@@ -11982,7 +11772,7 @@ var inflate$2 = (strm, flush) => {
11982
11772
  let hold;
11983
11773
  let bits;
11984
11774
  let _in, _out;
11985
- let copy;
11775
+ let copy2;
11986
11776
  let from;
11987
11777
  let from_source;
11988
11778
  let here = 0;
@@ -12181,11 +11971,11 @@ var inflate$2 = (strm, flush) => {
12181
11971
  /* falls through */
12182
11972
  case EXTRA:
12183
11973
  if (state.flags & 1024) {
12184
- copy = state.length;
12185
- if (copy > have) {
12186
- copy = have;
11974
+ copy2 = state.length;
11975
+ if (copy2 > have) {
11976
+ copy2 = have;
12187
11977
  }
12188
- if (copy) {
11978
+ if (copy2) {
12189
11979
  if (state.head) {
12190
11980
  len = state.head.extra_len - state.length;
12191
11981
  if (!state.head.extra) {
@@ -12196,18 +11986,18 @@ var inflate$2 = (strm, flush) => {
12196
11986
  next,
12197
11987
  // extra field is limited to 65536 bytes
12198
11988
  // - no need for additional size check
12199
- next + copy
11989
+ next + copy2
12200
11990
  ),
12201
11991
  /*len + copy > state.head.extra_max - len ? state.head.extra_max : copy,*/
12202
11992
  len
12203
11993
  );
12204
11994
  }
12205
11995
  if (state.flags & 512 && state.wrap & 4) {
12206
- state.check = crc32_1(state.check, input, copy, next);
11996
+ state.check = crc32_1(state.check, input, copy2, next);
12207
11997
  }
12208
- have -= copy;
12209
- next += copy;
12210
- state.length -= copy;
11998
+ have -= copy2;
11999
+ next += copy2;
12000
+ state.length -= copy2;
12211
12001
  }
12212
12002
  if (state.length) {
12213
12003
  break inf_leave;
@@ -12221,18 +12011,18 @@ var inflate$2 = (strm, flush) => {
12221
12011
  if (have === 0) {
12222
12012
  break inf_leave;
12223
12013
  }
12224
- copy = 0;
12014
+ copy2 = 0;
12225
12015
  do {
12226
- len = input[next + copy++];
12016
+ len = input[next + copy2++];
12227
12017
  if (state.head && len && state.length < 65536) {
12228
12018
  state.head.name += String.fromCharCode(len);
12229
12019
  }
12230
- } while (len && copy < have);
12020
+ } while (len && copy2 < have);
12231
12021
  if (state.flags & 512 && state.wrap & 4) {
12232
- state.check = crc32_1(state.check, input, copy, next);
12022
+ state.check = crc32_1(state.check, input, copy2, next);
12233
12023
  }
12234
- have -= copy;
12235
- next += copy;
12024
+ have -= copy2;
12025
+ next += copy2;
12236
12026
  if (len) {
12237
12027
  break inf_leave;
12238
12028
  }
@@ -12247,18 +12037,18 @@ var inflate$2 = (strm, flush) => {
12247
12037
  if (have === 0) {
12248
12038
  break inf_leave;
12249
12039
  }
12250
- copy = 0;
12040
+ copy2 = 0;
12251
12041
  do {
12252
- len = input[next + copy++];
12042
+ len = input[next + copy2++];
12253
12043
  if (state.head && len && state.length < 65536) {
12254
12044
  state.head.comment += String.fromCharCode(len);
12255
12045
  }
12256
- } while (len && copy < have);
12046
+ } while (len && copy2 < have);
12257
12047
  if (state.flags & 512 && state.wrap & 4) {
12258
- state.check = crc32_1(state.check, input, copy, next);
12048
+ state.check = crc32_1(state.check, input, copy2, next);
12259
12049
  }
12260
- have -= copy;
12261
- next += copy;
12050
+ have -= copy2;
12051
+ next += copy2;
12262
12052
  if (len) {
12263
12053
  break inf_leave;
12264
12054
  }
@@ -12393,23 +12183,23 @@ var inflate$2 = (strm, flush) => {
12393
12183
  state.mode = COPY;
12394
12184
  /* falls through */
12395
12185
  case COPY:
12396
- copy = state.length;
12397
- if (copy) {
12398
- if (copy > have) {
12399
- copy = have;
12186
+ copy2 = state.length;
12187
+ if (copy2) {
12188
+ if (copy2 > have) {
12189
+ copy2 = have;
12400
12190
  }
12401
- if (copy > left) {
12402
- copy = left;
12191
+ if (copy2 > left) {
12192
+ copy2 = left;
12403
12193
  }
12404
- if (copy === 0) {
12194
+ if (copy2 === 0) {
12405
12195
  break inf_leave;
12406
12196
  }
12407
- output.set(input.subarray(next, next + copy), put);
12408
- have -= copy;
12409
- next += copy;
12410
- left -= copy;
12411
- put += copy;
12412
- 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;
12413
12203
  break;
12414
12204
  }
12415
12205
  state.mode = TYPE;
@@ -12510,7 +12300,7 @@ var inflate$2 = (strm, flush) => {
12510
12300
  break;
12511
12301
  }
12512
12302
  len = state.lens[state.have - 1];
12513
- copy = 3 + (hold & 3);
12303
+ copy2 = 3 + (hold & 3);
12514
12304
  hold >>>= 2;
12515
12305
  bits -= 2;
12516
12306
  } else if (here_val === 17) {
@@ -12526,7 +12316,7 @@ var inflate$2 = (strm, flush) => {
12526
12316
  hold >>>= here_bits;
12527
12317
  bits -= here_bits;
12528
12318
  len = 0;
12529
- copy = 3 + (hold & 7);
12319
+ copy2 = 3 + (hold & 7);
12530
12320
  hold >>>= 3;
12531
12321
  bits -= 3;
12532
12322
  } else {
@@ -12542,16 +12332,16 @@ var inflate$2 = (strm, flush) => {
12542
12332
  hold >>>= here_bits;
12543
12333
  bits -= here_bits;
12544
12334
  len = 0;
12545
- copy = 11 + (hold & 127);
12335
+ copy2 = 11 + (hold & 127);
12546
12336
  hold >>>= 7;
12547
12337
  bits -= 7;
12548
12338
  }
12549
- if (state.have + copy > state.nlen + state.ndist) {
12339
+ if (state.have + copy2 > state.nlen + state.ndist) {
12550
12340
  strm.msg = "invalid bit length repeat";
12551
12341
  state.mode = BAD;
12552
12342
  break;
12553
12343
  }
12554
- while (copy--) {
12344
+ while (copy2--) {
12555
12345
  state.lens[state.have++] = len;
12556
12346
  }
12557
12347
  }
@@ -12770,39 +12560,39 @@ var inflate$2 = (strm, flush) => {
12770
12560
  if (left === 0) {
12771
12561
  break inf_leave;
12772
12562
  }
12773
- copy = _out - left;
12774
- if (state.offset > copy) {
12775
- copy = state.offset - copy;
12776
- if (copy > state.whave) {
12563
+ copy2 = _out - left;
12564
+ if (state.offset > copy2) {
12565
+ copy2 = state.offset - copy2;
12566
+ if (copy2 > state.whave) {
12777
12567
  if (state.sane) {
12778
12568
  strm.msg = "invalid distance too far back";
12779
12569
  state.mode = BAD;
12780
12570
  break;
12781
12571
  }
12782
12572
  }
12783
- if (copy > state.wnext) {
12784
- copy -= state.wnext;
12785
- from = state.wsize - copy;
12573
+ if (copy2 > state.wnext) {
12574
+ copy2 -= state.wnext;
12575
+ from = state.wsize - copy2;
12786
12576
  } else {
12787
- from = state.wnext - copy;
12577
+ from = state.wnext - copy2;
12788
12578
  }
12789
- if (copy > state.length) {
12790
- copy = state.length;
12579
+ if (copy2 > state.length) {
12580
+ copy2 = state.length;
12791
12581
  }
12792
12582
  from_source = state.window;
12793
12583
  } else {
12794
12584
  from_source = output;
12795
12585
  from = put - state.offset;
12796
- copy = state.length;
12586
+ copy2 = state.length;
12797
12587
  }
12798
- if (copy > left) {
12799
- copy = left;
12588
+ if (copy2 > left) {
12589
+ copy2 = left;
12800
12590
  }
12801
- left -= copy;
12802
- state.length -= copy;
12591
+ left -= copy2;
12592
+ state.length -= copy2;
12803
12593
  do {
12804
12594
  output[put++] = from_source[from++];
12805
- } while (--copy);
12595
+ } while (--copy2);
12806
12596
  if (state.length === 0) {
12807
12597
  state.mode = LEN;
12808
12598
  }
@@ -14693,12 +14483,12 @@ var Model = class _Model extends import_index2.default {
14693
14483
  });
14694
14484
  return value2;
14695
14485
  } catch {
14696
- const copy = { ...value2 };
14697
- Object.defineProperty(copy, SYM_EXPANDED_REF, {
14486
+ const copy2 = { ...value2 };
14487
+ Object.defineProperty(copy2, SYM_EXPANDED_REF, {
14698
14488
  enumerable: true,
14699
14489
  value: true
14700
14490
  });
14701
- return copy;
14491
+ return copy2;
14702
14492
  }
14703
14493
  }
14704
14494
  /**
@@ -16385,6 +16175,24 @@ function sealHistory(key, routing, message, random) {
16385
16175
 
16386
16176
  // src/history.ts
16387
16177
  var HISTORY_PATH = "/dashup/history";
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
+ }
16388
16196
  async function upload(config2, batch) {
16389
16197
  if (batch.records.length > HISTORY_BATCH_ITEMS || Buffer.byteLength(JSON.stringify(batch)) > HISTORY_BATCH_BYTES)
16390
16198
  throw new Error("The encrypted history upload exceeds the batch limit");
@@ -16399,8 +16207,10 @@ async function publishHistory(config2, pair, project, thread, messages5) {
16399
16207
  const local = config2.pairs[pair.id];
16400
16208
  const generation = ++local.sequence;
16401
16209
  saveConfig(config2);
16402
- const opaque = (value2) => createHmac2("sha256", Buffer.from(local.key, "hex")).update(JSON.stringify(value2)).digest("hex");
16403
- const fragments = splitHistoryMessages(messages5, (id) => opaque(["history-message", project, thread.id, id]));
16210
+ const all = historyMessages(local.key, project, thread.id, messages5);
16211
+ let start = Math.max(0, all.length - HISTORY_KEPT);
16212
+ while (start < all.length && (all[start].part ?? 0) > 0) start++;
16213
+ const fragments = start ? [...historyMessages(local.key, project, thread.id, [EARLIER]), ...all.slice(start)] : all;
16404
16214
  const summary = { generation, total: fragments.length, updatedAt: thread.updatedAt };
16405
16215
  const batch = {
16406
16216
  pairing: pair.id,
@@ -16416,7 +16226,7 @@ async function publishHistory(config2, pair, project, thread, messages5) {
16416
16226
  const record = sealHistory(
16417
16227
  local.key,
16418
16228
  {
16419
- id: opaque(["history-record", project, thread.id, generation, position]),
16229
+ id: opaque(local.key, ["history-record", project, thread.id, generation, position]),
16420
16230
  pairing: pair.id,
16421
16231
  sender: pair.device,
16422
16232
  recipient: pair.mobile,
@@ -16445,16 +16255,16 @@ async function publishHistory(config2, pair, project, thread, messages5) {
16445
16255
  }
16446
16256
 
16447
16257
  // src/harness/live.ts
16448
- import { promisify as promisify6 } from "node:util";
16449
- 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";
16450
16260
 
16451
16261
  // src/harness/codex.ts
16452
16262
  import WebSocket2 from "ws";
16453
16263
  import { homedir as homedir4 } from "node:os";
16454
16264
  import { connect } from "node:net";
16455
- import { promisify as promisify3 } from "node:util";
16265
+ import { promisify as promisify2 } from "node:util";
16456
16266
  import { randomUUID as randomUUID2 } from "node:crypto";
16457
- import { execFile as execFile3 } from "node:child_process";
16267
+ import { execFile as execFile2 } from "node:child_process";
16458
16268
  import { join as join4, isAbsolute as isAbsolute2 } from "node:path";
16459
16269
  import { stat, readdir, readFile } from "node:fs/promises";
16460
16270
  var REQUEST_MS = 4e3;
@@ -16478,7 +16288,7 @@ function approval(id, method, params) {
16478
16288
  const oversized = Buffer.byteLength(detail) > MAX_PREVIEW;
16479
16289
  const unavailable = !command2 || params.kind !== void 0 && params.kind !== "command" || typeof params.command !== "string" || !params.command.trim() || oversized;
16480
16290
  if (oversized) detail = Buffer.from(detail).subarray(0, MAX_PREVIEW).toString("utf8");
16481
- if (unavailable) detail = [detail, SCREEN_PREVIEW].filter(Boolean).join("\n\n");
16291
+ if (unavailable) detail = [detail, NO_PREVIEW].filter(Boolean).join("\n\n");
16482
16292
  return {
16483
16293
  id,
16484
16294
  title: command2 ? "Allow command?" : "Allow file changes?",
@@ -16774,7 +16584,7 @@ var Codex = class {
16774
16584
  // Codex's own daemon runs the threads Dashup starts or resumes, so they outlive this connector.
16775
16585
  async start(cwd, text3, saved) {
16776
16586
  const path = process.env.DASHUP_CODEX_SOCKET || join4(codexHome(), ...SOCKET_PARTS);
16777
- await promisify3(execFile3)("codex", ["app-server", "daemon", "start"], {
16587
+ await promisify2(execFile2)("codex", ["app-server", "daemon", "start"], {
16778
16588
  timeout: 6e4,
16779
16589
  shell: process.platform === "win32"
16780
16590
  });
@@ -16817,8 +16627,8 @@ var Codex = class {
16817
16627
  // src/harness/headless.ts
16818
16628
  import { randomUUID as randomUUID3 } from "node:crypto";
16819
16629
  import { readFileSync as readFileSync3 } from "node:fs";
16820
- import { createInterface as createInterface3 } from "node:readline";
16821
- 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";
16822
16632
  var CHOICES = [
16823
16633
  { id: "accept", label: "Allow once" },
16824
16634
  { id: "decline", label: "Reject" }
@@ -16941,7 +16751,7 @@ var Session = class {
16941
16751
  this.changed();
16942
16752
  return Promise.resolve();
16943
16753
  }
16944
- const turn = this.turn = spawn3(
16754
+ const turn = this.turn = spawn2(
16945
16755
  "claude",
16946
16756
  [
16947
16757
  "-p",
@@ -16964,7 +16774,7 @@ var Session = class {
16964
16774
  errors = (errors + String(chunk)).slice(-2e3);
16965
16775
  });
16966
16776
  const started = new Promise((resolve3, reject) => {
16967
- createInterface3({ input: turn.stdout }).on("line", (line) => {
16777
+ createInterface2({ input: turn.stdout }).on("line", (line) => {
16968
16778
  if (this.receive(line)) resolve3();
16969
16779
  });
16970
16780
  const end = (reason) => {
@@ -16996,7 +16806,7 @@ var Session = class {
16996
16806
  else if (message.type === "assistant") {
16997
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");
16998
16808
  if (!text3) return false;
16999
- 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 });
17000
16810
  } else if (message.type === "control_request" && message.request?.subtype === "can_use_tool") {
17001
16811
  const id = randomUUID3();
17002
16812
  this.approvals.set(id, {
@@ -17218,9 +17028,9 @@ var Claude = class {
17218
17028
  import Socket3 from "ws";
17219
17029
  import { homedir as homedir5 } from "node:os";
17220
17030
  import { connect as connect3 } from "node:net";
17221
- import { promisify as promisify4 } from "node:util";
17222
- import { fileURLToPath as fileURLToPath2 } from "node:url";
17223
- 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";
17224
17034
  import { createHash as createHash3, randomUUID as randomUUID4 } from "node:crypto";
17225
17035
  import { stat as stat2, readdir as readdir3, readFile as readFile3 } from "node:fs/promises";
17226
17036
  import { join as join6, dirname as dirname4, basename as basename2, isAbsolute as isAbsolute3 } from "node:path";
@@ -17233,7 +17043,7 @@ var MAX_METADATA = 64 * 1024;
17233
17043
  var MAX_HOSTS = 16;
17234
17044
  var MAX_SESSIONS3 = 100;
17235
17045
  var ROOT_CHANNEL = "ahp-root://";
17236
- var run2 = promisify4(execFile4);
17046
+ var run = promisify3(execFile3);
17237
17047
  var CONFIRMATION_CHOICES = [
17238
17048
  { id: "approve", label: "Allow once", kind: "approve" },
17239
17049
  { id: "deny", label: "Reject", kind: "deny" }
@@ -17253,7 +17063,7 @@ async function privateFile(path, limit) {
17253
17063
  const encodedPath = Buffer.from(path).toString("base64");
17254
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`;
17255
17065
  try {
17256
- const { stdout } = await run2(
17066
+ const { stdout } = await run(
17257
17067
  "powershell.exe",
17258
17068
  [
17259
17069
  "-NoProfile",
@@ -17322,7 +17132,7 @@ function directory(state) {
17322
17132
  const uri = state.workingDirectories?.[0];
17323
17133
  if (typeof uri !== "string" || !uri.startsWith("file:")) return;
17324
17134
  try {
17325
- return fileURLToPath2(uri);
17135
+ return fileURLToPath(uri);
17326
17136
  } catch {
17327
17137
  return;
17328
17138
  }
@@ -17343,7 +17153,7 @@ function confirmation(toolCall) {
17343
17153
  const paths = [before, after].filter((uri) => typeof uri === "string");
17344
17154
  const names = [...new Set(paths)].map((uri) => {
17345
17155
  try {
17346
- return fileURLToPath2(uri);
17156
+ return fileURLToPath(uri);
17347
17157
  } catch {
17348
17158
  return uri;
17349
17159
  }
@@ -17370,8 +17180,7 @@ function confirmation(toolCall) {
17370
17180
  })) : [];
17371
17181
  if (unavailable) choices = choices.filter((choice) => choice.kind === "deny");
17372
17182
  if (oversized) detail = Buffer.from(detail).subarray(0, MAX_PREVIEW).toString("utf8");
17373
- if (unavailable || !choices.length)
17374
- detail = [detail, SCREEN_PREVIEW].filter(Boolean).join("\n\n");
17183
+ if (unavailable || !choices.length) detail = [detail, NO_PREVIEW].filter(Boolean).join("\n\n");
17375
17184
  return { detail, choices };
17376
17185
  }
17377
17186
  function messages3(chat) {
@@ -17715,11 +17524,11 @@ var VSCode = class {
17715
17524
 
17716
17525
  // src/harness/opencode.ts
17717
17526
  import { join as join7 } from "node:path";
17718
- import { promisify as promisify5 } from "node:util";
17527
+ import { promisify as promisify4 } from "node:util";
17719
17528
  import { createHash as createHash4 } from "node:crypto";
17720
- import { execFile as execFile5, spawn as spawn4 } from "node:child_process";
17529
+ import { execFile as execFile4, spawn as spawn3 } from "node:child_process";
17721
17530
  import { stat as stat3, readlink, readdir as readdir4, readFile as readFile4 } from "node:fs/promises";
17722
- var run3 = promisify5(execFile5);
17531
+ var run2 = promisify4(execFile4);
17723
17532
  var REQUEST_MS4 = 4e3;
17724
17533
  var MAX_BYTES3 = 8 * 1024 * 1024;
17725
17534
  var MAX_SESSIONS4 = 100;
@@ -17799,7 +17608,7 @@ async function endpoints() {
17799
17608
  if (process.env.DASHUP_LIVE_DISCOVERY === "0") return [];
17800
17609
  if (process.platform === "linux") return linuxEndpoints();
17801
17610
  if (process.platform === "darwin") {
17802
- const { stdout } = await run3(
17611
+ const { stdout } = await run2(
17803
17612
  "lsof",
17804
17613
  ["-nP", "-a", "-c", "opencode", "-iTCP", "-sTCP:LISTEN", "-Fn"],
17805
17614
  { timeout: REQUEST_MS4, maxBuffer: MAX_BYTES3 }
@@ -17816,7 +17625,7 @@ async function endpoints() {
17816
17625
  }
17817
17626
  if (process.platform === "win32") {
17818
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";
17819
- const { stdout } = await run3(
17628
+ const { stdout } = await run2(
17820
17629
  "powershell.exe",
17821
17630
  ["-NoProfile", "-NonInteractive", "-Command", script],
17822
17631
  { timeout: REQUEST_MS4, maxBuffer: MAX_BYTES3 }
@@ -17897,7 +17706,7 @@ ${always.join("\n")}` : ""
17897
17706
  }
17898
17707
  if (unavailable) detail += `
17899
17708
 
17900
- ${SCREEN_PREVIEW}`;
17709
+ ${NO_PREVIEW}`;
17901
17710
  return {
17902
17711
  id: permission.id,
17903
17712
  title: `Allow ${String(permission.permission ?? "tool")}?`,
@@ -18098,7 +17907,7 @@ var Server = class {
18098
17907
  if (!permissionApproval(permission, history2, cwd).choices.some(
18099
17908
  (value2) => value2.id === choice
18100
17909
  ))
18101
- throw new Error(SCREEN_PREVIEW);
17910
+ throw new Error(NO_PREVIEW);
18102
17911
  }
18103
17912
  await this.request(`/permission/${encodeURIComponent(approvalId)}/reply`, cwd, {
18104
17913
  reply: choice
@@ -18185,7 +17994,7 @@ var Server = class {
18185
17994
  }
18186
17995
  };
18187
17996
  function serve(cwd) {
18188
- 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"], {
18189
17998
  cwd,
18190
17999
  stdio: ["ignore", "pipe", "ignore"],
18191
18000
  shell: process.platform === "win32"
@@ -18310,7 +18119,7 @@ var LiveHarnesses = class {
18310
18119
  async agents() {
18311
18120
  const harnesses = ["claude", "codex", "opencode"];
18312
18121
  const runs = await Promise.all(
18313
- harnesses.map((harness) => promisify6(execFile6)(harness, ["--version"], {
18122
+ harnesses.map((harness) => promisify5(execFile5)(harness, ["--version"], {
18314
18123
  timeout: 1e4,
18315
18124
  shell: process.platform === "win32"
18316
18125
  }).then(
@@ -18363,10 +18172,10 @@ var LiveHarnesses = class {
18363
18172
 
18364
18173
  // src/harness/queue.ts
18365
18174
  import { randomUUID as randomUUID5 } from "node:crypto";
18366
- import { spawn as spawn5 } from "node:child_process";
18175
+ import { spawn as spawn4 } from "node:child_process";
18367
18176
  import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
18368
18177
  import { join as join8, dirname as dirname5, basename as basename3 } from "node:path";
18369
- 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";
18370
18179
  var CODEX_VERSION = "0.160.1";
18371
18180
  var REQUEST_MS5 = 1e4;
18372
18181
  var MAX_BYTES4 = 1024 * 1024;
@@ -18387,7 +18196,7 @@ function packageVersion(executable) {
18387
18196
  let directory2 = dirname5(executable);
18388
18197
  for (let depth = 0; depth < 8; depth++, directory2 = dirname5(directory2)) {
18389
18198
  const path = join8(directory2, "package.json");
18390
- if (!existsSync5(path)) continue;
18199
+ if (!existsSync4(path)) continue;
18391
18200
  const value2 = JSON.parse(readFileSync4(path, "utf8"));
18392
18201
  if (value2.name?.startsWith("@openai/codex-")) return value2.version;
18393
18202
  }
@@ -18488,7 +18297,7 @@ function validateEvidence(owner) {
18488
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))
18489
18298
  throw new Error("The original native history storage changed");
18490
18299
  const path = join8(owner.home, "thread_history_1.sqlite");
18491
- if (existsSync5(path) && identity(path).path !== path)
18300
+ if (existsSync4(path) && identity(path).path !== path)
18492
18301
  throw new Error("Native history is outside the original home");
18493
18302
  }
18494
18303
  function nativeReceipt(owner, clientId) {
@@ -18505,7 +18314,7 @@ function nativeReceipt(owner, clientId) {
18505
18314
  queue.close();
18506
18315
  }
18507
18316
  const path = join8(owner.home, "thread_history_1.sqlite");
18508
- if (!existsSync5(path)) return { queueId };
18317
+ if (!existsSync4(path)) return { queueId };
18509
18318
  const history = new DatabaseSync3(path, { readOnly: true });
18510
18319
  try {
18511
18320
  const rows = history.prepare(
@@ -18560,7 +18369,7 @@ var Writer = class {
18560
18369
  tail = "";
18561
18370
  pending = /* @__PURE__ */ new Map();
18562
18371
  constructor(owner) {
18563
- this.process = spawn5(owner.executable, ["app-server", "--listen", "stdio://"], {
18372
+ this.process = spawn4(owner.executable, ["app-server", "--listen", "stdio://"], {
18564
18373
  cwd: owner.cwd,
18565
18374
  env: { ...process.env, CODEX_HOME: owner.home },
18566
18375
  stdio: ["pipe", "pipe", "ignore"]
@@ -18719,6 +18528,8 @@ var NativeQueue = class {
18719
18528
  // src/connector.ts
18720
18529
  var EPHEMERAL_TTL_MS = 6e4;
18721
18530
  var HOME_RECENT = 20;
18531
+ var FOLLOW_MS = 5 * 6e4;
18532
+ var RESCAN_MS = 1e3;
18722
18533
  var Command = z2.object({
18723
18534
  type: z2.literal("command"),
18724
18535
  requestId: z2.string().min(1),
@@ -18731,8 +18542,6 @@ var Command = z2.object({
18731
18542
  z2.object({ kind: z2.literal("thread.queue"), text: z2.string().min(1).max(1e5) }).strict(),
18732
18543
  z2.object({ kind: z2.literal("thread.stop") }).strict(),
18733
18544
  z2.object({ kind: z2.literal("approval.respond"), approvalId: z2.string(), choice: z2.string() }).strict(),
18734
- z2.object({ kind: z2.literal("screen.start"), sessionId: z2.string().min(1).max(128) }).strict(),
18735
- z2.object({ kind: z2.literal("screen.stop"), sessionId: z2.string().min(1).max(128) }).strict(),
18736
18545
  z2.object({
18737
18546
  kind: z2.literal("session.start"),
18738
18547
  harness: z2.enum(["claude", "codex", "opencode"]),
@@ -18758,31 +18567,13 @@ var Connector = class {
18758
18567
  autoConnect: false
18759
18568
  });
18760
18569
  provideClient(() => this.client);
18761
- this.viewer = new Viewer(
18762
- config2,
18763
- (pair) => this.isActive(pair.id),
18764
- (pair, content) => this.send(pair, content)
18765
- );
18766
- this.discovery = new Discovery(
18767
- config2.device.id,
18768
- {
18769
- read: true,
18770
- send: false,
18771
- queue: false,
18772
- stop: false,
18773
- approve: false,
18774
- screen: config2.screen && Viewer.available(),
18775
- control: config2.control && controlAvailable()
18776
- },
18777
- config2.localSecret
18778
- );
18570
+ this.discovery = new Discovery(config2.device.id, config2.localSecret);
18779
18571
  this.pairings = createLiveQuery(() => this.client.bind(Pairing).where({ device: config2.device.id }).whereNull("revokedAt").clearLimit());
18780
18572
  this.relays = createLiveQuery(() => this.client.bind(Relay).where({ recipient: config2.device.id }).orderBy("createdAt", "desc").orderBy("id", "desc").clearLimit());
18781
18573
  }
18782
18574
  config;
18783
18575
  client;
18784
18576
  discovery;
18785
- viewer;
18786
18577
  pairings;
18787
18578
  relays;
18788
18579
  nativeQueue;
@@ -18793,18 +18584,30 @@ var Connector = class {
18793
18584
  retry;
18794
18585
  stopWatching = () => {
18795
18586
  };
18796
- rescan = () => void this.queueChange().catch(() => {
18797
- });
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
+ };
18798
18598
  releases = [];
18799
18599
  announced = /* @__PURE__ */ new Set();
18800
18600
  listed = /* @__PURE__ */ new Map();
18801
18601
  listedProjects = /* @__PURE__ */ new Map();
18802
18602
  overviewQueued = false;
18603
+ pendingChange;
18803
18604
  // The agents that can start sessions here.
18804
18605
  startable = [];
18805
18606
  snapshots = /* @__PURE__ */ new Set();
18806
18607
  nativeSnapshots = /* @__PURE__ */ new Map();
18807
18608
  queueReceipts = /* @__PURE__ */ new Map();
18609
+ // The conversations phones are reading, by pairing and thread.
18610
+ follows = /* @__PURE__ */ new Map();
18808
18611
  // The content key behind each pairing code on show, until a phone pairs with it.
18809
18612
  invitations = /* @__PURE__ */ new Map();
18810
18613
  async start() {
@@ -18821,8 +18624,6 @@ var Connector = class {
18821
18624
  this.releases.push(this.harnesses.watch(this.rescan));
18822
18625
  this.releases.push(
18823
18626
  this.pairings.retain(() => {
18824
- if (this.pairings.snapshot().status !== "ready") this.viewer.close();
18825
- else this.viewer.revoke(new Set(this.activePairs().map((pair) => pair.id)));
18826
18627
  this.work = this.work.then(async () => {
18827
18628
  if (this.pairings.snapshot().status !== "ready") return;
18828
18629
  for (const pair of this.activePairs()) {
@@ -18902,7 +18703,6 @@ var Connector = class {
18902
18703
  this.client.send("dashup:presence", { device: this.config.device.id });
18903
18704
  });
18904
18705
  this.client.on("error", () => this.report(new Error("Connection interrupted. Dashup will reconnect.")));
18905
- this.client.on("disconnected", () => this.viewer.close());
18906
18706
  await this.client.reconnect();
18907
18707
  process.stdout.write(
18908
18708
  `Dashup is connected. ${this.discovery.projects.size} existing projects found.
@@ -18916,29 +18716,16 @@ var Connector = class {
18916
18716
  return invite;
18917
18717
  }
18918
18718
  stop() {
18719
+ this.stopped = true;
18919
18720
  this.stopWatching();
18920
18721
  for (const release of this.releases.splice(0)) release();
18921
18722
  clearTimeout(this.retry);
18723
+ clearTimeout(this.rescanning);
18922
18724
  this.client.dispose();
18923
- this.viewer.close();
18924
18725
  this.nativeQueue.stop();
18925
18726
  this.harnesses.stop();
18926
18727
  for (const agent of this.agents.values()) agent.socket.destroy();
18927
18728
  }
18928
- async permissions(screen, control) {
18929
- this.config.screen = screen;
18930
- this.config.control = screen && control;
18931
- this.discovery.capabilities = {
18932
- ...this.discovery.capabilities,
18933
- screen: screen && Viewer.available(),
18934
- control: this.config.control && controlAvailable()
18935
- };
18936
- const sessions = screen ? [] : [...this.viewer.sessions];
18937
- if (!screen) this.viewer.close();
18938
- saveConfig(this.config);
18939
- for (const [id, current] of sessions) await this.viewer.sendClose(id, current);
18940
- await this.queueChange();
18941
- }
18942
18729
  activePairs() {
18943
18730
  return this.pairings.snapshot().items;
18944
18731
  }
@@ -18967,7 +18754,7 @@ var Connector = class {
18967
18754
  );
18968
18755
  if (envelope.ciphertext.length > MAX_ENVELOPE_BYTES * 2)
18969
18756
  throw new Error(
18970
- "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."
18971
18758
  );
18972
18759
  this.config.outbox.push(envelope);
18973
18760
  saveConfig(this.config);
@@ -18980,7 +18767,7 @@ var Connector = class {
18980
18767
  saveConfig(this.config);
18981
18768
  }
18982
18769
  flush() {
18983
- const run5 = this.drain.catch(() => {
18770
+ const run4 = this.drain.catch(() => {
18984
18771
  }).then(async () => {
18985
18772
  this.discardLegacySnapshots();
18986
18773
  while (this.config.outbox.length && this.client.isConnected) {
@@ -18990,8 +18777,8 @@ var Connector = class {
18990
18777
  saveConfig(this.config);
18991
18778
  }
18992
18779
  });
18993
- this.drain = run5;
18994
- return run5.catch((error) => {
18780
+ this.drain = run4;
18781
+ return run4.catch((error) => {
18995
18782
  this.report(error);
18996
18783
  clearTimeout(this.retry);
18997
18784
  this.retry = setTimeout(() => {
@@ -19032,7 +18819,6 @@ var Connector = class {
19032
18819
  return {
19033
18820
  type: "projects",
19034
18821
  version,
19035
- screen: this.discovery.capabilities.screen,
19036
18822
  agents: this.startable,
19037
18823
  folders: this.discovery.folders(),
19038
18824
  projects: [...this.listedProjects.values()],
@@ -19062,12 +18848,20 @@ var Connector = class {
19062
18848
  for (const pair of this.activePairs()) await this.listProjects(pair);
19063
18849
  }).catch((error) => this.report(error));
19064
18850
  }
18851
+ // A change still waiting to start covers every event after it, so a burst of writes to a
18852
+ // transcript costs one rescan rather than one each.
19065
18853
  queueChange() {
19066
- const run5 = this.work.then(() => this.changed());
19067
- this.work = run5.catch((error) => this.report(error));
19068
- return run5;
18854
+ if (this.pendingChange) return this.pendingChange;
18855
+ const run4 = this.work.then(() => {
18856
+ this.pendingChange = void 0;
18857
+ return this.changed();
18858
+ });
18859
+ this.pendingChange = run4;
18860
+ this.work = run4.catch((error) => this.report(error));
18861
+ return run4;
19069
18862
  }
19070
18863
  async changed() {
18864
+ if (this.stopped) return;
19071
18865
  const previous = new Map(
19072
18866
  [...this.discovery.projects.values()].map((project) => [project.id, project])
19073
18867
  );
@@ -19102,9 +18896,19 @@ var Connector = class {
19102
18896
  const projects = [...this.discovery.projects.values()];
19103
18897
  this.stopWatching();
19104
18898
  this.stopWatching = this.discovery.watch(this.rescan);
18899
+ await this.stream();
19105
18900
  for (const project of projects) {
19106
- if (JSON.stringify(previous.get(project.id)) === JSON.stringify(project)) continue;
19107
- 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);
19108
18912
  }
19109
18913
  for (const { project, thread, session } of live) {
19110
18914
  const snapshot = this.threadState(project, thread);
@@ -19127,6 +18931,40 @@ var Connector = class {
19127
18931
  for (const pair of this.activePairs()) await this.listProjects(pair);
19128
18932
  await this.publishQueueReceipts();
19129
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
+ }
19130
18968
  // A saved session nothing else has open continues headless when its agent runs here. One a
19131
18969
  // native process owns takes queued messages instead.
19132
18970
  refreshResumes() {
@@ -19178,38 +19016,23 @@ var Connector = class {
19178
19016
  }
19179
19017
  async receive(envelope) {
19180
19018
  const pair = this.activePairs().find((item) => item.id === envelope.pairing);
19181
- 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")
19182
19020
  return;
19183
19021
  const key = this.config.pairs[pair.id];
19184
19022
  if (envelope.sequence <= key.received) return;
19185
19023
  const content = open(key.key, envelope);
19186
- if (content.type !== "command" && content.type !== "signal" || content.projectId !== envelope.project)
19024
+ if (content.type !== "command" || content.projectId !== envelope.project || content.requestId !== envelope.id)
19187
19025
  return;
19188
- if (content.type === "command" && content.requestId !== envelope.id) return;
19189
- if (content.type === "command") Command.parse(content);
19026
+ Command.parse(content);
19190
19027
  key.received = envelope.sequence;
19191
- if (content.type === "command")
19192
- this.config.unconfirmed[content.requestId] = {
19193
- pairing: pair.id,
19194
- project: content.projectId
19195
- };
19028
+ this.config.unconfirmed[content.requestId] = { pairing: pair.id, project: content.projectId };
19196
19029
  saveConfig(this.config);
19197
- if (content.type === "signal") {
19198
- this.viewer.signal(pair, content.projectId, content.sessionId, content.signal);
19199
- return;
19200
- }
19201
- if (content.action.kind === "screen.start") {
19202
- void this.execute(pair, content, envelope).catch((error) => this.report(error));
19203
- return;
19204
- }
19205
19030
  await this.execute(pair, content, envelope);
19206
19031
  }
19207
19032
  async execute(pair, content, envelope) {
19208
19033
  let queue = void 0;
19209
19034
  try {
19210
- if (["screen.start", "approval.respond", "thread.stop", "session.start"].includes(
19211
- content.action.kind
19212
- ) && 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)
19213
19036
  throw new Error("Command expired. Try again while this computer is online.");
19214
19037
  const outcome = await this.command(pair, content);
19215
19038
  delete this.config.unconfirmed[content.requestId];
@@ -19362,12 +19185,20 @@ var Connector = class {
19362
19185
  if (!thread) throw new Error("Thread not found");
19363
19186
  const messages5 = this.discovery.read(thread, this.attached(thread)?.messages);
19364
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
+ });
19365
19196
  return this.thread(pair, project, thread, history);
19366
19197
  }
19367
19198
  case "thread.send": {
19368
19199
  if (!thread || !thread.capabilities.send)
19369
19200
  throw new Error(
19370
- "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."
19371
19202
  );
19372
19203
  const location2 = this.discovery.locations.get(thread.id);
19373
19204
  if (location2?.live?.send) return location2.live.send(command2.requestId, command2.action.text);
@@ -19388,7 +19219,7 @@ var Connector = class {
19388
19219
  const agent = thread && this.attached(thread);
19389
19220
  if (!agent || agent.socket.destroyed)
19390
19221
  throw new Error(
19391
- "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."
19392
19223
  );
19393
19224
  return commandAcp(agent, command2.requestId, { kind: "cancel" });
19394
19225
  }
@@ -19422,7 +19253,8 @@ var Connector = class {
19422
19253
  return native.approve(command2.requestId, approval4.id, action.choice);
19423
19254
  }
19424
19255
  const agent = this.attached(thread);
19425
- 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.");
19426
19258
  const [request2, approval3] = [...agent.approvals].find(([, item]) => item.id === action.approvalId) ?? [];
19427
19259
  if (!approval3?.choices.some((choice) => choice.id === action.choice))
19428
19260
  throw new Error("Approval is no longer available");
@@ -19432,12 +19264,6 @@ var Connector = class {
19432
19264
  choice: action.choice
19433
19265
  });
19434
19266
  }
19435
- case "screen.start":
19436
- if (!this.config.screen) throw new Error("Enable screen access on this computer first");
19437
- return this.viewer.start(pair, project.id, command2.action.sessionId);
19438
- case "screen.stop":
19439
- this.viewer.stop(pair, command2.action.sessionId);
19440
- return;
19441
19267
  }
19442
19268
  }
19443
19269
  // Starts an agent in a project, or in a new folder inside one this computer offers. Commands run
@@ -19478,24 +19304,24 @@ var Connector = class {
19478
19304
  };
19479
19305
 
19480
19306
  // src/install.ts
19481
- import { existsSync as existsSync6 } from "node:fs";
19307
+ import { existsSync as existsSync5 } from "node:fs";
19482
19308
  import { homedir as homedir6, userInfo } from "node:os";
19483
- import { promisify as promisify7 } from "node:util";
19484
- import { fileURLToPath as fileURLToPath3 } from "node:url";
19485
- 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";
19486
19312
  import { cp, mkdir as mkdir2, rm, writeFile } from "node:fs/promises";
19487
19313
  import { dirname as dirname6, join as join10, relative, sep } from "node:path";
19488
- var run4 = promisify7(execFile7);
19314
+ var run3 = promisify6(execFile6);
19489
19315
  var xml = (value2) => value2.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
19490
19316
  var NODE_FLAGS = ["--disable-warning=ExperimentalWarning"];
19491
- var systemctl = (...args2) => run4("systemctl", ["--user", ...args2], {
19317
+ var systemctl = (...args2) => run3("systemctl", ["--user", ...args2], {
19492
19318
  env: { ...process.env, XDG_RUNTIME_DIR: process.env.XDG_RUNTIME_DIR ?? `/run/user/${userInfo().uid}` }
19493
19319
  });
19494
19320
  var serviceFile = () => process.platform === "darwin" ? join10(homedir6(), "Library/LaunchAgents/com.dashup.connector.plist") : process.platform === "win32" ? join10(
19495
19321
  process.env.APPDATA ?? join10(homedir6(), "AppData/Roaming"),
19496
19322
  "Microsoft/Windows/Start Menu/Programs/Startup/Dashup.vbs"
19497
19323
  ) : join10(homedir6(), ".config/systemd/user/dashup.service");
19498
- var startupInstalled = () => existsSync6(serviceFile());
19324
+ var startupInstalled = () => existsSync5(serviceFile());
19499
19325
  function service(platform, node, entry, config2) {
19500
19326
  const command2 = [node, ...NODE_FLAGS, entry];
19501
19327
  if (platform === "darwin")
@@ -19523,7 +19349,7 @@ WantedBy=default.target
19523
19349
  `;
19524
19350
  }
19525
19351
  async function durableEntry() {
19526
- const entry = fileURLToPath3(import.meta.url);
19352
+ const entry = fileURLToPath2(import.meta.url);
19527
19353
  if (!entry.endsWith(`${sep}dist${sep}index.js`))
19528
19354
  throw new Error(
19529
19355
  "Build the connector before installing startup: pnpm --filter @dashup/connector build"
@@ -19546,13 +19372,13 @@ async function installStartup() {
19546
19372
  service(process.platform, process.execPath, await durableEntry(), configPath())
19547
19373
  );
19548
19374
  if (process.platform === "darwin") {
19549
- await run4("launchctl", ["unload", path]).catch(() => {
19375
+ await run3("launchctl", ["unload", path]).catch(() => {
19550
19376
  });
19551
- await run4("launchctl", ["load", path]);
19377
+ await run3("launchctl", ["load", path]);
19552
19378
  } else if (process.platform === "win32") {
19553
- spawn6("wscript.exe", [path], { detached: true, stdio: "ignore" }).unref();
19379
+ spawn5("wscript.exe", [path], { detached: true, stdio: "ignore" }).unref();
19554
19380
  } else {
19555
- 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(
19556
19382
  () => void 0,
19557
19383
  () => `It stops when you log out. To keep it running, run sudo loginctl enable-linger ${userInfo().username}.`
19558
19384
  );
@@ -19564,7 +19390,7 @@ async function installStartup() {
19564
19390
  }
19565
19391
  async function uninstallStartup() {
19566
19392
  const path = serviceFile();
19567
- if (process.platform === "darwin") await run4("launchctl", ["unload", path]).catch(() => {
19393
+ if (process.platform === "darwin") await run3("launchctl", ["unload", path]).catch(() => {
19568
19394
  });
19569
19395
  else if (process.platform !== "win32")
19570
19396
  await systemctl("disable", "dashup.service").catch(() => {
@@ -19669,22 +19495,6 @@ async function main() {
19669
19495
  if (!response.ok) throw new Error("Could not stop Dashup");
19670
19496
  return;
19671
19497
  }
19672
- if (command === "permissions") {
19673
- config.screen = args.includes("--screen");
19674
- config.control = config.screen && args.includes("--control");
19675
- const remote = await running("/permissions", {
19676
- method: "POST",
19677
- body: JSON.stringify({ screen: config.screen, control: config.control }),
19678
- signal: AbortSignal.timeout(5e3)
19679
- }).catch(refused);
19680
- if (remote && !remote.ok) throw new Error("Could not change connector permissions");
19681
- if (!remote) saveConfig({ ...loadConfig(), screen: config.screen, control: config.control });
19682
- process.stdout.write(
19683
- `Screen ${config.screen ? "enabled" : "disabled"}, control ${config.control ? "enabled" : "disabled"}.
19684
- `
19685
- );
19686
- return;
19687
- }
19688
19498
  if (command === "acp") {
19689
19499
  const native = args[1] === "--" ? args.slice(2) : args.slice(1);
19690
19500
  const [executable, ...agentArgs] = native;
@@ -19693,18 +19503,7 @@ async function main() {
19693
19503
  return proxyAcp(config, executable, agentArgs);
19694
19504
  }
19695
19505
  if (command === "doctor" || command === "projects") {
19696
- const support = { screen: Viewer.available(), control: controlAvailable() };
19697
- const discovery = new Discovery(
19698
- config.device?.id ?? "local",
19699
- {
19700
- read: true,
19701
- send: false,
19702
- approve: false,
19703
- screen: config.screen && support.screen,
19704
- control: config.control && support.control
19705
- },
19706
- config.localSecret
19707
- );
19506
+ const discovery = new Discovery(config.device?.id ?? "local", config.localSecret);
19708
19507
  discovery.scan();
19709
19508
  const live = new LiveHarnesses();
19710
19509
  try {
@@ -19719,7 +19518,6 @@ async function main() {
19719
19518
  node: process.version,
19720
19519
  paired: Boolean(config.secret),
19721
19520
  platform: process.platform,
19722
- screen: support,
19723
19521
  projectCount: projects.length,
19724
19522
  harnesses: Object.fromEntries(
19725
19523
  Object.keys(HARNESSES).filter((harness) => harness !== "other").map((harness) => [
@@ -19753,7 +19551,7 @@ async function main() {
19753
19551
  }
19754
19552
  if (!["pair", "watch"].includes(command)) {
19755
19553
  process.stdout.write(
19756
- "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"
19757
19555
  );
19758
19556
  return;
19759
19557
  }
@@ -19826,7 +19624,7 @@ async function main() {
19826
19624
  response.end(JSON.stringify(connector.invite()));
19827
19625
  return;
19828
19626
  }
19829
- if (!["/attention", "/permissions"].includes(request2.url ?? "") || request2.method !== "POST") {
19627
+ if (request2.url !== "/attention" || request2.method !== "POST") {
19830
19628
  response.writeHead(404).end();
19831
19629
  return;
19832
19630
  }
@@ -19835,12 +19633,6 @@ async function main() {
19835
19633
  value2 += String(chunk);
19836
19634
  if (value2.length > 2e4) throw new Error("Alert too large");
19837
19635
  }
19838
- if (request2.url === "/permissions") {
19839
- const input2 = z3.object({ screen: z3.boolean(), control: z3.boolean() }).strict().parse(JSON.parse(value2));
19840
- await connector.permissions(input2.screen, input2.control);
19841
- response.end("{}");
19842
- return;
19843
- }
19844
19636
  const input = Attention.parse(JSON.parse(value2));
19845
19637
  await connector.attention(
19846
19638
  input.projectId,