@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.
- package/README.md +6 -6
- package/dist/index.js +707 -205
- 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
|
|
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 }
|
|
@@ -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
|
-
|
|
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
|
|
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
|
|
18420
|
-
if (!["busy", "retry"].includes(
|
|
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_${
|
|
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
|
|
18449
|
-
if (!Array.isArray(
|
|
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
|
|
18460
|
-
const permission = Array.isArray(
|
|
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 =
|
|
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) =>
|
|
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
|
|
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
|
|
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
|
|
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
|
|
18749
|
-
import { spawn as
|
|
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
|
|
18752
|
-
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";
|
|
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 =
|
|
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 =
|
|
18773
|
-
if (!
|
|
18774
|
-
const value2 = JSON.parse(
|
|
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 &&
|
|
18782
|
-
const executable = realpathSync2.native(
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
19305
|
+
const directory2 = statSync4(home);
|
|
18804
19306
|
if (process.getuid && directory2.uid !== process.getuid()) return [];
|
|
18805
|
-
const queue = identity(
|
|
19307
|
+
const queue = identity(join9(home, "queue_1.sqlite"));
|
|
18806
19308
|
if (!files2.some((file) => {
|
|
18807
19309
|
try {
|
|
18808
|
-
return sameFile(identity(
|
|
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(
|
|
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(
|
|
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
|
|
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 &&
|
|
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 =
|
|
18874
|
-
if (
|
|
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 =
|
|
18891
|
-
if (!
|
|
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 =
|
|
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 =
|
|
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(
|
|
19149
|
-
this.config =
|
|
19150
|
-
|
|
19151
|
-
|
|
19650
|
+
constructor(config3) {
|
|
19651
|
+
this.config = config3;
|
|
19652
|
+
config3.unconfirmed ??= {};
|
|
19653
|
+
config3.queued ??= {};
|
|
19152
19654
|
this.nativeQueue = new NativeQueue(
|
|
19153
|
-
|
|
19154
|
-
() => saveConfig(
|
|
19655
|
+
config3.queued,
|
|
19656
|
+
() => saveConfig(config3),
|
|
19155
19657
|
(request2) => this.isActive(request2.pairing)
|
|
19156
19658
|
);
|
|
19157
|
-
if (!
|
|
19659
|
+
if (!config3.device || !config3.secret) throw new Error("Pair this computer with dashup pair first");
|
|
19158
19660
|
this.client = createClient({
|
|
19159
|
-
url:
|
|
19160
|
-
getToken: async () =>
|
|
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(
|
|
19165
|
-
this.pairings = createLiveQuery(() => this.client.bind(Pairing).where({ device:
|
|
19166
|
-
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());
|
|
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
|
|
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 =
|
|
19375
|
-
return
|
|
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
|
|
19951
|
+
const run5 = this.work.then(() => {
|
|
19450
19952
|
this.pendingChange = void 0;
|
|
19451
19953
|
return this.changed();
|
|
19452
19954
|
});
|
|
19453
|
-
this.pendingChange =
|
|
19454
|
-
this.work =
|
|
19455
|
-
return
|
|
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 =
|
|
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
|
|
20467
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
19966
20468
|
import { homedir as homedir7, userInfo } from "node:os";
|
|
19967
|
-
import { promisify as
|
|
20469
|
+
import { promisify as promisify7 } from "node:util";
|
|
19968
20470
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
19969
|
-
import { execFile as
|
|
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
|
|
19972
|
-
var
|
|
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("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
19974
20476
|
var NODE_FLAGS = ["--disable-warning=ExperimentalWarning"];
|
|
19975
|
-
var systemctl = (...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" ?
|
|
19979
|
-
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"),
|
|
19980
20482
|
"Microsoft/Windows/Start Menu/Programs/Startup/Dashup.vbs"
|
|
19981
|
-
) :
|
|
19982
|
-
var startupInstalled = () =>
|
|
19983
|
-
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) {
|
|
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(
|
|
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") = "${
|
|
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=${
|
|
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 =
|
|
20519
|
+
const app = join11(dirname7(configPath()), "app");
|
|
20018
20520
|
await rm2(app, { recursive: true, force: true });
|
|
20019
|
-
await cp(
|
|
20521
|
+
await cp(join11(root, "node_modules"), join11(app, "node_modules"), {
|
|
20020
20522
|
recursive: true,
|
|
20021
20523
|
verbatimSymlinks: true
|
|
20022
20524
|
});
|
|
20023
|
-
return
|
|
20525
|
+
return join11(app, relative(root, entry));
|
|
20024
20526
|
}
|
|
20025
|
-
var claude2 = (...args2) =>
|
|
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",
|
|
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
|
|
20548
|
+
await run4("launchctl", ["unload", path]).catch(() => {
|
|
20047
20549
|
});
|
|
20048
|
-
await
|
|
20550
|
+
await run4("launchctl", ["load", path]);
|
|
20049
20551
|
} else if (process.platform === "win32") {
|
|
20050
|
-
|
|
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
|
|
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
|
|
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(
|
|
20576
|
+
await rm2(join11(dirname7(configPath()), "app"), { recursive: true, force: true });
|
|
20075
20577
|
}
|
|
20076
20578
|
|
|
20077
20579
|
// src/index.ts
|
|
20078
|
-
var
|
|
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 ${
|
|
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 ${
|
|
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 (
|
|
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
|
-
|
|
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 (!
|
|
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(
|
|
20680
|
+
return proxyAcp(config2, executable, agentArgs);
|
|
20179
20681
|
}
|
|
20180
20682
|
if (command === "doctor" || command === "projects") {
|
|
20181
|
-
const discovery = new Discovery(
|
|
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(
|
|
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 (!
|
|
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(
|
|
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 (!
|
|
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 =
|
|
20337
|
-
Object.assign(
|
|
20338
|
-
|
|
20339
|
-
saveConfig(
|
|
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 = !
|
|
20849
|
+
const linking = !config2.secret;
|
|
20348
20850
|
if (linking) {
|
|
20349
20851
|
intro2();
|
|
20350
|
-
if (!await link(
|
|
20852
|
+
if (!await link(config2, account)) process.exit(1);
|
|
20351
20853
|
await keepRunning(() => server.close());
|
|
20352
20854
|
}
|
|
20353
|
-
connector = new Connector(
|
|
20855
|
+
connector = new Connector(config2);
|
|
20354
20856
|
await connector.start();
|
|
20355
20857
|
if (command === "pair" && !linking) {
|
|
20356
20858
|
if (interactive) intro2();
|