@dashup/connector 0.1.7 → 0.1.8

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 (2) hide show
  1. package/dist/index.js +62 -22
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -4017,7 +4017,7 @@ import * as ui from "@clack/prompts";
4017
4017
  import { z } from "zod";
4018
4018
 
4019
4019
  // package.json
4020
- var version = "0.1.7";
4020
+ var version = "0.1.8";
4021
4021
 
4022
4022
  // src/pair.ts
4023
4023
  var Invitation = z.object({
@@ -4425,6 +4425,11 @@ import { join as join2, dirname as dirname2, resolve as resolve2, isAbsolute } f
4425
4425
  import { closeSync as closeSync2, existsSync as existsSync3, openSync as openSync2, readSync, realpathSync, readdirSync, statSync } from "node:fs";
4426
4426
 
4427
4427
  // src/harness/session.ts
4428
+ var EARLIER = {
4429
+ id: "earlier",
4430
+ role: "system",
4431
+ text: "Earlier messages stay on your computer."
4432
+ };
4428
4433
  var sessionKey = (session) => `${session.harness}\0${session.id}`;
4429
4434
  function agentHarness(name) {
4430
4435
  if (/claude/i.test(name)) return "claude";
@@ -4439,6 +4444,8 @@ var SCREEN_PREVIEW = "Open Screen to review the full request before approving.";
4439
4444
  var SAMPLE_BYTES = 128 * 1024;
4440
4445
  var READ_BYTES = 64 * 1024;
4441
4446
  var TITLE_LIMIT = 120;
4447
+ var LATEST_BYTES = 8 * 1024 * 1024;
4448
+ var JOURNALS = 16;
4442
4449
  var journals = /* @__PURE__ */ new Map();
4443
4450
  var metadata = /* @__PURE__ */ new Map();
4444
4451
  function object(value2) {
@@ -4505,33 +4512,42 @@ function sample(path, until) {
4505
4512
  metadata.set(path, { stamp, rows });
4506
4513
  return rows;
4507
4514
  }
4508
- function journal(path) {
4515
+ var windowed = (path, messages5) => journals.get(path)?.start ? [EARLIER, ...messages5] : messages5;
4516
+ function journal(path, latest = false) {
4509
4517
  const stamp = statSync(path);
4510
4518
  let saved = journals.get(path);
4511
4519
  if (saved && unchanged(saved.stamp, stamp)) {
4520
+ journals.delete(path);
4521
+ journals.set(path, saved);
4512
4522
  const tail2 = parse(saved.tail.toString("utf8"));
4513
4523
  return tail2 ? [...saved.rows, tail2] : saved.rows;
4514
4524
  }
4515
- if (!saved || saved.stamp.ino !== stamp.ino || stamp.size <= saved.offset)
4516
- saved = { stamp, offset: 0, tail: Buffer.alloc(0), rows: [] };
4525
+ if (!saved || saved.stamp.ino !== stamp.ino || stamp.size <= saved.offset || latest && stamp.size - saved.start > 2 * LATEST_BYTES) {
4526
+ const start = latest ? Math.max(0, stamp.size - LATEST_BYTES) : 0;
4527
+ saved = { stamp, start, offset: start, tail: Buffer.alloc(0), rows: [] };
4528
+ }
4517
4529
  while (saved.offset < stamp.size) {
4518
4530
  const chunk = readRange(path, saved.offset, Math.min(READ_BYTES, stamp.size - saved.offset));
4519
4531
  if (!chunk.length) break;
4532
+ const first = saved.offset === saved.start && saved.start > 0;
4520
4533
  saved.offset += chunk.length;
4521
4534
  const buffer = Buffer.concat([saved.tail, chunk]);
4522
4535
  const end = buffer.lastIndexOf(10);
4523
4536
  if (end < 0) {
4524
- saved.tail = buffer;
4537
+ saved.tail = first ? Buffer.alloc(0) : buffer;
4525
4538
  continue;
4526
4539
  }
4527
- for (const line of buffer.subarray(0, end).toString("utf8").split("\n")) {
4540
+ const lines = buffer.subarray(0, end).toString("utf8").split("\n");
4541
+ for (const line of first ? lines.slice(1) : lines) {
4528
4542
  const row = parse(line);
4529
4543
  if (row) saved.rows.push(row);
4530
4544
  }
4531
4545
  saved.tail = Buffer.from(buffer.subarray(end + 1));
4532
4546
  }
4533
4547
  saved.stamp = stamp;
4548
+ journals.delete(path);
4534
4549
  journals.set(path, saved);
4550
+ if (journals.size > JOURNALS) journals.delete(journals.keys().next().value);
4535
4551
  const tail = parse(saved.tail.toString("utf8"));
4536
4552
  return tail ? [...saved.rows, tail] : saved.rows;
4537
4553
  }
@@ -4659,7 +4675,7 @@ function claudeChange(change2) {
4659
4675
  ].join("\n");
4660
4676
  }
4661
4677
  function claudeMessages(path) {
4662
- const rows = journal(path);
4678
+ const rows = journal(path, true);
4663
4679
  const chain = /* @__PURE__ */ new Map();
4664
4680
  let leaf;
4665
4681
  for (const row of rows) {
@@ -4679,6 +4695,9 @@ function claudeMessages(path) {
4679
4695
  leaf = typeof row.parentUuid === "string" ? row.parentUuid : void 0;
4680
4696
  }
4681
4697
  const conversation = selected.length ? selected.reverse() : rows;
4698
+ return windowed(path, claudeConversation(conversation, rows));
4699
+ }
4700
+ function claudeConversation(conversation, rows) {
4682
4701
  const results = /* @__PURE__ */ new Map();
4683
4702
  for (const row of rows)
4684
4703
  for (const part of row.type === "user" ? list(row.message?.content) : [])
@@ -4759,7 +4778,7 @@ function applyPatch(patch) {
4759
4778
  ).replace(/^\*\*\* .*\n?/gm, "");
4760
4779
  }
4761
4780
  function codexMessages(path) {
4762
- const rows = journal(path);
4781
+ const rows = journal(path, true);
4763
4782
  const outputs = /* @__PURE__ */ new Map();
4764
4783
  for (const row of rows)
4765
4784
  if (["function_call_output", "custom_tool_call_output"].includes(row.payload?.type))
@@ -4788,8 +4807,8 @@ function codexMessages(path) {
4788
4807
  )
4789
4808
  ];
4790
4809
  });
4791
- if (result.length) return result;
4792
- return rows.flatMap((row, index) => {
4810
+ if (result.length) return windowed(path, result);
4811
+ return windowed(path, rows.flatMap((row, index) => {
4793
4812
  if (row.type !== "event_msg") return [];
4794
4813
  const item = object(row.payload);
4795
4814
  if (!item || !["user_message", "agent_message"].includes(item.type)) return [];
@@ -4798,7 +4817,7 @@ function codexMessages(path) {
4798
4817
  item.type === "user_message" ? "user" : "assistant",
4799
4818
  item.message
4800
4819
  );
4801
- });
4820
+ }));
4802
4821
  }
4803
4822
  function codex(root, scan) {
4804
4823
  const index = join2(root, "session_index.jsonl");
@@ -5141,11 +5160,15 @@ var Discovery = class {
5141
5160
  secret;
5142
5161
  projects = /* @__PURE__ */ new Map();
5143
5162
  locations = /* @__PURE__ */ new Map();
5163
+ // Each native agent session's thread, so finding it costs the same however many sessions a scan
5164
+ // sees. The first project to list a thread keeps it.
5165
+ agents = /* @__PURE__ */ new Map();
5144
5166
  paths = /* @__PURE__ */ new Set();
5145
5167
  warnings = [];
5146
5168
  scan() {
5147
5169
  this.projects.clear();
5148
5170
  this.locations.clear();
5171
+ this.agents.clear();
5149
5172
  this.paths.clear();
5150
5173
  this.warnings.length = 0;
5151
5174
  const defaults = dataPaths();
@@ -5160,12 +5183,8 @@ var Discovery = class {
5160
5183
  return [...this.projects.values()];
5161
5184
  }
5162
5185
  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
- }
5186
+ const known = this.agents.get(`${session.harness}\0${session.id}`);
5187
+ if (known) return known;
5169
5188
  const project = this.project(session.cwd);
5170
5189
  const thread = {
5171
5190
  id: agentThread(session.harness, session.id, this.secret),
@@ -5178,6 +5197,7 @@ var Discovery = class {
5178
5197
  };
5179
5198
  project.threads.push(thread);
5180
5199
  this.locations.set(thread.id, { editor: "cli", path: "" });
5200
+ this.agents.set(`${session.harness}\0${session.id}`, { project, thread });
5181
5201
  return { project, thread };
5182
5202
  }
5183
5203
  // The native history owns the conversation's last message time, whichever editor lists it.
@@ -5300,8 +5320,8 @@ var Discovery = class {
5300
5320
  ...ssh.port ? { port: Number(ssh.port) } : {}
5301
5321
  } : void 0;
5302
5322
  const project = this.project(folder, remote);
5303
- if (!project.threads.some((thread) => thread.id === id))
5304
- project.threads.push({
5323
+ if (!project.threads.some((thread) => thread.id === id)) {
5324
+ const thread = {
5305
5325
  id,
5306
5326
  title: row.title_override || row.title,
5307
5327
  editor: "zed",
@@ -5309,7 +5329,11 @@ var Discovery = class {
5309
5329
  updatedAt,
5310
5330
  capabilities: this.capabilities,
5311
5331
  ...harness ? { agent: { harness, sessionId: session } } : {}
5312
- });
5332
+ };
5333
+ project.threads.push(thread);
5334
+ if (harness && !this.agents.has(`${harness}\0${session}`))
5335
+ this.agents.set(`${harness}\0${session}`, { project, thread });
5336
+ }
5313
5337
  this.locations.set(id, { editor: "zed", path: store, session });
5314
5338
  }
5315
5339
  }
@@ -16385,6 +16409,7 @@ function sealHistory(key, routing, message, random) {
16385
16409
 
16386
16410
  // src/history.ts
16387
16411
  var HISTORY_PATH = "/dashup/history";
16412
+ var HISTORY_KEPT = 300;
16388
16413
  async function upload(config2, batch) {
16389
16414
  if (batch.records.length > HISTORY_BATCH_ITEMS || Buffer.byteLength(JSON.stringify(batch)) > HISTORY_BATCH_BYTES)
16390
16415
  throw new Error("The encrypted history upload exceeds the batch limit");
@@ -16400,7 +16425,14 @@ async function publishHistory(config2, pair, project, thread, messages5) {
16400
16425
  const generation = ++local.sequence;
16401
16426
  saveConfig(config2);
16402
16427
  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]));
16428
+ const named = (id) => opaque(["history-message", project, thread.id, id]);
16429
+ const all = splitHistoryMessages(messages5, named);
16430
+ let start = Math.max(0, all.length - HISTORY_KEPT);
16431
+ while (start < all.length && (all[start].part ?? 0) > 0) start++;
16432
+ const fragments = start ? [
16433
+ ...splitHistoryMessages([EARLIER], named),
16434
+ ...all.slice(start)
16435
+ ] : all;
16404
16436
  const summary = { generation, total: fragments.length, updatedAt: thread.updatedAt };
16405
16437
  const batch = {
16406
16438
  pairing: pair.id,
@@ -18800,6 +18832,7 @@ var Connector = class {
18800
18832
  listed = /* @__PURE__ */ new Map();
18801
18833
  listedProjects = /* @__PURE__ */ new Map();
18802
18834
  overviewQueued = false;
18835
+ pendingChange;
18803
18836
  // The agents that can start sessions here.
18804
18837
  startable = [];
18805
18838
  snapshots = /* @__PURE__ */ new Set();
@@ -19062,8 +19095,15 @@ var Connector = class {
19062
19095
  for (const pair of this.activePairs()) await this.listProjects(pair);
19063
19096
  }).catch((error) => this.report(error));
19064
19097
  }
19098
+ // A change still waiting to start covers every event after it, so a burst of writes to a
19099
+ // transcript costs one rescan rather than one each.
19065
19100
  queueChange() {
19066
- const run5 = this.work.then(() => this.changed());
19101
+ if (this.pendingChange) return this.pendingChange;
19102
+ const run5 = this.work.then(() => {
19103
+ this.pendingChange = void 0;
19104
+ return this.changed();
19105
+ });
19106
+ this.pendingChange = run5;
19067
19107
  this.work = run5.catch((error) => this.report(error));
19068
19108
  return run5;
19069
19109
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dashup/connector",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "dashup": "dist/index.js"
@@ -29,11 +29,11 @@
29
29
  "tsup": "^8.5.1",
30
30
  "tsx": "^4.22.4",
31
31
  "typescript": "^5.9.3",
32
- "@dashup/typescript-config": "0.0.0",
33
32
  "@dashup/models": "0.1.0",
33
+ "@dashup/protocol": "0.1.0",
34
+ "@dashup/typescript-config": "0.0.0",
34
35
  "@parcae/lynx": "0.1.0",
35
- "@parcae/sdk": "0.8.1",
36
- "@dashup/protocol": "0.1.0"
36
+ "@parcae/sdk": "0.8.1"
37
37
  },
38
38
  "scripts": {
39
39
  "dashup": "tsx src/index.ts",