@dashup/connector 0.1.17 → 0.1.19

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 -6
  2. package/dist/index.js +707 -205
  3. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -42,7 +42,7 @@ var require_XMLHttpRequest = __commonJS({
42
42
  "use strict";
43
43
  var fs = __require("fs");
44
44
  var Url = __require("url");
45
- var spawn7 = __require("child_process").spawn;
45
+ var spawn8 = __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 = spawn8(process.argv[0], ["-e", execString]);
342
342
  var statusText;
343
343
  while (fs.existsSync(syncFile)) {
344
344
  }
@@ -2746,7 +2746,7 @@ var HARNESSES = {
2746
2746
  opencode: "OpenCode",
2747
2747
  other: "Agent"
2748
2748
  };
2749
- var isStartHarness = (harness) => harness !== "other" && harness !== "kimi";
2749
+ var isStartHarness = (harness) => harness !== "other";
2750
2750
 
2751
2751
  // ../../node_modules/.pnpm/@noble+ciphers@2.4.0/node_modules/@noble/ciphers/utils.js
2752
2752
  function isBytes(a) {
@@ -3648,12 +3648,12 @@ function loadConfig() {
3648
3648
  localSecret: ""
3649
3649
  };
3650
3650
  }
3651
- function saveConfig(config2) {
3651
+ function saveConfig(config3) {
3652
3652
  const path = configPath();
3653
3653
  mkdirSync(dirname(path), { recursive: true, mode: 448 });
3654
3654
  const file = openSync(path + ".tmp", "w", 384);
3655
3655
  try {
3656
- writeFileSync(file, JSON.stringify(config2, null, 2));
3656
+ writeFileSync(file, JSON.stringify(config3, null, 2));
3657
3657
  fsyncSync(file);
3658
3658
  } finally {
3659
3659
  closeSync(file);
@@ -3668,13 +3668,13 @@ function saveConfig(config2) {
3668
3668
  }
3669
3669
  }
3670
3670
  }
3671
- async function api(config2, path, body2, method = body2 ? "POST" : "GET") {
3671
+ async function api(config3, path, body2, method = body2 ? "POST" : "GET") {
3672
3672
  const bytes = body2 instanceof Uint8Array;
3673
- const response = await fetch(`${config2.api}/v1${path}`, {
3673
+ const response = await fetch(`${config3.api}/v1${path}`, {
3674
3674
  method,
3675
3675
  headers: {
3676
3676
  "Content-Type": bytes ? "application/octet-stream" : "application/json",
3677
- Authorization: `Bearer ${config2.secret}`
3677
+ Authorization: `Bearer ${config3.secret}`
3678
3678
  },
3679
3679
  body: bytes ? new Uint8Array(body2) : body2 ? JSON.stringify(body2) : void 0
3680
3680
  });
@@ -3683,8 +3683,8 @@ async function api(config2, path, body2, method = body2 ? "POST" : "GET") {
3683
3683
  throw new Error(value2.error ?? `Dashup request failed (${response.status})`);
3684
3684
  return value2.result;
3685
3685
  }
3686
- function activePair(config2, pair) {
3687
- return Boolean(!pair.revokedAt && config2.pairs[pair.id] && pair.device === config2.device?.id);
3686
+ function activePair(config3, pair) {
3687
+ return Boolean(!pair.revokedAt && config3.pairs[pair.id] && pair.device === config3.device?.id);
3688
3688
  }
3689
3689
 
3690
3690
  // src/acp.ts
@@ -3868,15 +3868,15 @@ function attachAcp(socket, onEvent) {
3868
3868
  }
3869
3869
  });
3870
3870
  }
3871
- async function proxyAcp(config2, executable, args2) {
3872
- if (!config2.localSecret) throw new Error("Start dashup watch before using the ACP proxy");
3871
+ async function proxyAcp(config3, executable, args2) {
3872
+ if (!config3.localSecret) throw new Error("Start dashup watch before using the ACP proxy");
3873
3873
  const socket = await new Promise((resolve3, reject) => {
3874
3874
  const connect3 = request({
3875
3875
  hostname: "127.0.0.1",
3876
3876
  port: connectorPort(),
3877
3877
  method: "CONNECT",
3878
3878
  path: "/acp",
3879
- headers: { Authorization: `Bearer ${config2.localSecret}` }
3879
+ headers: { Authorization: `Bearer ${config3.localSecret}` }
3880
3880
  });
3881
3881
  connect3.on("connect", (response, stream) => response.statusCode === 200 ? resolve3(stream) : reject(new Error("Dashup ACP connection was rejected")));
3882
3882
  connect3.on("error", reject);
@@ -4048,7 +4048,7 @@ import * as ui from "@clack/prompts";
4048
4048
  import { z } from "zod";
4049
4049
 
4050
4050
  // package.json
4051
- var version = "0.1.17";
4051
+ var version = "0.1.19";
4052
4052
 
4053
4053
  // src/pair.ts
4054
4054
  var Invitation = z.object({
@@ -4058,11 +4058,11 @@ var Invitation = z.object({
4058
4058
  }).strict();
4059
4059
  var interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
4060
4060
  var intro2 = () => ui.intro(`Dashup ${version}`);
4061
- function invitation(config2, secret = config2.secret) {
4061
+ function invitation(config3, secret = config3.secret) {
4062
4062
  const code = randomBytes2(8).toString("hex");
4063
4063
  const key = newSecret(randomBytes2);
4064
4064
  const computer = createHash2("sha256").update(secret).digest("hex");
4065
- return { code, key, text: formatPairingCode({ api: config2.api, computer, code, key }) };
4065
+ return { code, key, text: formatPairingCode({ api: config3.api, computer, code, key }) };
4066
4066
  }
4067
4067
  var SCAN = "Scan this in the Dashup app under Pair a computer. It expires in 5 minutes.";
4068
4068
  var BROWSER = {
@@ -4099,7 +4099,7 @@ async function show(text3) {
4099
4099
  `);
4100
4100
  return () => server.close();
4101
4101
  }
4102
- async function scanned(config2, { code, text: text3 }, done, secret = config2.secret) {
4102
+ async function scanned(config3, { code, text: text3 }, done, secret = config3.secret) {
4103
4103
  if (interactive) {
4104
4104
  const qr = await QRCode.toString(text3, {
4105
4105
  type: "terminal",
@@ -4118,7 +4118,7 @@ async function scanned(config2, { code, text: text3 }, done, secret = config2.se
4118
4118
  try {
4119
4119
  for (const deadline = Date.now() + PAIRING_TTL_MS; Date.now() < deadline; ) {
4120
4120
  await new Promise((resolve3) => setTimeout(resolve3, 1e3));
4121
- const response = await fetch(`${config2.api}/v1/dashup/pairings?code=${code}`, {
4121
+ const response = await fetch(`${config3.api}/v1/dashup/pairings?code=${code}`, {
4122
4122
  headers: { Authorization: `Bearer ${secret}` }
4123
4123
  });
4124
4124
  if (response.status === 401 || response.status === 404) continue;
@@ -4140,16 +4140,16 @@ async function scanned(config2, { code, text: text3 }, done, secret = config2.se
4140
4140
  }
4141
4141
  }
4142
4142
  var reported = (error) => error?.reported === true;
4143
- async function link(config2, account) {
4143
+ async function link(config3, account) {
4144
4144
  const secret = newSecret(randomBytes2);
4145
- const invite = invitation(config2, secret);
4145
+ const invite = invitation(config3, secret);
4146
4146
  const { pairing, email } = await scanned(
4147
- config2,
4147
+ config3,
4148
4148
  invite,
4149
4149
  (account2) => `Scanned by ${account2}`,
4150
4150
  secret
4151
4151
  );
4152
- const linked = { ...config2, secret };
4152
+ const linked = { ...config3, secret };
4153
4153
  const platform = process.platform === "win32" ? "windows" : process.platform === "darwin" ? "macos" : "linux";
4154
4154
  const { device } = await api(linked, "/dashup/devices", {
4155
4155
  id: pairing.device,
@@ -4166,7 +4166,7 @@ async function link(config2, account) {
4166
4166
  );
4167
4167
  return false;
4168
4168
  }
4169
- Object.assign(config2, {
4169
+ Object.assign(config3, {
4170
4170
  secret,
4171
4171
  device,
4172
4172
  pairs: { [pairing.id]: { key: invite.key, sequence: 0, received: 0 } },
@@ -4174,7 +4174,7 @@ async function link(config2, account) {
4174
4174
  queued: {},
4175
4175
  unconfirmed: {}
4176
4176
  });
4177
- saveConfig(config2);
4177
+ saveConfig(config3);
4178
4178
  return true;
4179
4179
  }
4180
4180
 
@@ -4188,6 +4188,7 @@ import { watch, existsSync as existsSync3, readdirSync as readdirSync2, readFile
4188
4188
 
4189
4189
  // src/harness/history.ts
4190
4190
  import { homedir as homedir2 } from "node:os";
4191
+ import { createHash as createHash3 } from "node:crypto";
4191
4192
  import { DatabaseSync } from "node:sqlite";
4192
4193
  import { join as join2, dirname as dirname2, resolve as resolve2, isAbsolute } from "node:path";
4193
4194
  import { closeSync as closeSync2, existsSync as existsSync2, openSync as openSync2, readSync, realpathSync, readdirSync, statSync } from "node:fs";
@@ -4435,7 +4436,7 @@ function attachments(id, content) {
4435
4436
  const source = object(part?.source);
4436
4437
  if (source?.type === "base64" && typeof source.data === "string")
4437
4438
  return [attached(key, String(source.media_type), source.data, name)];
4438
- const url2 = String(part?.image_url ?? part?.url ?? "");
4439
+ const url2 = String(object(part?.image_url)?.url ?? part?.image_url ?? part?.url ?? "");
4439
4440
  const data = DATA_URL.exec(url2);
4440
4441
  return data ? [attached(key, data[1], url2.slice(data[0].length), name)] : [];
4441
4442
  });
@@ -4782,6 +4783,89 @@ function codex(root, scan) {
4782
4783
  }
4783
4784
  }
4784
4785
  }
4786
+ var kimiHome = () => process.env.DASHUP_KIMI_HOME ?? join2(homedir2(), ".kimi");
4787
+ var KIMI_FAILED = /^<system>ERROR/;
4788
+ function kimiMessages(directory2) {
4789
+ const path = join2(directory2, "context.jsonl");
4790
+ const rows = journal(path, true);
4791
+ const results = /* @__PURE__ */ new Map();
4792
+ for (const row of rows) if (row.role === "tool") results.set(row.tool_call_id, row);
4793
+ const messages5 = rows.flatMap((row, index) => {
4794
+ const id = String(index);
4795
+ if (row.role === "user") return injected(row.content) ? [] : prose(id, "user", row.content);
4796
+ if (row.role !== "assistant") return [];
4797
+ return [
4798
+ ...prose(id, "assistant", row.content),
4799
+ ...list(row.tool_calls).map((call, position) => {
4800
+ const result = results.get(call?.id);
4801
+ const output = result && text(result.content);
4802
+ return step(
4803
+ String(call?.id ?? `${id}:${position}`),
4804
+ call?.function?.name,
4805
+ parse(String(call?.function?.arguments ?? "")) ?? call?.function?.arguments,
4806
+ result && {
4807
+ text: output,
4808
+ status: KIMI_FAILED.test(output) ? "error" : "ok",
4809
+ attachments: attachments(String(call?.id), result.content)
4810
+ }
4811
+ );
4812
+ })
4813
+ ];
4814
+ });
4815
+ const shown = windowed(path, messages5);
4816
+ return existsSync2(join2(directory2, "context_1.jsonl")) && shown[0] !== EARLIER ? [EARLIER, ...shown] : shown;
4817
+ }
4818
+ function kimiSettings(directory2) {
4819
+ const state = existsSync2(join2(directory2, "state.json")) ? jsonFile(join2(directory2, "state.json")) : void 0;
4820
+ return typeof state?.plan_mode === "boolean" ? { plan: state.plan_mode } : {};
4821
+ }
4822
+ function kimi(root, scan) {
4823
+ const listed = existsSync2(join2(root, "kimi.json")) ? jsonFile(join2(root, "kimi.json")) : void 0;
4824
+ if (listed) scan.paths.add(root);
4825
+ for (const entry of list(listed?.work_dirs)) {
4826
+ const cwd = canonical(entry?.path);
4827
+ if (!cwd) continue;
4828
+ const folder = join2(root, "sessions", createHash3("md5").update(entry.path).digest("hex"));
4829
+ if (!existsSync2(folder)) continue;
4830
+ scan.paths.add(folder);
4831
+ for (const id of readdirSync(folder)) {
4832
+ const directory2 = join2(folder, id);
4833
+ const context = join2(directory2, "context.jsonl");
4834
+ const wire = join2(directory2, "wire.jsonl");
4835
+ if (!existsSync2(context)) continue;
4836
+ scan.paths.add(directory2);
4837
+ try {
4838
+ const state = existsSync2(join2(directory2, "state.json")) ? jsonFile(join2(directory2, "state.json")) : void 0;
4839
+ if (state?.archived === true) continue;
4840
+ const asked = described(
4841
+ context,
4842
+ (row) => row.role === "user",
4843
+ (rows) => text(rows.find((row) => row.role === "user" && !injected(row.content))?.content)
4844
+ );
4845
+ const updated = existsSync2(wire) ? described(
4846
+ wire,
4847
+ (row) => typeof row.timestamp === "number",
4848
+ (rows) => [...rows].reverse().find((row) => typeof row.timestamp === "number")?.timestamp
4849
+ ) : void 0;
4850
+ scan.sessions.push({
4851
+ id,
4852
+ cwd,
4853
+ harness: "kimi",
4854
+ title: title(state?.custom_title || asked, "Kimi thread"),
4855
+ updatedAt: date(
4856
+ typeof updated === "number" ? updated * 1e3 : void 0,
4857
+ statSync(context).mtimeMs
4858
+ ),
4859
+ files: [context, ...existsSync2(wire) ? [wire] : []],
4860
+ read: () => kimiMessages(directory2),
4861
+ settings: () => kimiSettings(directory2)
4862
+ });
4863
+ } catch {
4864
+ scan.warnings.push(`Cannot read Kimi session: ${directory2}`);
4865
+ }
4866
+ }
4867
+ }
4868
+ }
4785
4869
  function openCodeChange(state) {
4786
4870
  const metadata2 = object(state.metadata);
4787
4871
  const input = object(state.input);
@@ -4847,8 +4931,8 @@ function databaseMessages(path, session) {
4847
4931
  }
4848
4932
  }
4849
4933
  function codexSubagent(row) {
4850
- const spawn7 = object(object(parse(String(row.source ?? "")))?.subagent)?.thread_spawn;
4851
- const task = typeof spawn7?.agent_path === "string" ? spawn7.agent_path.split("/").pop() : "";
4934
+ const spawn8 = object(object(parse(String(row.source ?? "")))?.subagent)?.thread_spawn;
4935
+ const task = typeof spawn8?.agent_path === "string" ? spawn8.agent_path.split("/").pop() : "";
4852
4936
  return [row.agent_nickname, task?.replace(/_/g, " ")].filter(Boolean).join(" \xB7 ");
4853
4937
  }
4854
4938
  var codexState;
@@ -4975,6 +5059,7 @@ function scanHistories() {
4975
5059
  const scan = { sessions: [], paths: /* @__PURE__ */ new Set(), warnings: [] };
4976
5060
  claude(claudeHome(), scan);
4977
5061
  codex(process.env.CODEX_HOME ?? join2(home, ".codex"), scan);
5062
+ kimi(kimiHome(), scan);
4978
5063
  const opencode = join2(process.env.XDG_DATA_HOME ?? join2(home, ".local/share"), "opencode");
4979
5064
  openCodeDatabase(process.env.OPENCODE_DB ?? join2(opencode, "opencode.db"), scan);
4980
5065
  openCodeLegacy(join2(opencode, "storage"), scan);
@@ -5019,6 +5104,7 @@ function vscodeHost(authority) {
5019
5104
  return host;
5020
5105
  }
5021
5106
  }
5107
+ var zedData = () => process.env.DASHUP_ZED_DATA ?? dataPaths().zed;
5022
5108
  function dataPaths() {
5023
5109
  const home = homedir3();
5024
5110
  if (process.platform === "darwin")
@@ -5112,7 +5198,7 @@ var Discovery = class {
5112
5198
  this.paths.clear();
5113
5199
  this.warnings.length = 0;
5114
5200
  const defaults = dataPaths();
5115
- this.zed(process.env.DASHUP_ZED_DATA ?? defaults.zed);
5201
+ this.zed(zedData());
5116
5202
  this.vscode(process.env.DASHUP_VSCODE_DATA ?? defaults.code);
5117
5203
  const history = scanHistories();
5118
5204
  for (const path of history.paths) this.paths.add(path);
@@ -5391,7 +5477,7 @@ var Discovery = class {
5391
5477
 
5392
5478
  // src/connector.ts
5393
5479
  import { z as z2 } from "zod";
5394
- import { join as join9 } from "node:path";
5480
+ import { join as join10 } from "node:path";
5395
5481
  import { mkdir as mkdir2 } from "node:fs/promises";
5396
5482
  import { randomBytes as randomBytes4 } from "node:crypto";
5397
5483
 
@@ -15427,16 +15513,16 @@ var SocketTransport = class extends import_index2.default {
15427
15513
  helloReady = Promise.resolve();
15428
15514
  /** The token the server validated at the last resolved hello. */
15429
15515
  confirmedHelloToken = null;
15430
- constructor(config2) {
15516
+ constructor(config3) {
15431
15517
  super();
15432
- this.url = config2.url;
15433
- this.version = config2.version ?? "v1";
15434
- this.getToken = config2.getToken;
15435
- this.handshakeTimeout = config2.handshakeTimeout ?? DEFAULT_TIMEOUT;
15436
- const socketPath = config2.path ?? "/ws";
15437
- const transports2 = config2.transports ?? ["websocket"];
15438
- const extraHeaders = config2.extraHeaders;
15439
- const autoConnect = config2.autoConnect ?? true;
15518
+ this.url = config3.url;
15519
+ this.version = config3.version ?? "v1";
15520
+ this.getToken = config3.getToken;
15521
+ this.handshakeTimeout = config3.handshakeTimeout ?? DEFAULT_TIMEOUT;
15522
+ const socketPath = config3.path ?? "/ws";
15523
+ const transports2 = config3.transports ?? ["websocket"];
15524
+ const extraHeaders = config3.extraHeaders;
15525
+ const autoConnect = config3.autoConnect ?? true;
15440
15526
  this.socket = lookup(this.url, {
15441
15527
  path: socketPath,
15442
15528
  transports: transports2,
@@ -15870,14 +15956,14 @@ var SocketTransport = class extends import_index2.default {
15870
15956
  this.connection.disconnected();
15871
15957
  }
15872
15958
  };
15873
- function createClient(config2) {
15959
+ function createClient(config3) {
15874
15960
  const transport = new SocketTransport({
15875
- url: config2.url,
15876
- version: config2.version ?? "v1",
15877
- getToken: config2.getToken,
15878
- transports: config2.transports,
15879
- extraHeaders: config2.extraHeaders,
15880
- autoConnect: config2.autoConnect
15961
+ url: config3.url,
15962
+ version: config3.version ?? "v1",
15963
+ getToken: config3.getToken,
15964
+ transports: config3.transports,
15965
+ extraHeaders: config3.extraHeaders,
15966
+ autoConnect: config3.autoConnect
15881
15967
  });
15882
15968
  const adapter = new FrontendAdapter(transport);
15883
15969
  const client = {
@@ -16392,20 +16478,20 @@ function changes(copy2, messages5) {
16392
16478
  return true;
16393
16479
  });
16394
16480
  }
16395
- async function upload(config2, batch) {
16481
+ async function upload(config3, batch) {
16396
16482
  if (batch.records.length > HISTORY_BATCH_ITEMS || Buffer.byteLength(JSON.stringify(batch)) > HISTORY_BATCH_BYTES)
16397
16483
  throw new Error("The encrypted history upload exceeds the batch limit");
16398
16484
  try {
16399
- return await api(config2, HISTORY_PATH, batch);
16485
+ return await api(config3, HISTORY_PATH, batch);
16400
16486
  } catch {
16401
- return api(config2, HISTORY_PATH, batch);
16487
+ return api(config3, HISTORY_PATH, batch);
16402
16488
  }
16403
16489
  }
16404
- async function publishHistory(config2, pair, project, thread, messages5) {
16405
- if (!activePair(config2, pair)) throw new Error("Pairing is no longer available");
16406
- const local = config2.pairs[pair.id];
16490
+ async function publishHistory(config3, pair, project, thread, messages5) {
16491
+ if (!activePair(config3, pair)) throw new Error("Pairing is no longer available");
16492
+ const local = config3.pairs[pair.id];
16407
16493
  const generation = ++local.sequence;
16408
- saveConfig(config2);
16494
+ saveConfig(config3);
16409
16495
  const all = historyMessages(local.key, project, thread.id, messages5);
16410
16496
  let start2 = Math.max(0, all.length - HISTORY_KEPT);
16411
16497
  while (start2 < all.length && (all[start2].part ?? 0) > 0) start2++;
@@ -16439,15 +16525,15 @@ async function publishHistory(config2, pair, project, thread, messages5) {
16439
16525
  );
16440
16526
  const size = Buffer.byteLength(JSON.stringify(record));
16441
16527
  if (batch.records.length && (batch.records.length === HISTORY_BATCH_ITEMS || bytes + size + 1 > HISTORY_BATCH_BYTES)) {
16442
- await upload(config2, batch);
16528
+ await upload(config3, batch);
16443
16529
  batch.records = [];
16444
16530
  bytes = overhead;
16445
16531
  }
16446
16532
  bytes += size + (batch.records.length ? 1 : 0);
16447
16533
  batch.records.push(record);
16448
16534
  }
16449
- if (batch.records.length) await upload(config2, batch);
16450
- const published = await upload(config2, { ...batch, records: [], complete: true });
16535
+ if (batch.records.length) await upload(config3, batch);
16536
+ const published = await upload(config3, { ...batch, records: [], complete: true });
16451
16537
  if (published.generation !== generation || published.total !== summary.total)
16452
16538
  throw new Error("The encrypted history snapshot was not published");
16453
16539
  return summary;
@@ -16484,9 +16570,9 @@ var named = (key, project, thread, attachment) => ({
16484
16570
  ...attachment.name ? { name: attachment.name } : {}
16485
16571
  });
16486
16572
  var readable = (attachment) => "read" in attachment;
16487
- async function shareAttachment(config2, pair, project, thread, messages5, id) {
16488
- if (!activePair(config2, pair)) throw new Error("Pairing is no longer available");
16489
- const key = config2.pairs[pair.id].key;
16573
+ async function shareAttachment(config3, pair, project, thread, messages5, id) {
16574
+ if (!activePair(config3, pair)) throw new Error("Pairing is no longer available");
16575
+ const key = config3.pairs[pair.id].key;
16490
16576
  const found = messages5.flatMap((message) => {
16491
16577
  const { full, attachments: attachments2 = [] } = kept(message);
16492
16578
  return full ? [full, ...attachments2] : attachments2;
@@ -16495,12 +16581,12 @@ async function shareAttachment(config2, pair, project, thread, messages5, id) {
16495
16581
  if (found.bytes > MAX_ATTACHMENT_BYTES)
16496
16582
  throw new Error("This attachment is too large to open on your phone");
16497
16583
  const sealed = sealAttachment(key, pair.id, id, found.read(), randomBytes3);
16498
- await api(config2, `${ATTACHMENTS_PATH}/${pair.id}/${id}`, sealed);
16584
+ await api(config3, `${ATTACHMENTS_PATH}/${pair.id}/${id}`, sealed);
16499
16585
  }
16500
16586
 
16501
16587
  // src/harness/live.ts
16502
- import { promisify as promisify5 } from "node:util";
16503
- import { execFile as execFile5 } from "node:child_process";
16588
+ import { promisify as promisify6 } from "node:util";
16589
+ import { execFile as execFile6 } from "node:child_process";
16504
16590
 
16505
16591
  // src/harness/headless.ts
16506
16592
  import { randomUUID as randomUUID3 } from "node:crypto";
@@ -17595,16 +17681,362 @@ var Codex = class {
17595
17681
  }
17596
17682
  };
17597
17683
 
17684
+ // src/harness/kimi.ts
17685
+ import { join as join6 } from "node:path";
17686
+ import { promisify as promisify3 } from "node:util";
17687
+ import { randomUUID as randomUUID5 } from "node:crypto";
17688
+ import { createInterface as createInterface4 } from "node:readline";
17689
+ import { existsSync as existsSync4, readdirSync as readdirSync3, readFileSync as readFileSync5, statSync as statSync3 } from "node:fs";
17690
+ import { execFile as execFile3, spawn as spawn4 } from "node:child_process";
17691
+ var run = promisify3(execFile3);
17692
+ var MAX_REQUESTS3 = 1e3;
17693
+ var NOTIFY_MS2 = 100;
17694
+ var CHOICES = [
17695
+ { id: "approve", label: "Allow once" },
17696
+ { id: "approve_for_session", label: "Allow for this session" },
17697
+ { id: "reject", label: "Reject", text: true }
17698
+ ];
17699
+ async function kimiCommand() {
17700
+ const shell = process.platform === "win32";
17701
+ if (await run("kimi", ["--version"], { timeout: 1e4, shell }).then(
17702
+ () => true,
17703
+ () => false
17704
+ ))
17705
+ return "kimi";
17706
+ const registry2 = join6(zedData(), "external_agents/registry/kimi");
17707
+ return (existsSync4(registry2) ? readdirSync3(registry2) : []).map((version2) => join6(registry2, version2, shell ? "kimi.exe" : "kimi")).filter((path) => existsSync4(path)).sort((left, right) => statSync3(right).mtimeMs - statSync3(left).mtimeMs)[0];
17708
+ }
17709
+ function config() {
17710
+ let text3 = "";
17711
+ try {
17712
+ text3 = readFileSync5(join6(kimiHome(), "config.toml"), "utf8");
17713
+ } catch {
17714
+ }
17715
+ const found = { models: [] };
17716
+ let section = "";
17717
+ let model;
17718
+ for (const line of text3.split("\n")) {
17719
+ const header = /^\s*\[(.+)\]\s*$/.exec(line)?.[1];
17720
+ if (header !== void 0) {
17721
+ section = header;
17722
+ const named2 = /^models\."(.+)"$/.exec(header)?.[1];
17723
+ model = named2 ? { value: named2, name: named2, thinking: false } : void 0;
17724
+ if (model) found.models.push(model);
17725
+ continue;
17726
+ }
17727
+ const [, key, value2 = ""] = /^\s*(\w+)\s*=\s*(.+?)\s*$/.exec(line) ?? [];
17728
+ if (model && key === "display_name") model.name = JSON.parse(value2);
17729
+ else if (model && key === "capabilities") model.thinking = value2.includes('"thinking"');
17730
+ else if (!section && key === "default_model") found.model = JSON.parse(value2);
17731
+ else if (!section && key === "default_thinking") found.thinking = value2 === "true";
17732
+ }
17733
+ return found;
17734
+ }
17735
+ function kimiOptions(recorded) {
17736
+ const { models: models2 } = config();
17737
+ const model = typeof recorded.model === "string" ? recorded.model : void 0;
17738
+ const current = models2.find((item) => item.value === model);
17739
+ const choices = models2.map(({ value: value2, name }) => ({ value: value2, name }));
17740
+ if (model && !current) choices.push({ value: model, name: model });
17741
+ return [
17742
+ ...model ? [{ id: "model", name: "Model", value: model, choices }] : [],
17743
+ ...model && current?.thinking !== false && typeof recorded.thinking === "boolean" ? [{ id: "thinking", name: "Thinking", value: recorded.thinking }] : [],
17744
+ ...typeof recorded.plan === "boolean" ? [{ id: "plan", name: "Plan", value: recorded.plan }] : []
17745
+ ];
17746
+ }
17747
+ var Session2 = class {
17748
+ constructor(id, cwd, title2, changed) {
17749
+ this.id = id;
17750
+ this.cwd = cwd;
17751
+ this.title = title2;
17752
+ this.changed = changed;
17753
+ }
17754
+ id;
17755
+ cwd;
17756
+ title;
17757
+ changed;
17758
+ messages = [];
17759
+ status = "idle";
17760
+ updatedAt = (/* @__PURE__ */ new Date()).toISOString();
17761
+ turn;
17762
+ sequence = 0;
17763
+ // The reply Kimi is writing, as far as it has streamed.
17764
+ typing = "";
17765
+ begun;
17766
+ calls = /* @__PURE__ */ new Map();
17767
+ approvals = /* @__PURE__ */ new Map();
17768
+ requests = /* @__PURE__ */ new Map();
17769
+ chosen = {};
17770
+ write(value2) {
17771
+ this.turn?.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", ...value2 })}
17772
+ `);
17773
+ }
17774
+ call(method, params) {
17775
+ const id = `dashup-${++this.sequence}`;
17776
+ return new Promise((resolve3, reject) => {
17777
+ this.calls.set(id, { resolve: resolve3, reject });
17778
+ this.write({ id, method, ...params ? { params } : {} });
17779
+ });
17780
+ }
17781
+ once(id, action) {
17782
+ const known = this.requests.get(id);
17783
+ if (known) return known;
17784
+ if (this.requests.size >= MAX_REQUESTS3)
17785
+ throw new Error("Kimi command history is full; restart Dashup before sending");
17786
+ const result = action();
17787
+ this.requests.set(id, result);
17788
+ return result;
17789
+ }
17790
+ // What the session starts with: the person's choice, else Kimi's defaults.
17791
+ launch() {
17792
+ const defaults = config();
17793
+ const model = this.chosen.model ?? defaults.model;
17794
+ const thinking = this.chosen.thinking ?? defaults.thinking;
17795
+ return {
17796
+ ...model === void 0 ? {} : { model },
17797
+ ...thinking === void 0 ? {} : { thinking }
17798
+ };
17799
+ }
17800
+ // Resolves once Kimi begins the turn, or says why it could not. A message while a turn runs is
17801
+ // steered into it.
17802
+ async say(text3) {
17803
+ this.messages.push({ id: randomUUID5(), role: "user", text: text3 });
17804
+ this.typing = "";
17805
+ this.status = "working";
17806
+ this.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
17807
+ if (this.turn) {
17808
+ await this.call("steer", { user_input: text3 });
17809
+ this.changed();
17810
+ return;
17811
+ }
17812
+ const command2 = await kimiCommand();
17813
+ if (!command2) throw new Error("Kimi is not installed on this computer");
17814
+ const { model, thinking } = this.launch();
17815
+ const turn = this.turn = spawn4(
17816
+ command2,
17817
+ [
17818
+ "--wire",
17819
+ "--work-dir",
17820
+ this.cwd,
17821
+ "--session",
17822
+ this.id,
17823
+ ...typeof model === "string" ? ["--model", model] : [],
17824
+ ...typeof thinking === "boolean" ? [thinking ? "--thinking" : "--no-thinking"] : []
17825
+ ],
17826
+ { cwd: this.cwd, stdio: "pipe", shell: process.platform === "win32" }
17827
+ );
17828
+ let errors = "";
17829
+ turn.stdin.on("error", () => {
17830
+ });
17831
+ turn.stderr.on("data", (chunk) => {
17832
+ errors = (errors + String(chunk)).slice(-2e3);
17833
+ });
17834
+ createInterface4({ input: turn.stdout }).on("line", (line) => this.receive(line));
17835
+ const begun = new Promise((resolve3, reject) => {
17836
+ this.begun = { resolve: resolve3, reject };
17837
+ const end = (reason) => {
17838
+ reject(new Error(reason));
17839
+ if (this.turn !== turn) return;
17840
+ this.turn = void 0;
17841
+ for (const pending of this.calls.values()) pending.reject(new Error(reason));
17842
+ this.calls.clear();
17843
+ this.approvals.clear();
17844
+ this.status = "idle";
17845
+ this.changed();
17846
+ };
17847
+ turn.once("error", () => end("Kimi is not installed on this computer"));
17848
+ turn.once("exit", () => end(errors.trim() || "Kimi stopped before starting"));
17849
+ });
17850
+ await this.call("initialize", {
17851
+ protocol_version: "1.10",
17852
+ client: { name: "dashup", version },
17853
+ capabilities: { supports_question: true, supports_plan_mode: true }
17854
+ });
17855
+ if (typeof this.chosen.plan === "boolean")
17856
+ await this.call("set_plan_mode", { enabled: this.chosen.plan });
17857
+ void this.call("prompt", { user_input: text3 }).catch((error) => {
17858
+ this.begun?.reject(error);
17859
+ this.messages.push({ id: randomUUID5(), role: "system", text: error.message });
17860
+ }).finally(() => turn.stdin.end());
17861
+ this.changed();
17862
+ return begun;
17863
+ }
17864
+ // Handles one line of Kimi's output: the answer to a call, an event, or a request to answer.
17865
+ receive(line) {
17866
+ let message;
17867
+ try {
17868
+ message = JSON.parse(line);
17869
+ } catch {
17870
+ return;
17871
+ }
17872
+ if (message.id !== void 0 && !message.method) {
17873
+ const pending = this.calls.get(String(message.id));
17874
+ this.calls.delete(String(message.id));
17875
+ if (message.error) pending?.reject(new Error(String(message.error.message ?? "Kimi failed")));
17876
+ else pending?.resolve(message.result ?? {});
17877
+ return;
17878
+ }
17879
+ const type = message.params?.type;
17880
+ const payload = message.params?.payload ?? {};
17881
+ if (message.method === "event") {
17882
+ if (type === "TurnBegin") this.begun?.resolve();
17883
+ else if (type === "ContentPart" && payload.type === "text") {
17884
+ this.typing += String(payload.text ?? "");
17885
+ } else if (type === "TurnEnd") {
17886
+ if (this.typing)
17887
+ this.messages.push({ id: randomUUID5(), role: "assistant", text: this.typing });
17888
+ this.typing = "";
17889
+ this.status = this.approvals.size ? "attention" : "idle";
17890
+ } else return;
17891
+ } else if (message.method === "request" && type === "ApprovalRequest") {
17892
+ const id = randomUUID5();
17893
+ let detail = [payload.description, payload.sender].filter(Boolean).join("\n\n");
17894
+ if (Buffer.byteLength(detail) > MAX_PREVIEW)
17895
+ detail = Buffer.from(detail).subarray(0, MAX_PREVIEW).toString("utf8");
17896
+ this.approvals.set(id, {
17897
+ rpc: message.id,
17898
+ request: payload.id,
17899
+ approval: {
17900
+ id,
17901
+ title: `Allow ${String(payload.action ?? "this")}?`,
17902
+ detail,
17903
+ choices: CHOICES.map((choice) => ({ ...choice }))
17904
+ }
17905
+ });
17906
+ this.status = "attention";
17907
+ } else if (message.method === "request" && type === "QuestionRequest") {
17908
+ const id = randomUUID5();
17909
+ const questions = Array.isArray(payload.questions) ? payload.questions : [];
17910
+ this.approvals.set(id, {
17911
+ rpc: message.id,
17912
+ request: payload.id,
17913
+ questions,
17914
+ approval: questionApproval(id, questions, (item) => item.multi_select === true)
17915
+ });
17916
+ this.status = "attention";
17917
+ } else return;
17918
+ this.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
17919
+ this.changed();
17920
+ }
17921
+ live() {
17922
+ return {
17923
+ id: this.id,
17924
+ cwd: this.cwd,
17925
+ title: this.title,
17926
+ harness: "kimi",
17927
+ updatedAt: this.updatedAt,
17928
+ status: this.status,
17929
+ messages: [...this.messages],
17930
+ typing: this.typing,
17931
+ approvals: [...this.approvals.values()].map((pending) => pending.approval),
17932
+ send: (requestId, text3) => this.once(requestId, () => this.say(text3)),
17933
+ now: true,
17934
+ stop: this.turn ? (requestId) => this.once(requestId, async () => {
17935
+ await this.call("cancel");
17936
+ }) : void 0,
17937
+ approve: this.approvals.size ? (requestId, approvalId, reply) => this.once(requestId, async () => {
17938
+ const pending = this.approvals.get(approvalId);
17939
+ const choice = "choice" in reply ? reply.choice : void 0;
17940
+ if (!pending || !this.turn) throw new Error("Kimi is no longer asking");
17941
+ if (pending.questions)
17942
+ this.write({
17943
+ id: pending.rpc,
17944
+ result: {
17945
+ request_id: pending.request,
17946
+ answers: Object.fromEntries(
17947
+ "answers" in reply ? pending.questions.map((item, index) => [
17948
+ item.question,
17949
+ (reply.answers[index] ?? []).join(", ")
17950
+ ]) : []
17951
+ )
17952
+ }
17953
+ });
17954
+ else if (CHOICES.some((value2) => value2.id === choice))
17955
+ this.write({
17956
+ id: pending.rpc,
17957
+ result: {
17958
+ request_id: pending.request,
17959
+ response: choice,
17960
+ ..."text" in reply && reply.text ? { feedback: reply.text } : {}
17961
+ }
17962
+ });
17963
+ else throw new Error("Kimi is no longer asking");
17964
+ this.approvals.delete(approvalId);
17965
+ if (!this.approvals.size) this.status = "working";
17966
+ this.changed();
17967
+ }) : void 0,
17968
+ options: (recorded) => kimiOptions({ ...recorded, ...this.launch() }),
17969
+ // Plan mode applies to the turn under way; the model and thinking apply as the next starts.
17970
+ configure: (requestId, option2, value2) => this.once(requestId, async () => {
17971
+ this.chosen[option2] = value2;
17972
+ if (option2 === "plan" && this.turn) await this.call("set_plan_mode", { enabled: value2 });
17973
+ this.changed();
17974
+ })
17975
+ };
17976
+ }
17977
+ end() {
17978
+ this.turn?.kill();
17979
+ }
17980
+ };
17981
+ var Kimi = class {
17982
+ constructor(changed) {
17983
+ this.changed = changed;
17984
+ }
17985
+ changed;
17986
+ sessions = /* @__PURE__ */ new Map();
17987
+ notify;
17988
+ soon = () => {
17989
+ if (this.notify) return;
17990
+ this.notify = setTimeout(() => {
17991
+ this.notify = void 0;
17992
+ this.changed();
17993
+ }, NOTIFY_MS2);
17994
+ this.notify.unref();
17995
+ };
17996
+ // Starts a session in `cwd` with its first message, and resolves with the session's id.
17997
+ async start(cwd, text3) {
17998
+ const title2 = text3.trim().split("\n")[0].slice(0, 80);
17999
+ const session = new Session2(randomUUID5(), cwd, title2, this.soon);
18000
+ this.sessions.set(session.id, session);
18001
+ try {
18002
+ await session.say(text3);
18003
+ } catch (error) {
18004
+ this.sessions.delete(session.id);
18005
+ throw error;
18006
+ }
18007
+ return session.id;
18008
+ }
18009
+ // Continues a saved session with a message; Kimi resumes it from its own session folder.
18010
+ async resume(id, cwd, text3) {
18011
+ const known = this.sessions.get(id);
18012
+ const session = known ?? new Session2(id, cwd, void 0, this.soon);
18013
+ this.sessions.set(id, session);
18014
+ try {
18015
+ await session.say(text3);
18016
+ } catch (error) {
18017
+ if (!known) this.sessions.delete(id);
18018
+ throw error;
18019
+ }
18020
+ }
18021
+ async refresh() {
18022
+ return [...this.sessions.values()].map((session) => session.live());
18023
+ }
18024
+ stop() {
18025
+ clearTimeout(this.notify);
18026
+ for (const session of this.sessions.values()) session.end();
18027
+ }
18028
+ };
18029
+
17598
18030
  // src/harness/vscode.ts
17599
18031
  import Socket3 from "ws";
17600
18032
  import { homedir as homedir6 } from "node:os";
17601
18033
  import { connect as connect2 } from "node:net";
17602
- import { promisify as promisify3 } from "node:util";
18034
+ import { promisify as promisify4 } from "node:util";
17603
18035
  import { fileURLToPath } from "node:url";
17604
- import { execFile as execFile3 } from "node:child_process";
17605
- import { createHash as createHash3, randomUUID as randomUUID5 } from "node:crypto";
18036
+ import { execFile as execFile4 } from "node:child_process";
18037
+ import { createHash as createHash4, randomUUID as randomUUID6 } from "node:crypto";
17606
18038
  import { stat as stat2, readdir as readdir3, readFile as readFile3 } from "node:fs/promises";
17607
- import { join as join6, dirname as dirname5, basename as basename3, isAbsolute as isAbsolute3 } from "node:path";
18039
+ import { join as join7, dirname as dirname5, basename as basename3, isAbsolute as isAbsolute3 } from "node:path";
17608
18040
  import { WebSocketTransport } from "@microsoft/agent-host-protocol/ws";
17609
18041
  import { AhpClient } from "@microsoft/agent-host-protocol/client";
17610
18042
  import { chatReducer, sessionReducer, SUPPORTED_PROTOCOL_VERSIONS } from "@microsoft/agent-host-protocol";
@@ -17614,7 +18046,7 @@ var MAX_METADATA = 64 * 1024;
17614
18046
  var MAX_HOSTS = 16;
17615
18047
  var MAX_SESSIONS3 = 100;
17616
18048
  var ROOT_CHANNEL = "ahp-root://";
17617
- var run = promisify3(execFile3);
18049
+ var run2 = promisify4(execFile4);
17618
18050
  var CONFIRMATION_CHOICES = [
17619
18051
  { id: "approve", label: "Allow once", kind: "approve" },
17620
18052
  { id: "deny", label: "Reject", kind: "deny" }
@@ -17624,8 +18056,8 @@ function roots() {
17624
18056
  if (override !== void 0)
17625
18057
  return override ? [basename3(override) === "User" ? dirname5(override) : override] : [];
17626
18058
  if (process.env.DASHUP_LIVE_DISCOVERY === "0") return [];
17627
- const base = process.platform === "darwin" ? join6(homedir6(), "Library/Application Support") : process.platform === "win32" ? process.env.APPDATA ?? join6(homedir6(), "AppData/Roaming") : process.env.XDG_CONFIG_HOME ?? join6(homedir6(), ".config");
17628
- return ["Code", "Code - Insiders"].map((name) => join6(base, name));
18059
+ const base = process.platform === "darwin" ? join7(homedir6(), "Library/Application Support") : process.platform === "win32" ? process.env.APPDATA ?? join7(homedir6(), "AppData/Roaming") : process.env.XDG_CONFIG_HOME ?? join7(homedir6(), ".config");
18060
+ return ["Code", "Code - Insiders"].map((name) => join7(base, name));
17629
18061
  }
17630
18062
  async function privateFile(path, limit) {
17631
18063
  const file = await stat2(path).catch(() => void 0);
@@ -17634,7 +18066,7 @@ async function privateFile(path, limit) {
17634
18066
  const encodedPath = Buffer.from(path).toString("base64");
17635
18067
  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`;
17636
18068
  try {
17637
- const { stdout } = await run(
18069
+ const { stdout } = await run2(
17638
18070
  "powershell.exe",
17639
18071
  [
17640
18072
  "-NoProfile",
@@ -17654,11 +18086,11 @@ async function privateFile(path, limit) {
17654
18086
  async function entries() {
17655
18087
  const result = /* @__PURE__ */ new Map();
17656
18088
  for (const root of roots()) {
17657
- const registry2 = join6(root, "agent-host/local-endpoint");
17658
- const files2 = (await readdir3(join6(registry2, "entries")).catch(() => [])).filter((name) => name.endsWith(".json")).slice(0, MAX_HOSTS);
18089
+ const registry2 = join7(root, "agent-host/local-endpoint");
18090
+ const files2 = (await readdir3(join7(registry2, "entries")).catch(() => [])).filter((name) => name.endsWith(".json")).slice(0, MAX_HOSTS);
17659
18091
  const sources = [
17660
- ...files2.map((name) => join6(registry2, "entries", name)),
17661
- join6(registry2, "metadata.json")
18092
+ ...files2.map((name) => join7(registry2, "entries", name)),
18093
+ join7(registry2, "metadata.json")
17662
18094
  ];
17663
18095
  for (const path of sources) {
17664
18096
  try {
@@ -17668,7 +18100,7 @@ async function entries() {
17668
18100
  for (const row of rows.slice(0, MAX_HOSTS)) {
17669
18101
  if (row?.schemaVersion !== 2 || !["editor", "standalone"].includes(row.type) || !Number.isSafeInteger(row.pid) || row.pid <= 0 || typeof row.instanceId !== "string" || !row.instanceId || typeof row.connectionToken !== "string" || !row.connectionToken || row.connectionToken.length > 1024 || row.endpoint?.type !== "socket" || typeof row.endpoint.path !== "string" || !isAbsolute3(row.endpoint.path))
17670
18102
  continue;
17671
- const identity2 = createHash3("sha256").update(`${row.type}\0${row.pid}\0${row.instanceId}`).digest("hex");
18103
+ const identity2 = createHash4("sha256").update(`${row.type}\0${row.pid}\0${row.instanceId}`).digest("hex");
17672
18104
  if (basename3(path) !== "metadata.json" && basename3(path) !== `${identity2}.json`) continue;
17673
18105
  try {
17674
18106
  process.kill(row.pid, 0);
@@ -17795,7 +18227,7 @@ var Host = class {
17795
18227
  client;
17796
18228
  connecting;
17797
18229
  stopped = false;
17798
- clientId = `dashup-${randomUUID5()}`;
18230
+ clientId = `dashup-${randomUUID6()}`;
17799
18231
  resources = /* @__PURE__ */ new Map();
17800
18232
  subscriptions = /* @__PURE__ */ new Set();
17801
18233
  receipts = /* @__PURE__ */ new Map();
@@ -17984,7 +18416,7 @@ var Host = class {
17984
18416
  choices: preview3.choices.map(({ id: id2, label }) => ({ id: id2, label }))
17985
18417
  });
17986
18418
  }
17987
- const id = `vscode-ahp:${createHash3("sha256").update(this.entry.root).digest("hex").slice(0, 16)}:${item.resource}`;
18419
+ const id = `vscode-ahp:${createHash4("sha256").update(this.entry.root).digest("hex").slice(0, 16)}:${item.resource}`;
17988
18420
  result.push({
17989
18421
  id,
17990
18422
  cwd,
@@ -18095,12 +18527,12 @@ var VSCode = class {
18095
18527
  };
18096
18528
 
18097
18529
  // src/harness/opencode.ts
18098
- import { join as join7 } from "node:path";
18099
- import { promisify as promisify4 } from "node:util";
18100
- import { createHash as createHash4 } from "node:crypto";
18101
- import { execFile as execFile4, spawn as spawn4 } from "node:child_process";
18530
+ import { join as join8 } from "node:path";
18531
+ import { promisify as promisify5 } from "node:util";
18532
+ import { createHash as createHash5 } from "node:crypto";
18533
+ import { execFile as execFile5, spawn as spawn5 } from "node:child_process";
18102
18534
  import { stat as stat3, readlink as readlink2, readdir as readdir4, readFile as readFile4 } from "node:fs/promises";
18103
- var run2 = promisify4(execFile4);
18535
+ var run3 = promisify5(execFile5);
18104
18536
  var REQUEST_MS3 = 4e3;
18105
18537
  var MAX_BYTES3 = 8 * 1024 * 1024;
18106
18538
  var MAX_SESSIONS4 = 100;
@@ -18145,16 +18577,16 @@ async function linuxEndpoints() {
18145
18577
  const directory2 = `/proc/${pid}`;
18146
18578
  const owner = await stat3(directory2);
18147
18579
  if (process.getuid && owner.uid !== process.getuid()) continue;
18148
- const command2 = (await readFile4(join7(directory2, "cmdline"), "utf8")).slice(0, 4096);
18580
+ const command2 = (await readFile4(join8(directory2, "cmdline"), "utf8")).slice(0, 4096);
18149
18581
  if (!/(?:^|[\/\0 ])opencode(?:[\0 .]|$)/.test(command2)) continue;
18150
18582
  const environment = Object.fromEntries(
18151
- (await readFile4(join7(directory2, "environ"), "utf8")).split("\0").map((entry) => {
18583
+ (await readFile4(join8(directory2, "environ"), "utf8")).split("\0").map((entry) => {
18152
18584
  const split = entry.indexOf("=");
18153
18585
  return [entry.slice(0, split), entry.slice(split + 1)];
18154
18586
  })
18155
18587
  );
18156
- for (const fd of (await readdir4(join7(directory2, "fd"))).slice(0, 1024)) {
18157
- const target = await readlink2(join7(directory2, "fd", fd)).catch(() => "");
18588
+ for (const fd of (await readdir4(join8(directory2, "fd"))).slice(0, 1024)) {
18589
+ const target = await readlink2(join8(directory2, "fd", fd)).catch(() => "");
18158
18590
  const inode = /^socket:\[(\d+)\]$/.exec(target)?.[1];
18159
18591
  const url2 = inode ? listeners.get(inode) : void 0;
18160
18592
  if (url2) result.push({ url: url2, authorization: authorization(environment) });
@@ -18180,7 +18612,7 @@ async function endpoints() {
18180
18612
  if (process.env.DASHUP_LIVE_DISCOVERY === "0") return [];
18181
18613
  if (process.platform === "linux") return linuxEndpoints();
18182
18614
  if (process.platform === "darwin") {
18183
- const { stdout } = await run2(
18615
+ const { stdout } = await run3(
18184
18616
  "lsof",
18185
18617
  ["-nP", "-a", "-c", "opencode", "-iTCP", "-sTCP:LISTEN", "-Fn"],
18186
18618
  { timeout: REQUEST_MS3, maxBuffer: MAX_BYTES3 }
@@ -18197,7 +18629,7 @@ async function endpoints() {
18197
18629
  }
18198
18630
  if (process.platform === "win32") {
18199
18631
  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";
18200
- const { stdout } = await run2(
18632
+ const { stdout } = await run3(
18201
18633
  "powershell.exe",
18202
18634
  ["-NoProfile", "-NonInteractive", "-Command", script],
18203
18635
  { timeout: REQUEST_MS3, maxBuffer: MAX_BYTES3 }
@@ -18287,7 +18719,14 @@ ${NO_PREVIEW}`;
18287
18719
  ...change2 ? { change: change2 } : {}
18288
18720
  };
18289
18721
  }
18290
- function promptContext(history) {
18722
+ function promptContext(history, session) {
18723
+ const current = session?.model;
18724
+ if (typeof session?.agent === "string" && typeof current?.providerID === "string" && typeof current.id === "string")
18725
+ return {
18726
+ agent: session.agent,
18727
+ model: { providerID: current.providerID, modelID: current.id },
18728
+ ...typeof current.variant === "string" ? { variant: current.variant } : {}
18729
+ };
18291
18730
  if (!Array.isArray(history)) throw new Error("OpenCode session context is unavailable");
18292
18731
  const user = [...history].reverse().find((entry) => entry?.info?.role === "user")?.info;
18293
18732
  if (!user) return {};
@@ -18301,6 +18740,26 @@ function promptContext(history) {
18301
18740
  ...variant === void 0 ? {} : { variant }
18302
18741
  };
18303
18742
  }
18743
+ function sessionOptions(models2, agents, session) {
18744
+ const current = session?.model;
18745
+ if (typeof current?.providerID !== "string" || typeof current.id !== "string") return [];
18746
+ const value2 = `${current.providerID}/${current.id}`;
18747
+ const agent = typeof session?.agent === "string" ? session.agent : void 0;
18748
+ const listed = (Array.isArray(models2) ? models2 : []).filter((model) => typeof model?.providerID === "string" && typeof model.id === "string").map((model) => ({
18749
+ value: `${model.providerID}/${model.id}`,
18750
+ name: String(model.name ?? model.id)
18751
+ }));
18752
+ const primary = (Array.isArray(agents) ? agents : []).filter(
18753
+ (item) => typeof item?.id === "string" && item.mode !== "subagent" && !item.hidden
18754
+ ).map((item) => ({ value: String(item.id), name: String(item.name ?? item.id) }));
18755
+ if (!listed.some((choice) => choice.value === value2)) listed.push({ value: value2, name: current.id });
18756
+ if (agent && !primary.some((choice) => choice.value === agent))
18757
+ primary.push({ value: agent, name: agent });
18758
+ return [
18759
+ { id: "model", name: "Model", value: value2, choices: listed },
18760
+ ...agent ? [{ id: "agent", name: "Agent", value: agent, choices: primary }] : []
18761
+ ];
18762
+ }
18304
18763
  var Server = class {
18305
18764
  constructor(endpoint, changed) {
18306
18765
  this.endpoint = endpoint;
@@ -18313,6 +18772,8 @@ var Server = class {
18313
18772
  notify;
18314
18773
  stopped = false;
18315
18774
  requests = /* @__PURE__ */ new Map();
18775
+ // The models and agents OpenCode lists for each folder, asked once while it answers.
18776
+ catalogs = /* @__PURE__ */ new Map();
18316
18777
  headers(directory2) {
18317
18778
  return {
18318
18779
  ...this.endpoint.authorization ? { Authorization: this.endpoint.authorization } : {},
@@ -18347,12 +18808,29 @@ var Server = class {
18347
18808
  this.requests.set(id, pending);
18348
18809
  return pending;
18349
18810
  }
18350
- // A message to one of this folder's sessions, in the agent and model it last used; a new
18811
+ catalog(cwd) {
18812
+ let known = this.catalogs.get(cwd);
18813
+ if (!known) {
18814
+ known = Promise.all([
18815
+ this.request("/api/model", cwd).then((listed) => listed?.data),
18816
+ this.request("/api/agent", cwd).then((listed) => listed?.data)
18817
+ ]).catch(() => {
18818
+ this.catalogs.delete(cwd);
18819
+ return [[], []];
18820
+ });
18821
+ this.catalogs.set(cwd, known);
18822
+ }
18823
+ return known;
18824
+ }
18825
+ // A message to one of this folder's sessions, in the agent and model it runs with; a new
18351
18826
  // session takes OpenCode's defaults.
18352
18827
  async prompt(id, cwd, text3) {
18353
- const existing = await this.request(`/session/${encodeURIComponent(id)}/message?limit=200`, cwd);
18828
+ const [existing, session] = await Promise.all([
18829
+ this.request(`/session/${encodeURIComponent(id)}/message?limit=200`, cwd),
18830
+ this.request(`/api/session/${encodeURIComponent(id)}`, cwd).catch(() => void 0)
18831
+ ]);
18354
18832
  await this.request(`/session/${encodeURIComponent(id)}/prompt_async`, cwd, {
18355
- ...promptContext(existing),
18833
+ ...promptContext(existing, session?.data),
18356
18834
  parts: [{ type: "text", text: text3 }]
18357
18835
  });
18358
18836
  this.changed();
@@ -18382,11 +18860,16 @@ var Server = class {
18382
18860
  state = Promise.all([
18383
18861
  this.request("/session/status", cwd),
18384
18862
  this.request("/permission", cwd).catch(() => []),
18385
- this.request("/question", cwd).catch(() => [])
18863
+ this.request("/question", cwd).catch(() => []),
18864
+ this.request(`/api/session?limit=${MAX_SESSIONS4}`, cwd).then(
18865
+ (listed) => Array.isArray(listed?.data) ? listed.data : [],
18866
+ () => []
18867
+ ),
18868
+ this.catalog(cwd)
18386
18869
  ]);
18387
18870
  states.set(cwd, state);
18388
18871
  }
18389
- const [history, [status2, permissions, questions]] = await Promise.all([
18872
+ const [history, [status2, permissions, questions, settings, [models2, agents]]] = await Promise.all([
18390
18873
  this.request(`/session/${encodeURIComponent(id)}/message?limit=200`, cwd),
18391
18874
  state
18392
18875
  ]);
@@ -18396,6 +18879,7 @@ var Server = class {
18396
18879
  const asks = Array.isArray(questions) ? questions.filter(
18397
18880
  (question) => question?.sessionID === id && typeof question.id === "string"
18398
18881
  ) : [];
18882
+ const current = settings.find((item) => item?.id === id);
18399
18883
  const approvals = pending.map((permission) => permissionApproval(permission, history, cwd));
18400
18884
  for (const question of asks)
18401
18885
  approvals.push(
@@ -18416,8 +18900,8 @@ var Server = class {
18416
18900
  approvals,
18417
18901
  stop: (requestId) => this.once(requestId, async () => {
18418
18902
  if (this.stopped) throw new Error("OpenCode is disconnected");
18419
- const current = await this.request("/session/status", cwd);
18420
- if (!["busy", "retry"].includes(current?.[id]?.type))
18903
+ const current2 = await this.request("/session/status", cwd);
18904
+ if (!["busy", "retry"].includes(current2?.[id]?.type))
18421
18905
  throw new Error("Nothing is running in this OpenCode session");
18422
18906
  await this.request(`/session/${encodeURIComponent(id)}/abort`, cwd, {});
18423
18907
  this.changed();
@@ -18426,7 +18910,7 @@ var Server = class {
18426
18910
  if (this.stopped) throw new Error("OpenCode is disconnected");
18427
18911
  if (!text3.trim() || Buffer.byteLength(text3) > MAX_BYTES3 / 2)
18428
18912
  throw new Error("Message is empty or too large");
18429
- const messageID = `msg_${createHash4("sha256").update(requestId).digest("hex").slice(0, 32)}`;
18913
+ const messageID = `msg_${createHash5("sha256").update(requestId).digest("hex").slice(0, 32)}`;
18430
18914
  const existing = await this.request(
18431
18915
  `/session/${encodeURIComponent(id)}/message?limit=200`,
18432
18916
  cwd
@@ -18434,19 +18918,32 @@ var Server = class {
18434
18918
  if (Array.isArray(existing) && existing.some((entry) => entry?.info?.id === messageID))
18435
18919
  return;
18436
18920
  await this.request(`/session/${encodeURIComponent(id)}/prompt_async`, cwd, {
18437
- ...promptContext(existing),
18921
+ ...promptContext(existing, current),
18438
18922
  messageID,
18439
18923
  parts: [{ type: "text", text: text3 }]
18440
18924
  });
18441
18925
  this.changed();
18442
18926
  }),
18927
+ options: () => sessionOptions(models2, agents, current),
18928
+ // OpenCode keeps the model, as `provider/model`, and the agent on the session.
18929
+ configure: (requestId, option2, value2) => this.once(requestId, async () => {
18930
+ if (this.stopped) throw new Error("OpenCode is disconnected");
18931
+ const chosen = String(value2);
18932
+ const split = chosen.indexOf("/");
18933
+ await this.request(
18934
+ `/api/session/${encodeURIComponent(id)}/${option2}`,
18935
+ cwd,
18936
+ option2 === "model" ? { model: { providerID: chosen.slice(0, split), id: chosen.slice(split + 1) } } : { agent: chosen }
18937
+ );
18938
+ this.changed();
18939
+ }),
18443
18940
  approve: approvals.some((approval2) => approval2.choices.length) ? (requestId, approvalId, reply) => this.once(requestId, async () => {
18444
18941
  if (this.stopped) throw new Error("OpenCode is disconnected");
18445
18942
  const choice = "choice" in reply ? reply.choice : void 0;
18446
18943
  if (approvalId.startsWith("question:")) {
18447
18944
  const requestID = approvalId.slice("question:".length);
18448
- const current = await this.request("/question", cwd);
18449
- if (!Array.isArray(current) || !current.some(
18945
+ const current2 = await this.request("/question", cwd);
18946
+ if (!Array.isArray(current2) || !current2.some(
18450
18947
  (value2) => value2?.id === requestID && value2.sessionID === id
18451
18948
  ))
18452
18949
  throw new Error("Question is no longer pending");
@@ -18456,8 +18953,8 @@ var Server = class {
18456
18953
  "answers" in reply ? { answers: reply.answers } : {}
18457
18954
  );
18458
18955
  } else {
18459
- const current = await this.request("/permission", cwd);
18460
- const permission = Array.isArray(current) ? current.find(
18956
+ const current2 = await this.request("/permission", cwd);
18957
+ const permission = Array.isArray(current2) ? current2.find(
18461
18958
  (value2) => value2?.id === approvalId && value2.sessionID === id
18462
18959
  ) : void 0;
18463
18960
  if (!PERMISSION_CHOICES.some((value2) => value2.id === choice) || !permission)
@@ -18557,7 +19054,7 @@ var Server = class {
18557
19054
  }
18558
19055
  };
18559
19056
  function serve(cwd) {
18560
- const child = spawn4("opencode", ["serve", "--hostname", "127.0.0.1", "--port", "0"], {
19057
+ const child = spawn5("opencode", ["serve", "--hostname", "127.0.0.1", "--port", "0"], {
18561
19058
  cwd,
18562
19059
  stdio: ["ignore", "pipe", "ignore"],
18563
19060
  shell: process.platform === "win32"
@@ -18645,6 +19142,7 @@ var LiveHarnesses = class {
18645
19142
  listeners = /* @__PURE__ */ new Set();
18646
19143
  codex = new Codex(() => this.changed());
18647
19144
  headless = new Headless(() => this.changed());
19145
+ kimi = new Kimi(() => this.changed());
18648
19146
  claude = new Claude(() => this.changed());
18649
19147
  vscode = new VSCode(() => this.changed());
18650
19148
  opencode = new OpenCode(() => this.changed());
@@ -18660,6 +19158,7 @@ var LiveHarnesses = class {
18660
19158
  this.refreshing = (async () => {
18661
19159
  const results = await Promise.allSettled([
18662
19160
  this.headless.refresh(),
19161
+ this.kimi.refresh(),
18663
19162
  this.opencode.refresh(),
18664
19163
  this.codex.refresh(),
18665
19164
  this.claude.refresh(),
@@ -18682,14 +19181,16 @@ var LiveHarnesses = class {
18682
19181
  recorded(harness, settings) {
18683
19182
  const changed = () => this.changed();
18684
19183
  if (harness === "claude") return claudeOptions(claudeModels(changed), settings);
19184
+ if (harness === "kimi") return kimiOptions(settings);
18685
19185
  return harness === "codex" ? codexOptions(settings) : [];
18686
19186
  }
18687
19187
  // The agents whose own command runs on this computer. Claude Code is asked its models again.
18688
19188
  async agents() {
18689
19189
  const harnesses = ["claude", "codex", "opencode"];
19190
+ const kimi2 = kimiCommand();
18690
19191
  forgetModels();
18691
19192
  const runs = await Promise.all(
18692
- harnesses.map((harness) => promisify5(execFile5)(harness, ["--version"], {
19193
+ harnesses.map((harness) => promisify6(execFile6)(harness, ["--version"], {
18693
19194
  timeout: 1e4,
18694
19195
  shell: process.platform === "win32"
18695
19196
  }).then(
@@ -18697,22 +19198,22 @@ var LiveHarnesses = class {
18697
19198
  () => false
18698
19199
  ))
18699
19200
  );
18700
- return harnesses.filter((_, index) => runs[index]);
19201
+ return [
19202
+ ...harnesses.filter((_, index) => runs[index]),
19203
+ ...await kimi2 ? ["kimi"] : []
19204
+ ];
19205
+ }
19206
+ // The harness that starts and resumes each agent's sessions.
19207
+ host(harness) {
19208
+ return { claude: this.headless, codex: this.codex, kimi: this.kimi, opencode: this.opencode }[harness];
18701
19209
  }
18702
19210
  // Starts a session in `cwd` with its first message, and resolves with the session's native id.
18703
19211
  start(harness, cwd, text3) {
18704
- return { claude: this.headless, codex: this.codex, opencode: this.opencode }[harness].start(
18705
- cwd,
18706
- text3
18707
- );
19212
+ return this.host(harness).start(cwd, text3);
18708
19213
  }
18709
19214
  // Continues a saved session with a message, and resolves once its agent takes it.
18710
19215
  resume(harness, id, cwd, text3) {
18711
- return { claude: this.headless, codex: this.codex, opencode: this.opencode }[harness].resume(
18712
- id,
18713
- cwd,
18714
- text3
18715
- );
19216
+ return this.host(harness).resume(id, cwd, text3);
18716
19217
  }
18717
19218
  watch(onChange) {
18718
19219
  this.listeners.add(onChange);
@@ -18737,6 +19238,7 @@ var LiveHarnesses = class {
18737
19238
  clearInterval(this.timer);
18738
19239
  this.listeners.clear();
18739
19240
  this.headless.stop();
19241
+ this.kimi.stop();
18740
19242
  this.opencode.stop();
18741
19243
  this.codex.stop();
18742
19244
  this.claude.stop();
@@ -18745,11 +19247,11 @@ var LiveHarnesses = class {
18745
19247
  };
18746
19248
 
18747
19249
  // src/harness/queue.ts
18748
- import { randomUUID as randomUUID6 } from "node:crypto";
18749
- import { spawn as spawn5 } from "node:child_process";
19250
+ import { randomUUID as randomUUID7 } from "node:crypto";
19251
+ import { spawn as spawn6 } from "node:child_process";
18750
19252
  import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
18751
- import { join as join8, dirname as dirname6, basename as basename4 } from "node:path";
18752
- import { statSync as statSync3, existsSync as existsSync4, readdirSync as readdirSync3, readFileSync as readFileSync5, readlinkSync, realpathSync as realpathSync2 } from "node:fs";
19253
+ import { join as join9, dirname as dirname6, basename as basename4 } from "node:path";
19254
+ import { statSync as statSync4, existsSync as existsSync5, readdirSync as readdirSync4, readFileSync as readFileSync6, readlinkSync, realpathSync as realpathSync2 } from "node:fs";
18753
19255
  var CODEX_VERSION = "0.160.1";
18754
19256
  var REQUEST_MS4 = 1e4;
18755
19257
  var MAX_BYTES4 = 1024 * 1024;
@@ -18758,7 +19260,7 @@ var MAX_FILES = 4096;
18758
19260
  var THREAD_ID = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i;
18759
19261
  function identity(path) {
18760
19262
  const canonical2 = realpathSync2.native(path);
18761
- const file = statSync3(canonical2);
19263
+ const file = statSync4(canonical2);
18762
19264
  if (!file.isFile() || process.getuid && file.uid !== process.getuid())
18763
19265
  throw new Error("Native queue files are not owned by this user");
18764
19266
  return { path: canonical2, device: file.dev, inode: file.ino };
@@ -18769,25 +19271,25 @@ function sameFile(left, right) {
18769
19271
  function packageVersion(executable) {
18770
19272
  let directory2 = dirname6(executable);
18771
19273
  for (let depth = 0; depth < 8; depth++, directory2 = dirname6(directory2)) {
18772
- const path = join8(directory2, "package.json");
18773
- if (!existsSync4(path)) continue;
18774
- const value2 = JSON.parse(readFileSync5(path, "utf8"));
19274
+ const path = join9(directory2, "package.json");
19275
+ if (!existsSync5(path)) continue;
19276
+ const value2 = JSON.parse(readFileSync6(path, "utf8"));
18775
19277
  if (value2.name?.startsWith("@openai/codex-")) return value2.version;
18776
19278
  }
18777
19279
  return void 0;
18778
19280
  }
18779
19281
  function processOwners(pid) {
18780
19282
  const root = `/proc/${pid}`;
18781
- if (process.getuid && statSync3(root).uid !== process.getuid()) return [];
18782
- const executable = realpathSync2.native(join8(root, "exe"));
19283
+ if (process.getuid && statSync4(root).uid !== process.getuid()) return [];
19284
+ const executable = realpathSync2.native(join9(root, "exe"));
18783
19285
  if (basename4(executable) !== "codex" || packageVersion(executable) !== CODEX_VERSION) return [];
18784
- const status2 = readFileSync5(join8(root, "stat"), "utf8");
19286
+ const status2 = readFileSync6(join9(root, "stat"), "utf8");
18785
19287
  const start2 = status2.slice(status2.lastIndexOf(")") + 2).split(" ")[19];
18786
19288
  if (!start2 || !/^\d+$/.test(start2)) return [];
18787
- const fds = readdirSync3(join8(root, "fd")).slice(0, MAX_FILES);
19289
+ const fds = readdirSync4(join9(root, "fd")).slice(0, MAX_FILES);
18788
19290
  const files2 = fds.flatMap((fd) => {
18789
19291
  try {
18790
- return [{ fd, path: readlinkSync(join8(root, "fd", fd)) }];
19292
+ return [{ fd, path: readlinkSync(join9(root, "fd", fd)) }];
18791
19293
  } catch {
18792
19294
  return [];
18793
19295
  }
@@ -18797,22 +19299,22 @@ function processOwners(pid) {
18797
19299
  const thread = basename4(path, ".lock");
18798
19300
  if (!THREAD_ID.test(thread)) return [];
18799
19301
  try {
18800
- const lock = readFileSync5(join8(root, "fdinfo", fd), "utf8");
19302
+ const lock = readFileSync6(join9(root, "fdinfo", fd), "utf8");
18801
19303
  if (!/FLOCK\s+ADVISORY\s+WRITE/.test(lock)) return [];
18802
19304
  const home = realpathSync2.native(dirname6(dirname6(path)));
18803
- const directory2 = statSync3(home);
19305
+ const directory2 = statSync4(home);
18804
19306
  if (process.getuid && directory2.uid !== process.getuid()) return [];
18805
- const queue = identity(join8(home, "queue_1.sqlite"));
19307
+ const queue = identity(join9(home, "queue_1.sqlite"));
18806
19308
  if (!files2.some((file) => {
18807
19309
  try {
18808
- return sameFile(identity(join8(root, "fd", file.fd)), queue);
19310
+ return sameFile(identity(join9(root, "fd", file.fd)), queue);
18809
19311
  } catch {
18810
19312
  return false;
18811
19313
  }
18812
19314
  }))
18813
19315
  return [];
18814
19316
  const rollout = files2.find(
18815
- (file) => file.path.startsWith(join8(home, "sessions") + "/") && file.path.endsWith(`-${thread}.jsonl`)
19317
+ (file) => file.path.startsWith(join9(home, "sessions") + "/") && file.path.endsWith(`-${thread}.jsonl`)
18816
19318
  );
18817
19319
  if (!rollout) return [];
18818
19320
  return [
@@ -18824,7 +19326,7 @@ function processOwners(pid) {
18824
19326
  thread,
18825
19327
  queue,
18826
19328
  version: CODEX_VERSION,
18827
- cwd: realpathSync2.native(join8(root, "cwd")),
19329
+ cwd: realpathSync2.native(join9(root, "cwd")),
18828
19330
  rollout: identity(rollout.path)
18829
19331
  }
18830
19332
  ];
@@ -18836,7 +19338,7 @@ function processOwners(pid) {
18836
19338
  function queueOwners() {
18837
19339
  if (process.platform !== "linux" || process.env.DASHUP_LIVE_DISCOVERY === "0") return [];
18838
19340
  try {
18839
- return readdirSync3("/proc").filter((value2) => /^\d+$/.test(value2)).slice(0, MAX_PROCESSES3).flatMap((value2) => {
19341
+ return readdirSync4("/proc").filter((value2) => /^\d+$/.test(value2)).slice(0, MAX_PROCESSES3).flatMap((value2) => {
18840
19342
  try {
18841
19343
  return processOwners(Number(value2));
18842
19344
  } catch {
@@ -18868,10 +19370,10 @@ function validateOwner(owner) {
18868
19370
  throw new Error("The original native conversation owner is no longer available");
18869
19371
  }
18870
19372
  function validateEvidence(owner) {
18871
- if (realpathSync2.native(owner.home) !== owner.home || process.getuid && statSync3(owner.home).uid !== process.getuid() || !sameFile(identity(owner.queue.path), owner.queue) || !sameFile(identity(owner.rollout.path), owner.rollout))
19373
+ if (realpathSync2.native(owner.home) !== owner.home || process.getuid && statSync4(owner.home).uid !== process.getuid() || !sameFile(identity(owner.queue.path), owner.queue) || !sameFile(identity(owner.rollout.path), owner.rollout))
18872
19374
  throw new Error("The original native history storage changed");
18873
- const path = join8(owner.home, "thread_history_1.sqlite");
18874
- if (existsSync4(path) && identity(path).path !== path)
19375
+ const path = join9(owner.home, "thread_history_1.sqlite");
19376
+ if (existsSync5(path) && identity(path).path !== path)
18875
19377
  throw new Error("Native history is outside the original home");
18876
19378
  }
18877
19379
  function nativeReceipt(owner, clientId) {
@@ -18887,8 +19389,8 @@ function nativeReceipt(owner, clientId) {
18887
19389
  } finally {
18888
19390
  queue.close();
18889
19391
  }
18890
- const path = join8(owner.home, "thread_history_1.sqlite");
18891
- if (!existsSync4(path)) return { queueId };
19392
+ const path = join9(owner.home, "thread_history_1.sqlite");
19393
+ if (!existsSync5(path)) return { queueId };
18892
19394
  const history = new DatabaseSync3(path, { readOnly: true });
18893
19395
  try {
18894
19396
  const rows = history.prepare(
@@ -18943,7 +19445,7 @@ var Writer = class {
18943
19445
  tail = "";
18944
19446
  pending = /* @__PURE__ */ new Map();
18945
19447
  constructor(owner) {
18946
- this.process = spawn5(owner.executable, ["app-server", "--listen", "stdio://"], {
19448
+ this.process = spawn6(owner.executable, ["app-server", "--listen", "stdio://"], {
18947
19449
  cwd: owner.cwd,
18948
19450
  env: { ...process.env, CODEX_HOME: owner.home },
18949
19451
  stdio: ["pipe", "pipe", "ignore"]
@@ -19030,7 +19532,7 @@ var NativeQueue = class {
19030
19532
  if (existing) return existing;
19031
19533
  if (this.stopped) throw new Error("The connector is stopping");
19032
19534
  validateOwner(scope2.owner);
19033
- const clientUserMessageId = randomUUID6();
19535
+ const clientUserMessageId = randomUUID7();
19034
19536
  const request2 = { ...scope2, receipt: { state: "unknown", clientUserMessageId } };
19035
19537
  let writer;
19036
19538
  try {
@@ -19138,32 +19640,32 @@ var Command = z2.object({
19138
19640
  z2.object({ kind: z2.literal("attachment.read"), id: z2.string().regex(/^[a-f0-9]{64}$/) }).strict(),
19139
19641
  z2.object({
19140
19642
  kind: z2.literal("session.start"),
19141
- harness: z2.enum(["claude", "codex", "opencode"]),
19643
+ harness: z2.enum(["claude", "codex", "kimi", "opencode"]),
19142
19644
  text: z2.string().min(1).max(1e5),
19143
19645
  folder: z2.object({ parent: z2.string().min(1), name: z2.string().min(1).max(255) }).strict().optional()
19144
19646
  }).strict()
19145
19647
  ])
19146
19648
  }).strict();
19147
19649
  var Connector = class {
19148
- constructor(config2) {
19149
- this.config = config2;
19150
- config2.unconfirmed ??= {};
19151
- config2.queued ??= {};
19650
+ constructor(config3) {
19651
+ this.config = config3;
19652
+ config3.unconfirmed ??= {};
19653
+ config3.queued ??= {};
19152
19654
  this.nativeQueue = new NativeQueue(
19153
- config2.queued,
19154
- () => saveConfig(config2),
19655
+ config3.queued,
19656
+ () => saveConfig(config3),
19155
19657
  (request2) => this.isActive(request2.pairing)
19156
19658
  );
19157
- if (!config2.device || !config2.secret) throw new Error("Pair this computer with dashup pair first");
19659
+ if (!config3.device || !config3.secret) throw new Error("Pair this computer with dashup pair first");
19158
19660
  this.client = createClient({
19159
- url: config2.api,
19160
- getToken: async () => config2.secret,
19661
+ url: config3.api,
19662
+ getToken: async () => config3.secret,
19161
19663
  autoConnect: false
19162
19664
  });
19163
19665
  provideClient(() => this.client);
19164
- this.discovery = new Discovery(config2.device.id, config2.localSecret);
19165
- this.pairings = createLiveQuery(() => this.client.bind(Pairing).where({ device: config2.device.id }).whereNull("revokedAt").clearLimit());
19166
- this.relays = createLiveQuery(() => this.client.bind(Relay).where({ recipient: config2.device.id }).orderBy("createdAt", "desc").orderBy("id", "desc").clearLimit());
19666
+ this.discovery = new Discovery(config3.device.id, config3.localSecret);
19667
+ this.pairings = createLiveQuery(() => this.client.bind(Pairing).where({ device: config3.device.id }).whereNull("revokedAt").clearLimit());
19668
+ this.relays = createLiveQuery(() => this.client.bind(Relay).where({ recipient: config3.device.id }).orderBy("createdAt", "desc").orderBy("id", "desc").clearLimit());
19167
19669
  }
19168
19670
  config;
19169
19671
  client;
@@ -19361,7 +19863,7 @@ var Connector = class {
19361
19863
  saveConfig(this.config);
19362
19864
  }
19363
19865
  flush() {
19364
- const run4 = this.drain.catch(() => {
19866
+ const run5 = this.drain.catch(() => {
19365
19867
  }).then(async () => {
19366
19868
  this.discardLegacySnapshots();
19367
19869
  while (this.config.outbox.length && this.client.isConnected) {
@@ -19371,8 +19873,8 @@ var Connector = class {
19371
19873
  saveConfig(this.config);
19372
19874
  }
19373
19875
  });
19374
- this.drain = run4;
19375
- return run4.catch((error) => {
19876
+ this.drain = run5;
19877
+ return run5.catch((error) => {
19376
19878
  this.report(error);
19377
19879
  clearTimeout(this.retry);
19378
19880
  this.retry = setTimeout(() => {
@@ -19446,13 +19948,13 @@ var Connector = class {
19446
19948
  // transcript costs one rescan rather than one each.
19447
19949
  queueChange() {
19448
19950
  if (this.pendingChange) return this.pendingChange;
19449
- const run4 = this.work.then(() => {
19951
+ const run5 = this.work.then(() => {
19450
19952
  this.pendingChange = void 0;
19451
19953
  return this.changed();
19452
19954
  });
19453
- this.pendingChange = run4;
19454
- this.work = run4.catch((error) => this.report(error));
19455
- return run4;
19955
+ this.pendingChange = run5;
19956
+ this.work = run5.catch((error) => this.report(error));
19957
+ return run5;
19456
19958
  }
19457
19959
  async changed() {
19458
19960
  if (this.stopped) return;
@@ -19936,7 +20438,7 @@ var Connector = class {
19936
20438
  throw new Error("That folder is no longer offered for new projects");
19937
20439
  if (!name || name === "." || name === ".." || /[\\/:*?"<>|\x00-\x1f]/.test(name))
19938
20440
  throw new Error("Name the folder without slashes or special characters");
19939
- cwd = join9(folder.parent, name);
20441
+ cwd = join10(folder.parent, name);
19940
20442
  await mkdir2(cwd).catch((error) => {
19941
20443
  throw new Error(error.code === "EEXIST" ? `${name} already exists` : `Couldn't create ${name}`);
19942
20444
  });
@@ -19962,32 +20464,32 @@ var Connector = class {
19962
20464
  };
19963
20465
 
19964
20466
  // src/install.ts
19965
- import { existsSync as existsSync5 } from "node:fs";
20467
+ import { existsSync as existsSync6 } from "node:fs";
19966
20468
  import { homedir as homedir7, userInfo } from "node:os";
19967
- import { promisify as promisify6 } from "node:util";
20469
+ import { promisify as promisify7 } from "node:util";
19968
20470
  import { fileURLToPath as fileURLToPath2 } from "node:url";
19969
- import { execFile as execFile6, spawn as spawn6 } from "node:child_process";
20471
+ import { execFile as execFile7, spawn as spawn7 } from "node:child_process";
19970
20472
  import { cp, mkdir as mkdir3, rm as rm2, writeFile } from "node:fs/promises";
19971
- import { dirname as dirname7, join as join10, relative, sep } from "node:path";
19972
- var run3 = promisify6(execFile6);
20473
+ import { dirname as dirname7, join as join11, relative, sep } from "node:path";
20474
+ var run4 = promisify7(execFile7);
19973
20475
  var xml = (value2) => value2.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
19974
20476
  var NODE_FLAGS = ["--disable-warning=ExperimentalWarning"];
19975
- var systemctl = (...args2) => run3("systemctl", ["--user", ...args2], {
20477
+ var systemctl = (...args2) => run4("systemctl", ["--user", ...args2], {
19976
20478
  env: { ...process.env, XDG_RUNTIME_DIR: process.env.XDG_RUNTIME_DIR ?? `/run/user/${userInfo().uid}` }
19977
20479
  });
19978
- var serviceFile = () => process.platform === "darwin" ? join10(homedir7(), "Library/LaunchAgents/com.dashup.connector.plist") : process.platform === "win32" ? join10(
19979
- process.env.APPDATA ?? join10(homedir7(), "AppData/Roaming"),
20480
+ var serviceFile = () => process.platform === "darwin" ? join11(homedir7(), "Library/LaunchAgents/com.dashup.connector.plist") : process.platform === "win32" ? join11(
20481
+ process.env.APPDATA ?? join11(homedir7(), "AppData/Roaming"),
19980
20482
  "Microsoft/Windows/Start Menu/Programs/Startup/Dashup.vbs"
19981
- ) : join10(homedir7(), ".config/systemd/user/dashup.service");
19982
- var startupInstalled = () => existsSync5(serviceFile());
19983
- function service(platform, node, entry, config2) {
20483
+ ) : join11(homedir7(), ".config/systemd/user/dashup.service");
20484
+ var startupInstalled = () => existsSync6(serviceFile());
20485
+ function service(platform, node, entry, config3) {
19984
20486
  const command2 = [node, ...NODE_FLAGS, entry];
19985
20487
  if (platform === "darwin")
19986
- return `<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict><key>Label</key><string>com.dashup.connector</string><key>EnvironmentVariables</key><dict><key>DASHUP_CONFIG</key><string>${xml(config2)}</string></dict><key>ProgramArguments</key><array>${[...command2, "watch"].map((value2) => `<string>${xml(value2)}</string>`).join("")}</array><key>RunAtLoad</key><true/><key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict></dict></plist>`;
20488
+ return `<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict><key>Label</key><string>com.dashup.connector</string><key>EnvironmentVariables</key><dict><key>DASHUP_CONFIG</key><string>${xml(config3)}</string></dict><key>ProgramArguments</key><array>${[...command2, "watch"].map((value2) => `<string>${xml(value2)}</string>`).join("")}</array><key>RunAtLoad</key><true/><key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict></dict></plist>`;
19987
20489
  if (platform === "win32") {
19988
20490
  const quote2 = (value2) => '""' + value2.replaceAll('"', '""') + '""';
19989
20491
  return `Set shell = CreateObject("WScript.Shell")\r
19990
- shell.Environment("Process")("DASHUP_CONFIG") = "${config2.replaceAll('"', '""')}"\r
20492
+ shell.Environment("Process")("DASHUP_CONFIG") = "${config3.replaceAll('"', '""')}"\r
19991
20493
  shell.Run "${command2.map(quote2).join(" ")} watch", 0, False\r
19992
20494
  `;
19993
20495
  }
@@ -19997,7 +20499,7 @@ Description=Dashup desktop connector
19997
20499
  After=network-online.target
19998
20500
 
19999
20501
  [Service]
20000
- Environment=${quote(`DASHUP_CONFIG=${config2}`)}
20502
+ Environment=${quote(`DASHUP_CONFIG=${config3}`)}
20001
20503
  ExecStart=${command2.map(quote).join(" ")} watch
20002
20504
  Restart=on-failure
20003
20505
  RestartSec=5
@@ -20014,19 +20516,19 @@ async function durableEntry() {
20014
20516
  );
20015
20517
  if (!["_npx", "dlx"].some((cache2) => entry.includes(`${sep}${cache2}${sep}`))) return entry;
20016
20518
  const root = entry.slice(0, entry.indexOf(`${sep}node_modules${sep}`));
20017
- const app = join10(dirname7(configPath()), "app");
20519
+ const app = join11(dirname7(configPath()), "app");
20018
20520
  await rm2(app, { recursive: true, force: true });
20019
- await cp(join10(root, "node_modules"), join10(app, "node_modules"), {
20521
+ await cp(join11(root, "node_modules"), join11(app, "node_modules"), {
20020
20522
  recursive: true,
20021
20523
  verbatimSymlinks: true
20022
20524
  });
20023
- return join10(app, relative(root, entry));
20525
+ return join11(app, relative(root, entry));
20024
20526
  }
20025
- var claude2 = (...args2) => run3("claude", ["plugin", ...args2], { timeout: 6e4 });
20527
+ var claude2 = (...args2) => run4("claude", ["plugin", ...args2], { timeout: 6e4 });
20026
20528
  async function installClaude(entry) {
20027
20529
  if (process.platform === "win32") return false;
20028
20530
  try {
20029
- await claude2("marketplace", "add", join10(dirname7(entry), "..", "claude"));
20531
+ await claude2("marketplace", "add", join11(dirname7(entry), "..", "claude"));
20030
20532
  await claude2("install", "dashup@dashup", "--scope", "user");
20031
20533
  return true;
20032
20534
  } catch {
@@ -20043,13 +20545,13 @@ async function installStartup() {
20043
20545
  }
20044
20546
  async function start(path) {
20045
20547
  if (process.platform === "darwin") {
20046
- await run3("launchctl", ["unload", path]).catch(() => {
20548
+ await run4("launchctl", ["unload", path]).catch(() => {
20047
20549
  });
20048
- await run3("launchctl", ["load", path]);
20550
+ await run4("launchctl", ["load", path]);
20049
20551
  } else if (process.platform === "win32") {
20050
- spawn6("wscript.exe", [path], { detached: true, stdio: "ignore" }).unref();
20552
+ spawn7("wscript.exe", [path], { detached: true, stdio: "ignore" }).unref();
20051
20553
  } else {
20052
- const note = process.env.DISPLAY || process.env.WAYLAND_DISPLAY ? void 0 : await run3("loginctl", ["enable-linger"]).then(
20554
+ const note = process.env.DISPLAY || process.env.WAYLAND_DISPLAY ? void 0 : await run4("loginctl", ["enable-linger"]).then(
20053
20555
  () => void 0,
20054
20556
  () => `It stops when you log out. To keep it running, run sudo loginctl enable-linger ${userInfo().username}.`
20055
20557
  );
@@ -20065,17 +20567,17 @@ async function uninstallStartup() {
20065
20567
  });
20066
20568
  await claude2("marketplace", "remove", "dashup").catch(() => {
20067
20569
  });
20068
- if (process.platform === "darwin") await run3("launchctl", ["unload", path]).catch(() => {
20570
+ if (process.platform === "darwin") await run4("launchctl", ["unload", path]).catch(() => {
20069
20571
  });
20070
20572
  else if (process.platform !== "win32")
20071
20573
  await systemctl("disable", "dashup.service").catch(() => {
20072
20574
  });
20073
20575
  await rm2(path, { force: true });
20074
- await rm2(join10(dirname7(configPath()), "app"), { recursive: true, force: true });
20576
+ await rm2(join11(dirname7(configPath()), "app"), { recursive: true, force: true });
20075
20577
  }
20076
20578
 
20077
20579
  // src/index.ts
20078
- var config = loadConfig();
20580
+ var config2 = loadConfig();
20079
20581
  var args = process.argv.slice(2);
20080
20582
  var command = args[0] ?? "watch";
20081
20583
  var option = (name) => {
@@ -20084,7 +20586,7 @@ var option = (name) => {
20084
20586
  };
20085
20587
  var running = (path, init) => fetch(`http://127.0.0.1:${connectorPort()}${path}`, {
20086
20588
  ...init,
20087
- headers: { "Content-Type": "application/json", Authorization: `Bearer ${config.localSecret}` }
20589
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${config2.localSecret}` }
20088
20590
  });
20089
20591
  var refused = (error) => {
20090
20592
  if (error.cause?.code === "ECONNREFUSED") return null;
@@ -20127,7 +20629,7 @@ async function background() {
20127
20629
  return false;
20128
20630
  }
20129
20631
  var authorized = (request2) => {
20130
- const expected = Buffer.from(`Bearer ${config.localSecret}`);
20632
+ const expected = Buffer.from(`Bearer ${config2.localSecret}`);
20131
20633
  const actual = Buffer.from(request2.headers.authorization ?? "");
20132
20634
  return expected.length === actual.length && timingSafeEqual(expected, actual);
20133
20635
  };
@@ -20145,15 +20647,15 @@ async function main() {
20145
20647
  throw new Error("Use an HTTPS API origin");
20146
20648
  if (url2.username || url2.password || url2.search || url2.hash || url2.pathname !== "/")
20147
20649
  throw new Error("Use the API origin without a path");
20148
- if (config.secret && config.api !== url2.origin)
20650
+ if (config2.secret && config2.api !== url2.origin)
20149
20651
  throw new Error("Use a separate DASHUP_CONFIG file to connect a different server");
20150
- config.api = url2.origin;
20652
+ config2.api = url2.origin;
20151
20653
  }
20152
20654
  const account = option("--account");
20153
20655
  if (account !== void 0 && !/^[^\s@]+@[^\s@]+$/.test(account))
20154
20656
  throw new Error("Use --account with the email you sign in to the Dashup app with");
20155
20657
  if (command === "install") {
20156
- if (!config.secret) throw new Error("Pair this computer first: run dashup pair.");
20658
+ if (!config2.secret) throw new Error("Pair this computer first: run dashup pair.");
20157
20659
  if (!await background()) process.exitCode = 1;
20158
20660
  return;
20159
20661
  }
@@ -20175,10 +20677,10 @@ async function main() {
20175
20677
  const [executable, ...agentArgs] = native;
20176
20678
  if (!executable)
20177
20679
  throw new Error("Usage: dashup acp -- <original-agent-command> [arguments...]");
20178
- return proxyAcp(config, executable, agentArgs);
20680
+ return proxyAcp(config2, executable, agentArgs);
20179
20681
  }
20180
20682
  if (command === "doctor" || command === "projects") {
20181
- const discovery = new Discovery(config.device?.id ?? "local", config.localSecret);
20683
+ const discovery = new Discovery(config2.device?.id ?? "local", config2.localSecret);
20182
20684
  discovery.scan();
20183
20685
  const live = new LiveHarnesses();
20184
20686
  try {
@@ -20191,7 +20693,7 @@ async function main() {
20191
20693
  JSON.stringify(
20192
20694
  command === "doctor" ? {
20193
20695
  node: process.version,
20194
- paired: Boolean(config.secret),
20696
+ paired: Boolean(config2.secret),
20195
20697
  platform: process.platform,
20196
20698
  projectCount: projects.length,
20197
20699
  harnesses: Object.fromEntries(
@@ -20211,7 +20713,7 @@ async function main() {
20211
20713
  return;
20212
20714
  }
20213
20715
  if (command === "alert") {
20214
- if (!config.localSecret) throw new Error("Start dashup watch on this computer first");
20716
+ if (!config2.localSecret) throw new Error("Start dashup watch on this computer first");
20215
20717
  const input = Attention.parse({
20216
20718
  projectId: option("--project"),
20217
20719
  threadId: option("--thread") ?? "",
@@ -20230,7 +20732,7 @@ async function main() {
20230
20732
  );
20231
20733
  return;
20232
20734
  }
20233
- const pairPhone = (invite) => scanned(config, invite, (email) => `Paired a phone signed in as ${email}`);
20735
+ const pairPhone = (invite) => scanned(config2, invite, (email) => `Paired a phone signed in as ${email}`);
20234
20736
  const keepRunning = async (stopHere) => {
20235
20737
  const answer = startupInstalled() || interactive && await ui2.confirm({ message: BACKGROUND }) === true;
20236
20738
  if (answer) {
@@ -20269,7 +20771,7 @@ async function main() {
20269
20771
  if (interactive) ui2.outro(DONE2);
20270
20772
  process.exit();
20271
20773
  }
20272
- if (!config.secret && !interactive && !account)
20774
+ if (!config2.secret && !interactive && !account)
20273
20775
  throw new Error(
20274
20776
  "To pair this computer, run dashup pair in a terminal, or add --account with your Dashup email."
20275
20777
  );
@@ -20333,10 +20835,10 @@ async function main() {
20333
20835
  server.once("error", reject);
20334
20836
  server.listen(connectorPort(), "127.0.0.1", resolve3);
20335
20837
  });
20336
- const selectedApi = config.api;
20337
- Object.assign(config, loadConfig(), { api: selectedApi });
20338
- config.localSecret ||= newSecret(randomBytes5);
20339
- saveConfig(config);
20838
+ const selectedApi = config2.api;
20839
+ Object.assign(config2, loadConfig(), { api: selectedApi });
20840
+ config2.localSecret ||= newSecret(randomBytes5);
20841
+ saveConfig(config2);
20340
20842
  for (const signal of ["SIGINT", "SIGTERM"])
20341
20843
  process.once(signal, () => {
20342
20844
  connector?.stop();
@@ -20344,13 +20846,13 @@ async function main() {
20344
20846
  process.exit(0);
20345
20847
  });
20346
20848
  try {
20347
- const linking = !config.secret;
20849
+ const linking = !config2.secret;
20348
20850
  if (linking) {
20349
20851
  intro2();
20350
- if (!await link(config, account)) process.exit(1);
20852
+ if (!await link(config2, account)) process.exit(1);
20351
20853
  await keepRunning(() => server.close());
20352
20854
  }
20353
- connector = new Connector(config);
20855
+ connector = new Connector(config2);
20354
20856
  await connector.start();
20355
20857
  if (command === "pair" && !linking) {
20356
20858
  if (interactive) intro2();