@dashup/connector 0.1.18 → 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.
- package/README.md +6 -6
- package/dist/index.js +629 -192
- package/package.json +1 -1
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
|
|
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 =
|
|
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"
|
|
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(
|
|
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(
|
|
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(
|
|
3671
|
+
async function api(config3, path, body2, method = body2 ? "POST" : "GET") {
|
|
3672
3672
|
const bytes = body2 instanceof Uint8Array;
|
|
3673
|
-
const response = await fetch(`${
|
|
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 ${
|
|
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(
|
|
3687
|
-
return Boolean(!pair.revokedAt &&
|
|
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(
|
|
3872
|
-
if (!
|
|
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 ${
|
|
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.
|
|
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(
|
|
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:
|
|
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(
|
|
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(`${
|
|
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(
|
|
4143
|
+
async function link(config3, account) {
|
|
4144
4144
|
const secret = newSecret(randomBytes2);
|
|
4145
|
-
const invite = invitation(
|
|
4145
|
+
const invite = invitation(config3, secret);
|
|
4146
4146
|
const { pairing, email } = await scanned(
|
|
4147
|
-
|
|
4147
|
+
config3,
|
|
4148
4148
|
invite,
|
|
4149
4149
|
(account2) => `Scanned by ${account2}`,
|
|
4150
4150
|
secret
|
|
4151
4151
|
);
|
|
4152
|
-
const linked = { ...
|
|
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(
|
|
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(
|
|
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
|
|
4851
|
-
const task = typeof
|
|
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(
|
|
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
|
|
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(
|
|
15516
|
+
constructor(config3) {
|
|
15431
15517
|
super();
|
|
15432
|
-
this.url =
|
|
15433
|
-
this.version =
|
|
15434
|
-
this.getToken =
|
|
15435
|
-
this.handshakeTimeout =
|
|
15436
|
-
const socketPath =
|
|
15437
|
-
const transports2 =
|
|
15438
|
-
const extraHeaders =
|
|
15439
|
-
const autoConnect =
|
|
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(
|
|
15959
|
+
function createClient(config3) {
|
|
15874
15960
|
const transport = new SocketTransport({
|
|
15875
|
-
url:
|
|
15876
|
-
version:
|
|
15877
|
-
getToken:
|
|
15878
|
-
transports:
|
|
15879
|
-
extraHeaders:
|
|
15880
|
-
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(
|
|
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(
|
|
16485
|
+
return await api(config3, HISTORY_PATH, batch);
|
|
16400
16486
|
} catch {
|
|
16401
|
-
return api(
|
|
16487
|
+
return api(config3, HISTORY_PATH, batch);
|
|
16402
16488
|
}
|
|
16403
16489
|
}
|
|
16404
|
-
async function publishHistory(
|
|
16405
|
-
if (!activePair(
|
|
16406
|
-
const local =
|
|
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(
|
|
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(
|
|
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(
|
|
16450
|
-
const published = await upload(
|
|
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(
|
|
16488
|
-
if (!activePair(
|
|
16489
|
-
const 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(
|
|
16584
|
+
await api(config3, `${ATTACHMENTS_PATH}/${pair.id}/${id}`, sealed);
|
|
16499
16585
|
}
|
|
16500
16586
|
|
|
16501
16587
|
// src/harness/live.ts
|
|
16502
|
-
import { promisify as
|
|
16503
|
-
import { execFile as
|
|
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
|
|
18034
|
+
import { promisify as promisify4 } from "node:util";
|
|
17603
18035
|
import { fileURLToPath } from "node:url";
|
|
17604
|
-
import { execFile as
|
|
17605
|
-
import { createHash as
|
|
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
|
|
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
|
|
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" ?
|
|
17628
|
-
return ["Code", "Code - Insiders"].map((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
|
|
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 =
|
|
17658
|
-
const files2 = (await readdir3(
|
|
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) =>
|
|
17661
|
-
|
|
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 =
|
|
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-${
|
|
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:${
|
|
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
|
|
18099
|
-
import { promisify as
|
|
18100
|
-
import { createHash as
|
|
18101
|
-
import { execFile as
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
18157
|
-
const target = await readlink2(
|
|
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
|
|
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
|
|
18632
|
+
const { stdout } = await run3(
|
|
18201
18633
|
"powershell.exe",
|
|
18202
18634
|
["-NoProfile", "-NonInteractive", "-Command", script],
|
|
18203
18635
|
{ timeout: REQUEST_MS3, maxBuffer: MAX_BYTES3 }
|
|
@@ -18478,7 +18910,7 @@ var Server = class {
|
|
|
18478
18910
|
if (this.stopped) throw new Error("OpenCode is disconnected");
|
|
18479
18911
|
if (!text3.trim() || Buffer.byteLength(text3) > MAX_BYTES3 / 2)
|
|
18480
18912
|
throw new Error("Message is empty or too large");
|
|
18481
|
-
const messageID = `msg_${
|
|
18913
|
+
const messageID = `msg_${createHash5("sha256").update(requestId).digest("hex").slice(0, 32)}`;
|
|
18482
18914
|
const existing = await this.request(
|
|
18483
18915
|
`/session/${encodeURIComponent(id)}/message?limit=200`,
|
|
18484
18916
|
cwd
|
|
@@ -18622,7 +19054,7 @@ var Server = class {
|
|
|
18622
19054
|
}
|
|
18623
19055
|
};
|
|
18624
19056
|
function serve(cwd) {
|
|
18625
|
-
const child =
|
|
19057
|
+
const child = spawn5("opencode", ["serve", "--hostname", "127.0.0.1", "--port", "0"], {
|
|
18626
19058
|
cwd,
|
|
18627
19059
|
stdio: ["ignore", "pipe", "ignore"],
|
|
18628
19060
|
shell: process.platform === "win32"
|
|
@@ -18710,6 +19142,7 @@ var LiveHarnesses = class {
|
|
|
18710
19142
|
listeners = /* @__PURE__ */ new Set();
|
|
18711
19143
|
codex = new Codex(() => this.changed());
|
|
18712
19144
|
headless = new Headless(() => this.changed());
|
|
19145
|
+
kimi = new Kimi(() => this.changed());
|
|
18713
19146
|
claude = new Claude(() => this.changed());
|
|
18714
19147
|
vscode = new VSCode(() => this.changed());
|
|
18715
19148
|
opencode = new OpenCode(() => this.changed());
|
|
@@ -18725,6 +19158,7 @@ var LiveHarnesses = class {
|
|
|
18725
19158
|
this.refreshing = (async () => {
|
|
18726
19159
|
const results = await Promise.allSettled([
|
|
18727
19160
|
this.headless.refresh(),
|
|
19161
|
+
this.kimi.refresh(),
|
|
18728
19162
|
this.opencode.refresh(),
|
|
18729
19163
|
this.codex.refresh(),
|
|
18730
19164
|
this.claude.refresh(),
|
|
@@ -18747,14 +19181,16 @@ var LiveHarnesses = class {
|
|
|
18747
19181
|
recorded(harness, settings) {
|
|
18748
19182
|
const changed = () => this.changed();
|
|
18749
19183
|
if (harness === "claude") return claudeOptions(claudeModels(changed), settings);
|
|
19184
|
+
if (harness === "kimi") return kimiOptions(settings);
|
|
18750
19185
|
return harness === "codex" ? codexOptions(settings) : [];
|
|
18751
19186
|
}
|
|
18752
19187
|
// The agents whose own command runs on this computer. Claude Code is asked its models again.
|
|
18753
19188
|
async agents() {
|
|
18754
19189
|
const harnesses = ["claude", "codex", "opencode"];
|
|
19190
|
+
const kimi2 = kimiCommand();
|
|
18755
19191
|
forgetModels();
|
|
18756
19192
|
const runs = await Promise.all(
|
|
18757
|
-
harnesses.map((harness) =>
|
|
19193
|
+
harnesses.map((harness) => promisify6(execFile6)(harness, ["--version"], {
|
|
18758
19194
|
timeout: 1e4,
|
|
18759
19195
|
shell: process.platform === "win32"
|
|
18760
19196
|
}).then(
|
|
@@ -18762,22 +19198,22 @@ var LiveHarnesses = class {
|
|
|
18762
19198
|
() => false
|
|
18763
19199
|
))
|
|
18764
19200
|
);
|
|
18765
|
-
return
|
|
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];
|
|
18766
19209
|
}
|
|
18767
19210
|
// Starts a session in `cwd` with its first message, and resolves with the session's native id.
|
|
18768
19211
|
start(harness, cwd, text3) {
|
|
18769
|
-
return
|
|
18770
|
-
cwd,
|
|
18771
|
-
text3
|
|
18772
|
-
);
|
|
19212
|
+
return this.host(harness).start(cwd, text3);
|
|
18773
19213
|
}
|
|
18774
19214
|
// Continues a saved session with a message, and resolves once its agent takes it.
|
|
18775
19215
|
resume(harness, id, cwd, text3) {
|
|
18776
|
-
return
|
|
18777
|
-
id,
|
|
18778
|
-
cwd,
|
|
18779
|
-
text3
|
|
18780
|
-
);
|
|
19216
|
+
return this.host(harness).resume(id, cwd, text3);
|
|
18781
19217
|
}
|
|
18782
19218
|
watch(onChange) {
|
|
18783
19219
|
this.listeners.add(onChange);
|
|
@@ -18802,6 +19238,7 @@ var LiveHarnesses = class {
|
|
|
18802
19238
|
clearInterval(this.timer);
|
|
18803
19239
|
this.listeners.clear();
|
|
18804
19240
|
this.headless.stop();
|
|
19241
|
+
this.kimi.stop();
|
|
18805
19242
|
this.opencode.stop();
|
|
18806
19243
|
this.codex.stop();
|
|
18807
19244
|
this.claude.stop();
|
|
@@ -18810,11 +19247,11 @@ var LiveHarnesses = class {
|
|
|
18810
19247
|
};
|
|
18811
19248
|
|
|
18812
19249
|
// src/harness/queue.ts
|
|
18813
|
-
import { randomUUID as
|
|
18814
|
-
import { spawn as
|
|
19250
|
+
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
19251
|
+
import { spawn as spawn6 } from "node:child_process";
|
|
18815
19252
|
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
18816
|
-
import { join as
|
|
18817
|
-
import { statSync as
|
|
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";
|
|
18818
19255
|
var CODEX_VERSION = "0.160.1";
|
|
18819
19256
|
var REQUEST_MS4 = 1e4;
|
|
18820
19257
|
var MAX_BYTES4 = 1024 * 1024;
|
|
@@ -18823,7 +19260,7 @@ var MAX_FILES = 4096;
|
|
|
18823
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;
|
|
18824
19261
|
function identity(path) {
|
|
18825
19262
|
const canonical2 = realpathSync2.native(path);
|
|
18826
|
-
const file =
|
|
19263
|
+
const file = statSync4(canonical2);
|
|
18827
19264
|
if (!file.isFile() || process.getuid && file.uid !== process.getuid())
|
|
18828
19265
|
throw new Error("Native queue files are not owned by this user");
|
|
18829
19266
|
return { path: canonical2, device: file.dev, inode: file.ino };
|
|
@@ -18834,25 +19271,25 @@ function sameFile(left, right) {
|
|
|
18834
19271
|
function packageVersion(executable) {
|
|
18835
19272
|
let directory2 = dirname6(executable);
|
|
18836
19273
|
for (let depth = 0; depth < 8; depth++, directory2 = dirname6(directory2)) {
|
|
18837
|
-
const path =
|
|
18838
|
-
if (!
|
|
18839
|
-
const value2 = JSON.parse(
|
|
19274
|
+
const path = join9(directory2, "package.json");
|
|
19275
|
+
if (!existsSync5(path)) continue;
|
|
19276
|
+
const value2 = JSON.parse(readFileSync6(path, "utf8"));
|
|
18840
19277
|
if (value2.name?.startsWith("@openai/codex-")) return value2.version;
|
|
18841
19278
|
}
|
|
18842
19279
|
return void 0;
|
|
18843
19280
|
}
|
|
18844
19281
|
function processOwners(pid) {
|
|
18845
19282
|
const root = `/proc/${pid}`;
|
|
18846
|
-
if (process.getuid &&
|
|
18847
|
-
const executable = realpathSync2.native(
|
|
19283
|
+
if (process.getuid && statSync4(root).uid !== process.getuid()) return [];
|
|
19284
|
+
const executable = realpathSync2.native(join9(root, "exe"));
|
|
18848
19285
|
if (basename4(executable) !== "codex" || packageVersion(executable) !== CODEX_VERSION) return [];
|
|
18849
|
-
const status2 =
|
|
19286
|
+
const status2 = readFileSync6(join9(root, "stat"), "utf8");
|
|
18850
19287
|
const start2 = status2.slice(status2.lastIndexOf(")") + 2).split(" ")[19];
|
|
18851
19288
|
if (!start2 || !/^\d+$/.test(start2)) return [];
|
|
18852
|
-
const fds =
|
|
19289
|
+
const fds = readdirSync4(join9(root, "fd")).slice(0, MAX_FILES);
|
|
18853
19290
|
const files2 = fds.flatMap((fd) => {
|
|
18854
19291
|
try {
|
|
18855
|
-
return [{ fd, path: readlinkSync(
|
|
19292
|
+
return [{ fd, path: readlinkSync(join9(root, "fd", fd)) }];
|
|
18856
19293
|
} catch {
|
|
18857
19294
|
return [];
|
|
18858
19295
|
}
|
|
@@ -18862,22 +19299,22 @@ function processOwners(pid) {
|
|
|
18862
19299
|
const thread = basename4(path, ".lock");
|
|
18863
19300
|
if (!THREAD_ID.test(thread)) return [];
|
|
18864
19301
|
try {
|
|
18865
|
-
const lock =
|
|
19302
|
+
const lock = readFileSync6(join9(root, "fdinfo", fd), "utf8");
|
|
18866
19303
|
if (!/FLOCK\s+ADVISORY\s+WRITE/.test(lock)) return [];
|
|
18867
19304
|
const home = realpathSync2.native(dirname6(dirname6(path)));
|
|
18868
|
-
const directory2 =
|
|
19305
|
+
const directory2 = statSync4(home);
|
|
18869
19306
|
if (process.getuid && directory2.uid !== process.getuid()) return [];
|
|
18870
|
-
const queue = identity(
|
|
19307
|
+
const queue = identity(join9(home, "queue_1.sqlite"));
|
|
18871
19308
|
if (!files2.some((file) => {
|
|
18872
19309
|
try {
|
|
18873
|
-
return sameFile(identity(
|
|
19310
|
+
return sameFile(identity(join9(root, "fd", file.fd)), queue);
|
|
18874
19311
|
} catch {
|
|
18875
19312
|
return false;
|
|
18876
19313
|
}
|
|
18877
19314
|
}))
|
|
18878
19315
|
return [];
|
|
18879
19316
|
const rollout = files2.find(
|
|
18880
|
-
(file) => file.path.startsWith(
|
|
19317
|
+
(file) => file.path.startsWith(join9(home, "sessions") + "/") && file.path.endsWith(`-${thread}.jsonl`)
|
|
18881
19318
|
);
|
|
18882
19319
|
if (!rollout) return [];
|
|
18883
19320
|
return [
|
|
@@ -18889,7 +19326,7 @@ function processOwners(pid) {
|
|
|
18889
19326
|
thread,
|
|
18890
19327
|
queue,
|
|
18891
19328
|
version: CODEX_VERSION,
|
|
18892
|
-
cwd: realpathSync2.native(
|
|
19329
|
+
cwd: realpathSync2.native(join9(root, "cwd")),
|
|
18893
19330
|
rollout: identity(rollout.path)
|
|
18894
19331
|
}
|
|
18895
19332
|
];
|
|
@@ -18901,7 +19338,7 @@ function processOwners(pid) {
|
|
|
18901
19338
|
function queueOwners() {
|
|
18902
19339
|
if (process.platform !== "linux" || process.env.DASHUP_LIVE_DISCOVERY === "0") return [];
|
|
18903
19340
|
try {
|
|
18904
|
-
return
|
|
19341
|
+
return readdirSync4("/proc").filter((value2) => /^\d+$/.test(value2)).slice(0, MAX_PROCESSES3).flatMap((value2) => {
|
|
18905
19342
|
try {
|
|
18906
19343
|
return processOwners(Number(value2));
|
|
18907
19344
|
} catch {
|
|
@@ -18933,10 +19370,10 @@ function validateOwner(owner) {
|
|
|
18933
19370
|
throw new Error("The original native conversation owner is no longer available");
|
|
18934
19371
|
}
|
|
18935
19372
|
function validateEvidence(owner) {
|
|
18936
|
-
if (realpathSync2.native(owner.home) !== owner.home || process.getuid &&
|
|
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))
|
|
18937
19374
|
throw new Error("The original native history storage changed");
|
|
18938
|
-
const path =
|
|
18939
|
-
if (
|
|
19375
|
+
const path = join9(owner.home, "thread_history_1.sqlite");
|
|
19376
|
+
if (existsSync5(path) && identity(path).path !== path)
|
|
18940
19377
|
throw new Error("Native history is outside the original home");
|
|
18941
19378
|
}
|
|
18942
19379
|
function nativeReceipt(owner, clientId) {
|
|
@@ -18952,8 +19389,8 @@ function nativeReceipt(owner, clientId) {
|
|
|
18952
19389
|
} finally {
|
|
18953
19390
|
queue.close();
|
|
18954
19391
|
}
|
|
18955
|
-
const path =
|
|
18956
|
-
if (!
|
|
19392
|
+
const path = join9(owner.home, "thread_history_1.sqlite");
|
|
19393
|
+
if (!existsSync5(path)) return { queueId };
|
|
18957
19394
|
const history = new DatabaseSync3(path, { readOnly: true });
|
|
18958
19395
|
try {
|
|
18959
19396
|
const rows = history.prepare(
|
|
@@ -19008,7 +19445,7 @@ var Writer = class {
|
|
|
19008
19445
|
tail = "";
|
|
19009
19446
|
pending = /* @__PURE__ */ new Map();
|
|
19010
19447
|
constructor(owner) {
|
|
19011
|
-
this.process =
|
|
19448
|
+
this.process = spawn6(owner.executable, ["app-server", "--listen", "stdio://"], {
|
|
19012
19449
|
cwd: owner.cwd,
|
|
19013
19450
|
env: { ...process.env, CODEX_HOME: owner.home },
|
|
19014
19451
|
stdio: ["pipe", "pipe", "ignore"]
|
|
@@ -19095,7 +19532,7 @@ var NativeQueue = class {
|
|
|
19095
19532
|
if (existing) return existing;
|
|
19096
19533
|
if (this.stopped) throw new Error("The connector is stopping");
|
|
19097
19534
|
validateOwner(scope2.owner);
|
|
19098
|
-
const clientUserMessageId =
|
|
19535
|
+
const clientUserMessageId = randomUUID7();
|
|
19099
19536
|
const request2 = { ...scope2, receipt: { state: "unknown", clientUserMessageId } };
|
|
19100
19537
|
let writer;
|
|
19101
19538
|
try {
|
|
@@ -19203,32 +19640,32 @@ var Command = z2.object({
|
|
|
19203
19640
|
z2.object({ kind: z2.literal("attachment.read"), id: z2.string().regex(/^[a-f0-9]{64}$/) }).strict(),
|
|
19204
19641
|
z2.object({
|
|
19205
19642
|
kind: z2.literal("session.start"),
|
|
19206
|
-
harness: z2.enum(["claude", "codex", "opencode"]),
|
|
19643
|
+
harness: z2.enum(["claude", "codex", "kimi", "opencode"]),
|
|
19207
19644
|
text: z2.string().min(1).max(1e5),
|
|
19208
19645
|
folder: z2.object({ parent: z2.string().min(1), name: z2.string().min(1).max(255) }).strict().optional()
|
|
19209
19646
|
}).strict()
|
|
19210
19647
|
])
|
|
19211
19648
|
}).strict();
|
|
19212
19649
|
var Connector = class {
|
|
19213
|
-
constructor(
|
|
19214
|
-
this.config =
|
|
19215
|
-
|
|
19216
|
-
|
|
19650
|
+
constructor(config3) {
|
|
19651
|
+
this.config = config3;
|
|
19652
|
+
config3.unconfirmed ??= {};
|
|
19653
|
+
config3.queued ??= {};
|
|
19217
19654
|
this.nativeQueue = new NativeQueue(
|
|
19218
|
-
|
|
19219
|
-
() => saveConfig(
|
|
19655
|
+
config3.queued,
|
|
19656
|
+
() => saveConfig(config3),
|
|
19220
19657
|
(request2) => this.isActive(request2.pairing)
|
|
19221
19658
|
);
|
|
19222
|
-
if (!
|
|
19659
|
+
if (!config3.device || !config3.secret) throw new Error("Pair this computer with dashup pair first");
|
|
19223
19660
|
this.client = createClient({
|
|
19224
|
-
url:
|
|
19225
|
-
getToken: async () =>
|
|
19661
|
+
url: config3.api,
|
|
19662
|
+
getToken: async () => config3.secret,
|
|
19226
19663
|
autoConnect: false
|
|
19227
19664
|
});
|
|
19228
19665
|
provideClient(() => this.client);
|
|
19229
|
-
this.discovery = new Discovery(
|
|
19230
|
-
this.pairings = createLiveQuery(() => this.client.bind(Pairing).where({ device:
|
|
19231
|
-
this.relays = createLiveQuery(() => this.client.bind(Relay).where({ recipient:
|
|
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());
|
|
19232
19669
|
}
|
|
19233
19670
|
config;
|
|
19234
19671
|
client;
|
|
@@ -19426,7 +19863,7 @@ var Connector = class {
|
|
|
19426
19863
|
saveConfig(this.config);
|
|
19427
19864
|
}
|
|
19428
19865
|
flush() {
|
|
19429
|
-
const
|
|
19866
|
+
const run5 = this.drain.catch(() => {
|
|
19430
19867
|
}).then(async () => {
|
|
19431
19868
|
this.discardLegacySnapshots();
|
|
19432
19869
|
while (this.config.outbox.length && this.client.isConnected) {
|
|
@@ -19436,8 +19873,8 @@ var Connector = class {
|
|
|
19436
19873
|
saveConfig(this.config);
|
|
19437
19874
|
}
|
|
19438
19875
|
});
|
|
19439
|
-
this.drain =
|
|
19440
|
-
return
|
|
19876
|
+
this.drain = run5;
|
|
19877
|
+
return run5.catch((error) => {
|
|
19441
19878
|
this.report(error);
|
|
19442
19879
|
clearTimeout(this.retry);
|
|
19443
19880
|
this.retry = setTimeout(() => {
|
|
@@ -19511,13 +19948,13 @@ var Connector = class {
|
|
|
19511
19948
|
// transcript costs one rescan rather than one each.
|
|
19512
19949
|
queueChange() {
|
|
19513
19950
|
if (this.pendingChange) return this.pendingChange;
|
|
19514
|
-
const
|
|
19951
|
+
const run5 = this.work.then(() => {
|
|
19515
19952
|
this.pendingChange = void 0;
|
|
19516
19953
|
return this.changed();
|
|
19517
19954
|
});
|
|
19518
|
-
this.pendingChange =
|
|
19519
|
-
this.work =
|
|
19520
|
-
return
|
|
19955
|
+
this.pendingChange = run5;
|
|
19956
|
+
this.work = run5.catch((error) => this.report(error));
|
|
19957
|
+
return run5;
|
|
19521
19958
|
}
|
|
19522
19959
|
async changed() {
|
|
19523
19960
|
if (this.stopped) return;
|
|
@@ -20001,7 +20438,7 @@ var Connector = class {
|
|
|
20001
20438
|
throw new Error("That folder is no longer offered for new projects");
|
|
20002
20439
|
if (!name || name === "." || name === ".." || /[\\/:*?"<>|\x00-\x1f]/.test(name))
|
|
20003
20440
|
throw new Error("Name the folder without slashes or special characters");
|
|
20004
|
-
cwd =
|
|
20441
|
+
cwd = join10(folder.parent, name);
|
|
20005
20442
|
await mkdir2(cwd).catch((error) => {
|
|
20006
20443
|
throw new Error(error.code === "EEXIST" ? `${name} already exists` : `Couldn't create ${name}`);
|
|
20007
20444
|
});
|
|
@@ -20027,32 +20464,32 @@ var Connector = class {
|
|
|
20027
20464
|
};
|
|
20028
20465
|
|
|
20029
20466
|
// src/install.ts
|
|
20030
|
-
import { existsSync as
|
|
20467
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
20031
20468
|
import { homedir as homedir7, userInfo } from "node:os";
|
|
20032
|
-
import { promisify as
|
|
20469
|
+
import { promisify as promisify7 } from "node:util";
|
|
20033
20470
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
20034
|
-
import { execFile as
|
|
20471
|
+
import { execFile as execFile7, spawn as spawn7 } from "node:child_process";
|
|
20035
20472
|
import { cp, mkdir as mkdir3, rm as rm2, writeFile } from "node:fs/promises";
|
|
20036
|
-
import { dirname as dirname7, join as
|
|
20037
|
-
var
|
|
20473
|
+
import { dirname as dirname7, join as join11, relative, sep } from "node:path";
|
|
20474
|
+
var run4 = promisify7(execFile7);
|
|
20038
20475
|
var xml = (value2) => value2.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
20039
20476
|
var NODE_FLAGS = ["--disable-warning=ExperimentalWarning"];
|
|
20040
|
-
var systemctl = (...args2) =>
|
|
20477
|
+
var systemctl = (...args2) => run4("systemctl", ["--user", ...args2], {
|
|
20041
20478
|
env: { ...process.env, XDG_RUNTIME_DIR: process.env.XDG_RUNTIME_DIR ?? `/run/user/${userInfo().uid}` }
|
|
20042
20479
|
});
|
|
20043
|
-
var serviceFile = () => process.platform === "darwin" ?
|
|
20044
|
-
process.env.APPDATA ??
|
|
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"),
|
|
20045
20482
|
"Microsoft/Windows/Start Menu/Programs/Startup/Dashup.vbs"
|
|
20046
|
-
) :
|
|
20047
|
-
var startupInstalled = () =>
|
|
20048
|
-
function service(platform, node, entry,
|
|
20483
|
+
) : join11(homedir7(), ".config/systemd/user/dashup.service");
|
|
20484
|
+
var startupInstalled = () => existsSync6(serviceFile());
|
|
20485
|
+
function service(platform, node, entry, config3) {
|
|
20049
20486
|
const command2 = [node, ...NODE_FLAGS, entry];
|
|
20050
20487
|
if (platform === "darwin")
|
|
20051
|
-
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(
|
|
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>`;
|
|
20052
20489
|
if (platform === "win32") {
|
|
20053
20490
|
const quote2 = (value2) => '""' + value2.replaceAll('"', '""') + '""';
|
|
20054
20491
|
return `Set shell = CreateObject("WScript.Shell")\r
|
|
20055
|
-
shell.Environment("Process")("DASHUP_CONFIG") = "${
|
|
20492
|
+
shell.Environment("Process")("DASHUP_CONFIG") = "${config3.replaceAll('"', '""')}"\r
|
|
20056
20493
|
shell.Run "${command2.map(quote2).join(" ")} watch", 0, False\r
|
|
20057
20494
|
`;
|
|
20058
20495
|
}
|
|
@@ -20062,7 +20499,7 @@ Description=Dashup desktop connector
|
|
|
20062
20499
|
After=network-online.target
|
|
20063
20500
|
|
|
20064
20501
|
[Service]
|
|
20065
|
-
Environment=${quote(`DASHUP_CONFIG=${
|
|
20502
|
+
Environment=${quote(`DASHUP_CONFIG=${config3}`)}
|
|
20066
20503
|
ExecStart=${command2.map(quote).join(" ")} watch
|
|
20067
20504
|
Restart=on-failure
|
|
20068
20505
|
RestartSec=5
|
|
@@ -20079,19 +20516,19 @@ async function durableEntry() {
|
|
|
20079
20516
|
);
|
|
20080
20517
|
if (!["_npx", "dlx"].some((cache2) => entry.includes(`${sep}${cache2}${sep}`))) return entry;
|
|
20081
20518
|
const root = entry.slice(0, entry.indexOf(`${sep}node_modules${sep}`));
|
|
20082
|
-
const app =
|
|
20519
|
+
const app = join11(dirname7(configPath()), "app");
|
|
20083
20520
|
await rm2(app, { recursive: true, force: true });
|
|
20084
|
-
await cp(
|
|
20521
|
+
await cp(join11(root, "node_modules"), join11(app, "node_modules"), {
|
|
20085
20522
|
recursive: true,
|
|
20086
20523
|
verbatimSymlinks: true
|
|
20087
20524
|
});
|
|
20088
|
-
return
|
|
20525
|
+
return join11(app, relative(root, entry));
|
|
20089
20526
|
}
|
|
20090
|
-
var claude2 = (...args2) =>
|
|
20527
|
+
var claude2 = (...args2) => run4("claude", ["plugin", ...args2], { timeout: 6e4 });
|
|
20091
20528
|
async function installClaude(entry) {
|
|
20092
20529
|
if (process.platform === "win32") return false;
|
|
20093
20530
|
try {
|
|
20094
|
-
await claude2("marketplace", "add",
|
|
20531
|
+
await claude2("marketplace", "add", join11(dirname7(entry), "..", "claude"));
|
|
20095
20532
|
await claude2("install", "dashup@dashup", "--scope", "user");
|
|
20096
20533
|
return true;
|
|
20097
20534
|
} catch {
|
|
@@ -20108,13 +20545,13 @@ async function installStartup() {
|
|
|
20108
20545
|
}
|
|
20109
20546
|
async function start(path) {
|
|
20110
20547
|
if (process.platform === "darwin") {
|
|
20111
|
-
await
|
|
20548
|
+
await run4("launchctl", ["unload", path]).catch(() => {
|
|
20112
20549
|
});
|
|
20113
|
-
await
|
|
20550
|
+
await run4("launchctl", ["load", path]);
|
|
20114
20551
|
} else if (process.platform === "win32") {
|
|
20115
|
-
|
|
20552
|
+
spawn7("wscript.exe", [path], { detached: true, stdio: "ignore" }).unref();
|
|
20116
20553
|
} else {
|
|
20117
|
-
const note = process.env.DISPLAY || process.env.WAYLAND_DISPLAY ? void 0 : await
|
|
20554
|
+
const note = process.env.DISPLAY || process.env.WAYLAND_DISPLAY ? void 0 : await run4("loginctl", ["enable-linger"]).then(
|
|
20118
20555
|
() => void 0,
|
|
20119
20556
|
() => `It stops when you log out. To keep it running, run sudo loginctl enable-linger ${userInfo().username}.`
|
|
20120
20557
|
);
|
|
@@ -20130,17 +20567,17 @@ async function uninstallStartup() {
|
|
|
20130
20567
|
});
|
|
20131
20568
|
await claude2("marketplace", "remove", "dashup").catch(() => {
|
|
20132
20569
|
});
|
|
20133
|
-
if (process.platform === "darwin") await
|
|
20570
|
+
if (process.platform === "darwin") await run4("launchctl", ["unload", path]).catch(() => {
|
|
20134
20571
|
});
|
|
20135
20572
|
else if (process.platform !== "win32")
|
|
20136
20573
|
await systemctl("disable", "dashup.service").catch(() => {
|
|
20137
20574
|
});
|
|
20138
20575
|
await rm2(path, { force: true });
|
|
20139
|
-
await rm2(
|
|
20576
|
+
await rm2(join11(dirname7(configPath()), "app"), { recursive: true, force: true });
|
|
20140
20577
|
}
|
|
20141
20578
|
|
|
20142
20579
|
// src/index.ts
|
|
20143
|
-
var
|
|
20580
|
+
var config2 = loadConfig();
|
|
20144
20581
|
var args = process.argv.slice(2);
|
|
20145
20582
|
var command = args[0] ?? "watch";
|
|
20146
20583
|
var option = (name) => {
|
|
@@ -20149,7 +20586,7 @@ var option = (name) => {
|
|
|
20149
20586
|
};
|
|
20150
20587
|
var running = (path, init) => fetch(`http://127.0.0.1:${connectorPort()}${path}`, {
|
|
20151
20588
|
...init,
|
|
20152
|
-
headers: { "Content-Type": "application/json", Authorization: `Bearer ${
|
|
20589
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${config2.localSecret}` }
|
|
20153
20590
|
});
|
|
20154
20591
|
var refused = (error) => {
|
|
20155
20592
|
if (error.cause?.code === "ECONNREFUSED") return null;
|
|
@@ -20192,7 +20629,7 @@ async function background() {
|
|
|
20192
20629
|
return false;
|
|
20193
20630
|
}
|
|
20194
20631
|
var authorized = (request2) => {
|
|
20195
|
-
const expected = Buffer.from(`Bearer ${
|
|
20632
|
+
const expected = Buffer.from(`Bearer ${config2.localSecret}`);
|
|
20196
20633
|
const actual = Buffer.from(request2.headers.authorization ?? "");
|
|
20197
20634
|
return expected.length === actual.length && timingSafeEqual(expected, actual);
|
|
20198
20635
|
};
|
|
@@ -20210,15 +20647,15 @@ async function main() {
|
|
|
20210
20647
|
throw new Error("Use an HTTPS API origin");
|
|
20211
20648
|
if (url2.username || url2.password || url2.search || url2.hash || url2.pathname !== "/")
|
|
20212
20649
|
throw new Error("Use the API origin without a path");
|
|
20213
|
-
if (
|
|
20650
|
+
if (config2.secret && config2.api !== url2.origin)
|
|
20214
20651
|
throw new Error("Use a separate DASHUP_CONFIG file to connect a different server");
|
|
20215
|
-
|
|
20652
|
+
config2.api = url2.origin;
|
|
20216
20653
|
}
|
|
20217
20654
|
const account = option("--account");
|
|
20218
20655
|
if (account !== void 0 && !/^[^\s@]+@[^\s@]+$/.test(account))
|
|
20219
20656
|
throw new Error("Use --account with the email you sign in to the Dashup app with");
|
|
20220
20657
|
if (command === "install") {
|
|
20221
|
-
if (!
|
|
20658
|
+
if (!config2.secret) throw new Error("Pair this computer first: run dashup pair.");
|
|
20222
20659
|
if (!await background()) process.exitCode = 1;
|
|
20223
20660
|
return;
|
|
20224
20661
|
}
|
|
@@ -20240,10 +20677,10 @@ async function main() {
|
|
|
20240
20677
|
const [executable, ...agentArgs] = native;
|
|
20241
20678
|
if (!executable)
|
|
20242
20679
|
throw new Error("Usage: dashup acp -- <original-agent-command> [arguments...]");
|
|
20243
|
-
return proxyAcp(
|
|
20680
|
+
return proxyAcp(config2, executable, agentArgs);
|
|
20244
20681
|
}
|
|
20245
20682
|
if (command === "doctor" || command === "projects") {
|
|
20246
|
-
const discovery = new Discovery(
|
|
20683
|
+
const discovery = new Discovery(config2.device?.id ?? "local", config2.localSecret);
|
|
20247
20684
|
discovery.scan();
|
|
20248
20685
|
const live = new LiveHarnesses();
|
|
20249
20686
|
try {
|
|
@@ -20256,7 +20693,7 @@ async function main() {
|
|
|
20256
20693
|
JSON.stringify(
|
|
20257
20694
|
command === "doctor" ? {
|
|
20258
20695
|
node: process.version,
|
|
20259
|
-
paired: Boolean(
|
|
20696
|
+
paired: Boolean(config2.secret),
|
|
20260
20697
|
platform: process.platform,
|
|
20261
20698
|
projectCount: projects.length,
|
|
20262
20699
|
harnesses: Object.fromEntries(
|
|
@@ -20276,7 +20713,7 @@ async function main() {
|
|
|
20276
20713
|
return;
|
|
20277
20714
|
}
|
|
20278
20715
|
if (command === "alert") {
|
|
20279
|
-
if (!
|
|
20716
|
+
if (!config2.localSecret) throw new Error("Start dashup watch on this computer first");
|
|
20280
20717
|
const input = Attention.parse({
|
|
20281
20718
|
projectId: option("--project"),
|
|
20282
20719
|
threadId: option("--thread") ?? "",
|
|
@@ -20295,7 +20732,7 @@ async function main() {
|
|
|
20295
20732
|
);
|
|
20296
20733
|
return;
|
|
20297
20734
|
}
|
|
20298
|
-
const pairPhone = (invite) => scanned(
|
|
20735
|
+
const pairPhone = (invite) => scanned(config2, invite, (email) => `Paired a phone signed in as ${email}`);
|
|
20299
20736
|
const keepRunning = async (stopHere) => {
|
|
20300
20737
|
const answer = startupInstalled() || interactive && await ui2.confirm({ message: BACKGROUND }) === true;
|
|
20301
20738
|
if (answer) {
|
|
@@ -20334,7 +20771,7 @@ async function main() {
|
|
|
20334
20771
|
if (interactive) ui2.outro(DONE2);
|
|
20335
20772
|
process.exit();
|
|
20336
20773
|
}
|
|
20337
|
-
if (!
|
|
20774
|
+
if (!config2.secret && !interactive && !account)
|
|
20338
20775
|
throw new Error(
|
|
20339
20776
|
"To pair this computer, run dashup pair in a terminal, or add --account with your Dashup email."
|
|
20340
20777
|
);
|
|
@@ -20398,10 +20835,10 @@ async function main() {
|
|
|
20398
20835
|
server.once("error", reject);
|
|
20399
20836
|
server.listen(connectorPort(), "127.0.0.1", resolve3);
|
|
20400
20837
|
});
|
|
20401
|
-
const selectedApi =
|
|
20402
|
-
Object.assign(
|
|
20403
|
-
|
|
20404
|
-
saveConfig(
|
|
20838
|
+
const selectedApi = config2.api;
|
|
20839
|
+
Object.assign(config2, loadConfig(), { api: selectedApi });
|
|
20840
|
+
config2.localSecret ||= newSecret(randomBytes5);
|
|
20841
|
+
saveConfig(config2);
|
|
20405
20842
|
for (const signal of ["SIGINT", "SIGTERM"])
|
|
20406
20843
|
process.once(signal, () => {
|
|
20407
20844
|
connector?.stop();
|
|
@@ -20409,13 +20846,13 @@ async function main() {
|
|
|
20409
20846
|
process.exit(0);
|
|
20410
20847
|
});
|
|
20411
20848
|
try {
|
|
20412
|
-
const linking = !
|
|
20849
|
+
const linking = !config2.secret;
|
|
20413
20850
|
if (linking) {
|
|
20414
20851
|
intro2();
|
|
20415
|
-
if (!await link(
|
|
20852
|
+
if (!await link(config2, account)) process.exit(1);
|
|
20416
20853
|
await keepRunning(() => server.close());
|
|
20417
20854
|
}
|
|
20418
|
-
connector = new Connector(
|
|
20855
|
+
connector = new Connector(config2);
|
|
20419
20856
|
await connector.start();
|
|
20420
20857
|
if (command === "pair" && !linking) {
|
|
20421
20858
|
if (interactive) intro2();
|