@dashup/connector 0.1.4 → 0.1.5
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 +8 -4
- package/dist/index.js +683 -132
- package/package.json +3 -3
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 spawn7 = __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 = spawn7(process.argv[0], ["-e", execString]);
|
|
342
342
|
var statusText;
|
|
343
343
|
while (fs.existsSync(syncFile)) {
|
|
344
344
|
}
|
|
@@ -2714,10 +2714,14 @@ import { z as z3 } from "zod";
|
|
|
2714
2714
|
var PRODUCT = {
|
|
2715
2715
|
name: "Dashup",
|
|
2716
2716
|
shorthand: "-up",
|
|
2717
|
+
app: "com.dashup.mobile",
|
|
2718
|
+
site: "https://dashup.com",
|
|
2717
2719
|
api: "https://api.dashup.com",
|
|
2720
|
+
support: "support@dashup.com",
|
|
2718
2721
|
currency: "USD",
|
|
2719
2722
|
trialDays: 14,
|
|
2720
|
-
monthlyPrice: 10
|
|
2723
|
+
monthlyPrice: 10,
|
|
2724
|
+
storeProduct: "dashup.monthly"
|
|
2721
2725
|
};
|
|
2722
2726
|
var PAIRING_TTL_MS = 5 * 6e4;
|
|
2723
2727
|
var PROTOCOL_VERSION = 1;
|
|
@@ -3767,7 +3771,7 @@ function bridgeAcp(socket, onUpdate) {
|
|
|
3767
3771
|
if (session && direction === "agent" && message.method === methods.client.session.requestPermission) {
|
|
3768
3772
|
const tool = message.params.toolCall ?? {};
|
|
3769
3773
|
const diff = (tool.content ?? []).find((part) => part.type === "diff");
|
|
3770
|
-
const
|
|
3774
|
+
const approval3 = {
|
|
3771
3775
|
id: randomUUID(),
|
|
3772
3776
|
title: String(message.params.title ?? tool.title ?? "Agent needs approval"),
|
|
3773
3777
|
detail: String(message.params.description ?? ""),
|
|
@@ -3785,10 +3789,10 @@ ${String(diff.newText).split("\n").map((line) => "+" + line).join("\n")}`
|
|
|
3785
3789
|
}
|
|
3786
3790
|
} : {}
|
|
3787
3791
|
};
|
|
3788
|
-
session.approvals.set(String(message.id),
|
|
3792
|
+
session.approvals.set(String(message.id), approval3);
|
|
3789
3793
|
permissions.set(String(message.id), session);
|
|
3790
3794
|
session.status = "attention";
|
|
3791
|
-
publish(session,
|
|
3795
|
+
publish(session, approval3);
|
|
3792
3796
|
}
|
|
3793
3797
|
if (direction === "editor" && !message.method) {
|
|
3794
3798
|
const pending = permissions.get(String(message.id));
|
|
@@ -3836,16 +3840,16 @@ function attachAcp(socket, onEvent) {
|
|
|
3836
3840
|
async function proxyAcp(config2, executable, args2) {
|
|
3837
3841
|
if (!config2.localSecret) throw new Error("Start dashup watch before using the ACP proxy");
|
|
3838
3842
|
const socket = await new Promise((resolve3, reject) => {
|
|
3839
|
-
const
|
|
3843
|
+
const connect4 = request({
|
|
3840
3844
|
hostname: "127.0.0.1",
|
|
3841
3845
|
port: connectorPort(),
|
|
3842
3846
|
method: "CONNECT",
|
|
3843
3847
|
path: "/acp",
|
|
3844
3848
|
headers: { Authorization: `Bearer ${config2.localSecret}` }
|
|
3845
3849
|
});
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3850
|
+
connect4.on("connect", (response, stream) => response.statusCode === 200 ? resolve3(stream) : reject(new Error("Dashup ACP connection was rejected")));
|
|
3851
|
+
connect4.on("error", reject);
|
|
3852
|
+
connect4.end();
|
|
3849
3853
|
});
|
|
3850
3854
|
const child = spawn(executable, args2, { stdio: ["pipe", "pipe", "inherit"], windowsHide: true });
|
|
3851
3855
|
const local = /* @__PURE__ */ new Set();
|
|
@@ -4013,7 +4017,7 @@ import * as ui from "@clack/prompts";
|
|
|
4013
4017
|
import { z } from "zod";
|
|
4014
4018
|
|
|
4015
4019
|
// package.json
|
|
4016
|
-
var version = "0.1.
|
|
4020
|
+
var version = "0.1.5";
|
|
4017
4021
|
|
|
4018
4022
|
// src/pair.ts
|
|
4019
4023
|
var Invitation = z.object({
|
|
@@ -4409,7 +4413,7 @@ var Viewer = class _Viewer {
|
|
|
4409
4413
|
// src/discovery.ts
|
|
4410
4414
|
import { homedir as homedir3 } from "node:os";
|
|
4411
4415
|
import { createHmac } from "node:crypto";
|
|
4412
|
-
import { basename, join as join3 } from "node:path";
|
|
4416
|
+
import { basename, dirname as dirname3, join as join3 } from "node:path";
|
|
4413
4417
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
4414
4418
|
import { zstdDecompressSync } from "node:zlib";
|
|
4415
4419
|
import { watch, existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
@@ -4569,6 +4573,7 @@ function title(value2, fallback) {
|
|
|
4569
4573
|
const text3 = typeof value2 === "string" ? value2.trim().split("\n")[0].trim() : "";
|
|
4570
4574
|
return text3 ? text3.slice(0, TITLE_LIMIT) : fallback;
|
|
4571
4575
|
}
|
|
4576
|
+
var injected = (content) => /^\s*(# AGENTS\.md instructions for |<[a-z_][\w -]*>)/i.test(text(content));
|
|
4572
4577
|
function text(value2) {
|
|
4573
4578
|
if (typeof value2 === "string") return value2;
|
|
4574
4579
|
if (Array.isArray(value2)) return value2.map(text).filter(Boolean).join("\n");
|
|
@@ -4637,6 +4642,7 @@ function step(id, name, input, result) {
|
|
|
4637
4642
|
}
|
|
4638
4643
|
};
|
|
4639
4644
|
}
|
|
4645
|
+
var claudeHome = () => process.env.CLAUDE_CONFIG_DIR ?? join2(homedir2(), ".claude");
|
|
4640
4646
|
var claudeMessage = (row) => (row.type === "user" || row.type === "assistant") && !row.isSidechain;
|
|
4641
4647
|
function claudeChange(change) {
|
|
4642
4648
|
if (typeof change?.filePath !== "string") return void 0;
|
|
@@ -4714,7 +4720,9 @@ function claude(root, scan) {
|
|
|
4714
4720
|
continue;
|
|
4715
4721
|
}
|
|
4716
4722
|
const named = latest.find((row) => row.type === "custom-title" || row.type === "ai-title");
|
|
4717
|
-
const first = rows.find(
|
|
4723
|
+
const first = rows.find(
|
|
4724
|
+
(row) => row.type === "user" && !row.isMeta && !row.isSidechain && !injected(row.message?.content)
|
|
4725
|
+
);
|
|
4718
4726
|
scan.sessions.push({
|
|
4719
4727
|
harness: "claude",
|
|
4720
4728
|
id: owner.sessionId,
|
|
@@ -4762,7 +4770,7 @@ function codexMessages(path) {
|
|
|
4762
4770
|
if (!item) return [];
|
|
4763
4771
|
const id = String(item.id ?? row.ordinal ?? `${index}`);
|
|
4764
4772
|
if (item.type === "message" && ["user", "assistant"].includes(item.role))
|
|
4765
|
-
return prose(id, item.role, item.content);
|
|
4773
|
+
return item.role === "user" && injected(item.content) ? [] : prose(id, item.role, item.content);
|
|
4766
4774
|
if (!["function_call", "custom_tool_call"].includes(item.type)) return [];
|
|
4767
4775
|
const input = item.type === "function_call" ? parse(String(item.arguments)) ?? item.arguments : item.input;
|
|
4768
4776
|
const output = outputs.get(item.call_id);
|
|
@@ -4803,6 +4811,7 @@ function codex(root, scan) {
|
|
|
4803
4811
|
scan.warnings.push(`Cannot read Codex session index: ${index}`);
|
|
4804
4812
|
}
|
|
4805
4813
|
}
|
|
4814
|
+
const titles = codexTitles(root);
|
|
4806
4815
|
for (const path of [
|
|
4807
4816
|
...files(join2(root, "sessions"), scan),
|
|
4808
4817
|
...files(join2(root, "archived_sessions"), scan)
|
|
@@ -4818,13 +4827,16 @@ function codex(root, scan) {
|
|
|
4818
4827
|
}
|
|
4819
4828
|
const named = names.get(owner.id);
|
|
4820
4829
|
const first = rows.find(
|
|
4821
|
-
(row) => row.type === "response_item" && row.payload?.type === "message" && row.payload.role === "user"
|
|
4830
|
+
(row) => row.type === "response_item" && row.payload?.type === "message" && row.payload.role === "user" && !injected(row.payload.content)
|
|
4822
4831
|
);
|
|
4823
4832
|
scan.sessions.push({
|
|
4824
4833
|
harness: "codex",
|
|
4825
4834
|
id: owner.id,
|
|
4826
4835
|
cwd,
|
|
4827
|
-
title: title(
|
|
4836
|
+
title: title(
|
|
4837
|
+
named?.thread_name ?? titles.get(owner.id) ?? text(first?.payload?.content),
|
|
4838
|
+
"Codex thread"
|
|
4839
|
+
),
|
|
4828
4840
|
parent: typeof owner.parent_thread_id === "string" ? owner.parent_thread_id : void 0,
|
|
4829
4841
|
updatedAt: date(latest.find(codexItem)?.timestamp, statSync(path).mtimeMs),
|
|
4830
4842
|
files: [path, ...existsSync3(index) ? [index] : []],
|
|
@@ -4893,6 +4905,30 @@ function databaseMessages(path, session) {
|
|
|
4893
4905
|
db.close();
|
|
4894
4906
|
}
|
|
4895
4907
|
}
|
|
4908
|
+
function codexSubagent(row) {
|
|
4909
|
+
const spawn7 = object(object(parse(String(row.source ?? "")))?.subagent)?.thread_spawn;
|
|
4910
|
+
const task = typeof spawn7?.agent_path === "string" ? spawn7.agent_path.split("/").pop() : "";
|
|
4911
|
+
return [row.agent_nickname, task?.replace(/_/g, " ")].filter(Boolean).join(" \xB7 ");
|
|
4912
|
+
}
|
|
4913
|
+
function codexTitles(root) {
|
|
4914
|
+
const titles = /* @__PURE__ */ new Map();
|
|
4915
|
+
const state = (existsSync3(root) ? readdirSync(root) : []).filter((name) => /^state_\d+\.sqlite$/.test(name)).sort((a, b) => Number(b.match(/\d+/)[0]) - Number(a.match(/\d+/)[0]))[0];
|
|
4916
|
+
if (!state) return titles;
|
|
4917
|
+
let db;
|
|
4918
|
+
try {
|
|
4919
|
+
db = openDatabase(join2(root, state));
|
|
4920
|
+
for (const row of db.prepare("SELECT id, title, first_user_message, source, agent_nickname FROM threads").all()) {
|
|
4921
|
+
const name = [row.title, row.first_user_message].find(
|
|
4922
|
+
(value2) => typeof value2 === "string" && !!value2.trim() && !injected(value2)
|
|
4923
|
+
);
|
|
4924
|
+
if (typeof row.id === "string") titles.set(row.id, name ?? codexSubagent(row));
|
|
4925
|
+
}
|
|
4926
|
+
} catch {
|
|
4927
|
+
} finally {
|
|
4928
|
+
db?.close();
|
|
4929
|
+
}
|
|
4930
|
+
return titles;
|
|
4931
|
+
}
|
|
4896
4932
|
function openCodeDatabase(path, scan) {
|
|
4897
4933
|
if (!existsSync3(path)) {
|
|
4898
4934
|
watchParent(path, scan);
|
|
@@ -4991,7 +5027,7 @@ function openCodeLegacy(root, scan) {
|
|
|
4991
5027
|
function scanHistories() {
|
|
4992
5028
|
const home = homedir2();
|
|
4993
5029
|
const scan = { sessions: [], paths: /* @__PURE__ */ new Set(), warnings: [] };
|
|
4994
|
-
claude(
|
|
5030
|
+
claude(claudeHome(), scan);
|
|
4995
5031
|
codex(process.env.CODEX_HOME ?? join2(home, ".codex"), scan);
|
|
4996
5032
|
const opencode = join2(process.env.XDG_DATA_HOME ?? join2(home, ".local/share"), "opencode");
|
|
4997
5033
|
openCodeDatabase(process.env.OPENCODE_DB ?? join2(opencode, "opencode.db"), scan);
|
|
@@ -5133,7 +5169,7 @@ var Discovery = class {
|
|
|
5133
5169
|
const project = this.project(session.cwd);
|
|
5134
5170
|
const thread = {
|
|
5135
5171
|
id: agentThread(session.harness, session.id, this.secret),
|
|
5136
|
-
title: session.title,
|
|
5172
|
+
title: session.title ?? "",
|
|
5137
5173
|
editor: "cli",
|
|
5138
5174
|
status: "unknown",
|
|
5139
5175
|
updatedAt: session.updatedAt,
|
|
@@ -5152,13 +5188,24 @@ var Discovery = class {
|
|
|
5152
5188
|
thread.updatedAt = session.updatedAt;
|
|
5153
5189
|
if (session.parent) thread.parent = agentThread(session.harness, session.parent, this.secret);
|
|
5154
5190
|
}
|
|
5191
|
+
// Where new projects go: the folders holding this computer's projects, most used first.
|
|
5192
|
+
folders() {
|
|
5193
|
+
const counts = /* @__PURE__ */ new Map();
|
|
5194
|
+
for (const project of this.projects.values())
|
|
5195
|
+
if (!project.remote) {
|
|
5196
|
+
const parent = dirname3(project.path);
|
|
5197
|
+
counts.set(parent, (counts.get(parent) ?? 0) + 1);
|
|
5198
|
+
}
|
|
5199
|
+
const folders = [...counts].sort((a, b) => b[1] - a[1]).map(([folder]) => folder).filter((folder) => existsSync4(folder)).slice(0, 3);
|
|
5200
|
+
return folders.length ? folders : [homedir3()];
|
|
5201
|
+
}
|
|
5155
5202
|
observe(session) {
|
|
5156
5203
|
const found = this.native(session);
|
|
5157
5204
|
const location2 = this.locations.get(found.thread.id);
|
|
5158
5205
|
location2.live = session;
|
|
5159
5206
|
Object.assign(found.thread, {
|
|
5160
5207
|
editor: session.editor ?? found.thread.editor,
|
|
5161
|
-
title: session.title,
|
|
5208
|
+
title: session.title ?? found.thread.title,
|
|
5162
5209
|
status: session.status,
|
|
5163
5210
|
updatedAt: location2.history?.updatedAt ?? session.updatedAt,
|
|
5164
5211
|
capabilities: {
|
|
@@ -5377,6 +5424,8 @@ var Discovery = class {
|
|
|
5377
5424
|
|
|
5378
5425
|
// src/connector.ts
|
|
5379
5426
|
import { z as z2 } from "zod";
|
|
5427
|
+
import { join as join9 } from "node:path";
|
|
5428
|
+
import { mkdir } from "node:fs/promises";
|
|
5380
5429
|
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
5381
5430
|
|
|
5382
5431
|
// ../../node_modules/.pnpm/engine.io-client@6.6.7/node_modules/engine.io-client/build/esm-debug/transports/polling-xhr.node.js
|
|
@@ -16395,11 +16444,17 @@ async function publishHistory(config2, pair, project, thread, messages5) {
|
|
|
16395
16444
|
return summary;
|
|
16396
16445
|
}
|
|
16397
16446
|
|
|
16447
|
+
// src/harness/live.ts
|
|
16448
|
+
import { promisify as promisify6 } from "node:util";
|
|
16449
|
+
import { execFile as execFile6 } from "node:child_process";
|
|
16450
|
+
|
|
16398
16451
|
// src/harness/codex.ts
|
|
16399
16452
|
import WebSocket2 from "ws";
|
|
16400
16453
|
import { homedir as homedir4 } from "node:os";
|
|
16401
16454
|
import { connect } from "node:net";
|
|
16455
|
+
import { promisify as promisify3 } from "node:util";
|
|
16402
16456
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
16457
|
+
import { execFile as execFile3 } from "node:child_process";
|
|
16403
16458
|
import { join as join4, isAbsolute as isAbsolute2 } from "node:path";
|
|
16404
16459
|
import { stat, readdir, readFile } from "node:fs/promises";
|
|
16405
16460
|
var REQUEST_MS = 4e3;
|
|
@@ -16431,13 +16486,12 @@ function approval(id, method, params) {
|
|
|
16431
16486
|
choices: DECISIONS.filter((choice) => !unavailable || choice.id === "decline").map((choice) => ({ ...choice }))
|
|
16432
16487
|
};
|
|
16433
16488
|
}
|
|
16489
|
+
var codexHome = () => process.env.DASHUP_CODEX_HOME ?? process.env.CODEX_HOME ?? join4(homedir4(), ".codex");
|
|
16434
16490
|
async function sockets() {
|
|
16435
16491
|
const explicit = process.env.DASHUP_CODEX_SOCKET;
|
|
16436
16492
|
if (explicit !== void 0) return explicit && isAbsolute2(explicit) ? [explicit] : [];
|
|
16437
16493
|
if (process.env.DASHUP_LIVE_DISCOVERY === "0") return [];
|
|
16438
|
-
const homes = /* @__PURE__ */ new Set([
|
|
16439
|
-
process.env.DASHUP_CODEX_HOME ?? process.env.CODEX_HOME ?? join4(homedir4(), ".codex")
|
|
16440
|
-
]);
|
|
16494
|
+
const homes = /* @__PURE__ */ new Set([codexHome()]);
|
|
16441
16495
|
if (process.platform === "linux") {
|
|
16442
16496
|
const entries2 = await readdir("/proc").catch(() => []);
|
|
16443
16497
|
for (const pid of entries2.filter((value2) => /^\d+$/.test(value2)).slice(0, MAX_PROCESSES)) {
|
|
@@ -16623,6 +16677,16 @@ var Client = class {
|
|
|
16623
16677
|
this.requests.set(id, result);
|
|
16624
16678
|
return result;
|
|
16625
16679
|
}
|
|
16680
|
+
// Starts a thread in `cwd` with Codex's own settings, then its first turn.
|
|
16681
|
+
async start(cwd, text3) {
|
|
16682
|
+
await this.connect();
|
|
16683
|
+
if (!this.controls) throw new Error("Update Codex to start threads from Dashup");
|
|
16684
|
+
const id = (await this.call("thread/start", { cwd })).thread?.id;
|
|
16685
|
+
if (typeof id !== "string") throw new Error("Codex did not start a thread");
|
|
16686
|
+
await this.call("turn/start", { threadId: id, input: [{ type: "text", text: text3, text_elements: [] }] });
|
|
16687
|
+
this.changed();
|
|
16688
|
+
return id;
|
|
16689
|
+
}
|
|
16626
16690
|
async read() {
|
|
16627
16691
|
await this.connect();
|
|
16628
16692
|
const loaded = await this.call("thread/loaded/list", { limit: MAX_SESSIONS });
|
|
@@ -16670,12 +16734,12 @@ var Client = class {
|
|
|
16670
16734
|
this.changed();
|
|
16671
16735
|
}) : void 0,
|
|
16672
16736
|
approve: approvals.length ? (requestId, approvalId, choice) => this.once(requestId, async () => {
|
|
16673
|
-
const
|
|
16737
|
+
const approval3 = this.approvals.get(approvalId);
|
|
16674
16738
|
const socket = this.socket;
|
|
16675
|
-
if (!
|
|
16739
|
+
if (!approval3 || approval3.threadId !== id || !approval3.approval.choices.some((value2) => value2.id === choice) || socket?.readyState !== WebSocket2.OPEN)
|
|
16676
16740
|
throw new Error("Codex approval is no longer pending");
|
|
16677
16741
|
await new Promise((resolve3, reject) => socket.send(
|
|
16678
|
-
JSON.stringify({ id:
|
|
16742
|
+
JSON.stringify({ id: approval3.requestId, result: { decision: choice } }),
|
|
16679
16743
|
(error) => error ? reject(new Error("Codex approval could not be sent")) : resolve3()
|
|
16680
16744
|
));
|
|
16681
16745
|
this.approvals.delete(approvalId);
|
|
@@ -16705,8 +16769,25 @@ var Codex = class {
|
|
|
16705
16769
|
}
|
|
16706
16770
|
changed;
|
|
16707
16771
|
clients = /* @__PURE__ */ new Map();
|
|
16772
|
+
// Control sockets of daemons this connector started, which discovery may not look for.
|
|
16773
|
+
started = /* @__PURE__ */ new Set();
|
|
16774
|
+
// Codex's own daemon runs the threads Dashup starts, so they outlive this connector.
|
|
16775
|
+
async start(cwd, text3) {
|
|
16776
|
+
const path = process.env.DASHUP_CODEX_SOCKET || join4(codexHome(), ...SOCKET_PARTS);
|
|
16777
|
+
await promisify3(execFile3)("codex", ["app-server", "daemon", "start"], {
|
|
16778
|
+
timeout: 6e4,
|
|
16779
|
+
shell: process.platform === "win32"
|
|
16780
|
+
});
|
|
16781
|
+
this.started.add(path);
|
|
16782
|
+
let client = this.clients.get(path);
|
|
16783
|
+
if (!client) {
|
|
16784
|
+
client = new Client(path, this.changed);
|
|
16785
|
+
this.clients.set(path, client);
|
|
16786
|
+
}
|
|
16787
|
+
return client.start(cwd, text3);
|
|
16788
|
+
}
|
|
16708
16789
|
async refresh() {
|
|
16709
|
-
const paths = new Set(await sockets());
|
|
16790
|
+
const paths = /* @__PURE__ */ new Set([...await sockets(), ...this.started]);
|
|
16710
16791
|
for (const [path, client] of this.clients)
|
|
16711
16792
|
if (!paths.has(path)) {
|
|
16712
16793
|
client.stop();
|
|
@@ -16730,26 +16811,360 @@ var Codex = class {
|
|
|
16730
16811
|
}
|
|
16731
16812
|
};
|
|
16732
16813
|
|
|
16814
|
+
// src/harness/headless.ts
|
|
16815
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
16816
|
+
import { createInterface as createInterface3 } from "node:readline";
|
|
16817
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
16818
|
+
var CHOICES = [
|
|
16819
|
+
{ id: "accept", label: "Allow once" },
|
|
16820
|
+
{ id: "decline", label: "Reject" }
|
|
16821
|
+
];
|
|
16822
|
+
var EDITS = /* @__PURE__ */ new Set(["Edit", "MultiEdit", "NotebookEdit", "Write"]);
|
|
16823
|
+
var MAX_REQUESTS = 1e3;
|
|
16824
|
+
var NOTIFY_MS = 100;
|
|
16825
|
+
function approval2(id, request2) {
|
|
16826
|
+
const input = request2.input ?? {};
|
|
16827
|
+
const tool = String(request2.display_name ?? request2.tool_name ?? "a tool");
|
|
16828
|
+
let detail = [
|
|
16829
|
+
typeof request2.description === "string" ? request2.description : "",
|
|
16830
|
+
typeof input.command === "string" ? input.command : JSON.stringify(input, null, 2)
|
|
16831
|
+
].filter(Boolean).join("\n\n");
|
|
16832
|
+
if (Buffer.byteLength(detail) > MAX_PREVIEW)
|
|
16833
|
+
detail = Buffer.from(detail).subarray(0, MAX_PREVIEW).toString("utf8");
|
|
16834
|
+
return {
|
|
16835
|
+
id,
|
|
16836
|
+
title: request2.tool_name === "Bash" ? "Allow command?" : EDITS.has(request2.tool_name) ? "Allow file changes?" : `Allow ${tool}?`,
|
|
16837
|
+
detail,
|
|
16838
|
+
choices: CHOICES.map((choice) => ({ ...choice }))
|
|
16839
|
+
};
|
|
16840
|
+
}
|
|
16841
|
+
var Session = class {
|
|
16842
|
+
constructor(id, cwd, title2, changed) {
|
|
16843
|
+
this.id = id;
|
|
16844
|
+
this.cwd = cwd;
|
|
16845
|
+
this.title = title2;
|
|
16846
|
+
this.changed = changed;
|
|
16847
|
+
}
|
|
16848
|
+
id;
|
|
16849
|
+
cwd;
|
|
16850
|
+
title;
|
|
16851
|
+
changed;
|
|
16852
|
+
messages = [];
|
|
16853
|
+
status = "idle";
|
|
16854
|
+
updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
16855
|
+
turn;
|
|
16856
|
+
// Messages written to this turn's process that Claude Code has not finished answering.
|
|
16857
|
+
open = 0;
|
|
16858
|
+
// Once Claude Code has saved the session, later turns resume it.
|
|
16859
|
+
saved = false;
|
|
16860
|
+
approvals = /* @__PURE__ */ new Map();
|
|
16861
|
+
requests = /* @__PURE__ */ new Map();
|
|
16862
|
+
write(value2) {
|
|
16863
|
+
this.turn?.stdin.write(`${JSON.stringify(value2)}
|
|
16864
|
+
`);
|
|
16865
|
+
}
|
|
16866
|
+
once(id, action) {
|
|
16867
|
+
const known = this.requests.get(id);
|
|
16868
|
+
if (known) return known;
|
|
16869
|
+
if (this.requests.size >= MAX_REQUESTS)
|
|
16870
|
+
throw new Error("Claude Code command history is full; restart Dashup before sending");
|
|
16871
|
+
const result = action();
|
|
16872
|
+
this.requests.set(id, result);
|
|
16873
|
+
return result;
|
|
16874
|
+
}
|
|
16875
|
+
// Resolves once Claude Code takes the message, or says why it could not start.
|
|
16876
|
+
say(text3) {
|
|
16877
|
+
this.messages.push({ id: randomUUID3(), role: "user", text: text3 });
|
|
16878
|
+
this.status = "working";
|
|
16879
|
+
this.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
16880
|
+
const message = { type: "user", message: { role: "user", content: text3 } };
|
|
16881
|
+
if (this.turn) {
|
|
16882
|
+
this.open++;
|
|
16883
|
+
this.write(message);
|
|
16884
|
+
this.changed();
|
|
16885
|
+
return Promise.resolve();
|
|
16886
|
+
}
|
|
16887
|
+
const turn = this.turn = spawn3(
|
|
16888
|
+
"claude",
|
|
16889
|
+
[
|
|
16890
|
+
"-p",
|
|
16891
|
+
"--input-format",
|
|
16892
|
+
"stream-json",
|
|
16893
|
+
"--output-format",
|
|
16894
|
+
"stream-json",
|
|
16895
|
+
"--verbose",
|
|
16896
|
+
"--permission-prompt-tool",
|
|
16897
|
+
"stdio",
|
|
16898
|
+
...this.saved ? ["--resume", this.id] : ["--session-id", this.id]
|
|
16899
|
+
],
|
|
16900
|
+
{ cwd: this.cwd, stdio: "pipe", shell: process.platform === "win32" }
|
|
16901
|
+
);
|
|
16902
|
+
this.open = 1;
|
|
16903
|
+
let errors = "";
|
|
16904
|
+
turn.stdin.on("error", () => {
|
|
16905
|
+
});
|
|
16906
|
+
turn.stderr.on("data", (chunk) => {
|
|
16907
|
+
errors = (errors + String(chunk)).slice(-2e3);
|
|
16908
|
+
});
|
|
16909
|
+
const started = new Promise((resolve3, reject) => {
|
|
16910
|
+
createInterface3({ input: turn.stdout }).on("line", (line) => {
|
|
16911
|
+
if (this.receive(line)) resolve3();
|
|
16912
|
+
});
|
|
16913
|
+
const end = (reason) => {
|
|
16914
|
+
reject(new Error(reason));
|
|
16915
|
+
if (this.turn !== turn) return;
|
|
16916
|
+
this.turn = void 0;
|
|
16917
|
+
this.open = 0;
|
|
16918
|
+
this.approvals.clear();
|
|
16919
|
+
this.status = "idle";
|
|
16920
|
+
this.changed();
|
|
16921
|
+
};
|
|
16922
|
+
turn.once("error", () => end("Claude Code is not installed on this computer"));
|
|
16923
|
+
turn.once("exit", () => end(errors.trim() || "Claude Code stopped before starting"));
|
|
16924
|
+
});
|
|
16925
|
+
this.write(message);
|
|
16926
|
+
this.changed();
|
|
16927
|
+
return started;
|
|
16928
|
+
}
|
|
16929
|
+
// Handles one line of Claude Code's output, and says whether it started the session.
|
|
16930
|
+
receive(line) {
|
|
16931
|
+
let message;
|
|
16932
|
+
try {
|
|
16933
|
+
message = JSON.parse(line);
|
|
16934
|
+
} catch {
|
|
16935
|
+
return false;
|
|
16936
|
+
}
|
|
16937
|
+
const init = message.type === "system" && message.subtype === "init";
|
|
16938
|
+
if (init) this.saved = true;
|
|
16939
|
+
else if (message.type === "assistant") {
|
|
16940
|
+
const text3 = (Array.isArray(message.message?.content) ? message.message.content : []).filter((part) => part?.type === "text" && typeof part.text === "string").map((part) => part.text).join("\n\n");
|
|
16941
|
+
if (!text3) return false;
|
|
16942
|
+
this.messages.push({ id: String(message.message?.id ?? randomUUID3()), role: "assistant", text: text3 });
|
|
16943
|
+
} else if (message.type === "control_request" && message.request?.subtype === "can_use_tool") {
|
|
16944
|
+
const id = randomUUID3();
|
|
16945
|
+
this.approvals.set(id, {
|
|
16946
|
+
request: String(message.request_id),
|
|
16947
|
+
input: message.request.input ?? {},
|
|
16948
|
+
approval: approval2(id, message.request)
|
|
16949
|
+
});
|
|
16950
|
+
this.status = "attention";
|
|
16951
|
+
} else if (message.type === "result") {
|
|
16952
|
+
if (--this.open <= 0) this.turn?.stdin.end();
|
|
16953
|
+
} else return false;
|
|
16954
|
+
this.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
16955
|
+
this.changed();
|
|
16956
|
+
return init;
|
|
16957
|
+
}
|
|
16958
|
+
live() {
|
|
16959
|
+
return {
|
|
16960
|
+
id: this.id,
|
|
16961
|
+
cwd: this.cwd,
|
|
16962
|
+
title: this.title,
|
|
16963
|
+
harness: "claude",
|
|
16964
|
+
updatedAt: this.updatedAt,
|
|
16965
|
+
status: this.status,
|
|
16966
|
+
messages: [...this.messages],
|
|
16967
|
+
approvals: [...this.approvals.values()].map((pending) => pending.approval),
|
|
16968
|
+
send: (requestId, text3) => this.once(requestId, () => this.say(text3)),
|
|
16969
|
+
stop: this.turn ? (requestId) => this.once(requestId, async () => {
|
|
16970
|
+
if (!this.turn) throw new Error("Nothing is running in this Claude Code session");
|
|
16971
|
+
this.write({
|
|
16972
|
+
type: "control_request",
|
|
16973
|
+
request_id: randomUUID3(),
|
|
16974
|
+
request: { subtype: "interrupt" }
|
|
16975
|
+
});
|
|
16976
|
+
}) : void 0,
|
|
16977
|
+
approve: this.approvals.size ? (requestId, approvalId, choice) => this.once(requestId, async () => {
|
|
16978
|
+
const pending = this.approvals.get(approvalId);
|
|
16979
|
+
if (!pending || !this.turn || !CHOICES.some((value2) => value2.id === choice))
|
|
16980
|
+
throw new Error("Claude Code approval is no longer pending");
|
|
16981
|
+
this.write({
|
|
16982
|
+
type: "control_response",
|
|
16983
|
+
response: {
|
|
16984
|
+
subtype: "success",
|
|
16985
|
+
request_id: pending.request,
|
|
16986
|
+
response: choice === "accept" ? { behavior: "allow", updatedInput: pending.input } : { behavior: "deny", message: "The person rejected this from their phone." }
|
|
16987
|
+
}
|
|
16988
|
+
});
|
|
16989
|
+
this.approvals.delete(approvalId);
|
|
16990
|
+
if (!this.approvals.size) this.status = "working";
|
|
16991
|
+
this.changed();
|
|
16992
|
+
}) : void 0
|
|
16993
|
+
};
|
|
16994
|
+
}
|
|
16995
|
+
end() {
|
|
16996
|
+
this.turn?.kill();
|
|
16997
|
+
}
|
|
16998
|
+
};
|
|
16999
|
+
var Headless = class {
|
|
17000
|
+
constructor(changed) {
|
|
17001
|
+
this.changed = changed;
|
|
17002
|
+
}
|
|
17003
|
+
changed;
|
|
17004
|
+
sessions = /* @__PURE__ */ new Map();
|
|
17005
|
+
notify;
|
|
17006
|
+
soon = () => {
|
|
17007
|
+
if (this.notify) return;
|
|
17008
|
+
this.notify = setTimeout(() => {
|
|
17009
|
+
this.notify = void 0;
|
|
17010
|
+
this.changed();
|
|
17011
|
+
}, NOTIFY_MS);
|
|
17012
|
+
this.notify.unref();
|
|
17013
|
+
};
|
|
17014
|
+
// Starts a session in `cwd` with its first message, and resolves with the session's id.
|
|
17015
|
+
async start(cwd, text3) {
|
|
17016
|
+
const title2 = text3.trim().split("\n")[0].slice(0, 80);
|
|
17017
|
+
const session = new Session(randomUUID3(), cwd, title2, this.soon);
|
|
17018
|
+
this.sessions.set(session.id, session);
|
|
17019
|
+
try {
|
|
17020
|
+
await session.say(text3);
|
|
17021
|
+
} catch (error) {
|
|
17022
|
+
this.sessions.delete(session.id);
|
|
17023
|
+
throw error;
|
|
17024
|
+
}
|
|
17025
|
+
return session.id;
|
|
17026
|
+
}
|
|
17027
|
+
async refresh() {
|
|
17028
|
+
return [...this.sessions.values()].map((session) => session.live());
|
|
17029
|
+
}
|
|
17030
|
+
stop() {
|
|
17031
|
+
clearTimeout(this.notify);
|
|
17032
|
+
for (const session of this.sessions.values()) session.end();
|
|
17033
|
+
}
|
|
17034
|
+
};
|
|
17035
|
+
|
|
17036
|
+
// src/harness/claude.ts
|
|
17037
|
+
import { join as join5 } from "node:path";
|
|
17038
|
+
import { connect as connect2 } from "node:net";
|
|
17039
|
+
import { watch as watch2 } from "node:fs";
|
|
17040
|
+
import { lstat, readdir as readdir2, readFile as readFile2 } from "node:fs/promises";
|
|
17041
|
+
var PEER_PROTOCOL = 1;
|
|
17042
|
+
var MAX_SESSIONS2 = 100;
|
|
17043
|
+
var REQUEST_MS2 = 4e3;
|
|
17044
|
+
var MAX_REQUESTS2 = 1e3;
|
|
17045
|
+
var ENVELOPE = "cross-session-message";
|
|
17046
|
+
var STATUS = {
|
|
17047
|
+
busy: "working",
|
|
17048
|
+
idle: "idle",
|
|
17049
|
+
shell: "idle",
|
|
17050
|
+
waiting: "attention"
|
|
17051
|
+
};
|
|
17052
|
+
async function registrations() {
|
|
17053
|
+
if (process.env.DASHUP_LIVE_DISCOVERY === "0") return [];
|
|
17054
|
+
const directory2 = join5(claudeHome(), "sessions");
|
|
17055
|
+
const names = await readdir2(directory2).catch(() => []);
|
|
17056
|
+
const found = await Promise.all(
|
|
17057
|
+
names.filter((name) => /^\d+\.json$/.test(name)).slice(0, MAX_SESSIONS2).map(async (name) => {
|
|
17058
|
+
try {
|
|
17059
|
+
const value2 = JSON.parse(await readFile2(join5(directory2, name), "utf8"));
|
|
17060
|
+
if (typeof value2.sessionId !== "string" || typeof value2.cwd !== "string") return [];
|
|
17061
|
+
process.kill(value2.pid, 0);
|
|
17062
|
+
return [value2];
|
|
17063
|
+
} catch {
|
|
17064
|
+
return [];
|
|
17065
|
+
}
|
|
17066
|
+
})
|
|
17067
|
+
);
|
|
17068
|
+
return found.flat();
|
|
17069
|
+
}
|
|
17070
|
+
async function post(session, path, text3) {
|
|
17071
|
+
const file = await lstat(path);
|
|
17072
|
+
if (!file.isSocket() || process.getuid && file.uid !== process.getuid())
|
|
17073
|
+
throw new Error("Claude Code inbox is not owned by this user");
|
|
17074
|
+
const body2 = text3.replace(new RegExp(`<(?=/?${ENVELOPE})`, "gi"), "<\\");
|
|
17075
|
+
const line = JSON.stringify({
|
|
17076
|
+
type: "user",
|
|
17077
|
+
session_id: session.sessionId,
|
|
17078
|
+
priority: "next",
|
|
17079
|
+
message: { role: "user", content: `<${ENVELOPE} from-name="Dashup">
|
|
17080
|
+
${body2}
|
|
17081
|
+
</${ENVELOPE}>` }
|
|
17082
|
+
});
|
|
17083
|
+
await new Promise((resolve3, reject) => {
|
|
17084
|
+
const socket = connect2(path);
|
|
17085
|
+
socket.setTimeout(REQUEST_MS2, () => socket.destroy(new Error("timed out")));
|
|
17086
|
+
socket.once("error", () => reject(new Error("Claude Code is not accepting messages")));
|
|
17087
|
+
socket.end(`${line}
|
|
17088
|
+
`, resolve3);
|
|
17089
|
+
});
|
|
17090
|
+
}
|
|
17091
|
+
var Claude = class {
|
|
17092
|
+
constructor(changed) {
|
|
17093
|
+
this.changed = changed;
|
|
17094
|
+
}
|
|
17095
|
+
changed;
|
|
17096
|
+
watcher;
|
|
17097
|
+
notify;
|
|
17098
|
+
requests = /* @__PURE__ */ new Map();
|
|
17099
|
+
async refresh() {
|
|
17100
|
+
this.watch();
|
|
17101
|
+
return (await registrations()).map((session) => {
|
|
17102
|
+
const path = session.peerProtocol === PEER_PROTOCOL ? session.messagingSocketPath : void 0;
|
|
17103
|
+
return {
|
|
17104
|
+
id: session.sessionId,
|
|
17105
|
+
harness: "claude",
|
|
17106
|
+
cwd: session.cwd,
|
|
17107
|
+
updatedAt: new Date(session.updatedAt ?? 0).toISOString(),
|
|
17108
|
+
status: STATUS[session.status ?? ""] ?? "unknown",
|
|
17109
|
+
messages: [],
|
|
17110
|
+
approvals: [],
|
|
17111
|
+
send: path ? (requestId, text3) => this.once(requestId, () => post(session, path, text3)) : void 0
|
|
17112
|
+
};
|
|
17113
|
+
});
|
|
17114
|
+
}
|
|
17115
|
+
once(id, action) {
|
|
17116
|
+
const known = this.requests.get(id);
|
|
17117
|
+
if (known) return known;
|
|
17118
|
+
if (this.requests.size >= MAX_REQUESTS2) this.requests.delete(this.requests.keys().next().value);
|
|
17119
|
+
const result = action();
|
|
17120
|
+
this.requests.set(id, result);
|
|
17121
|
+
return result;
|
|
17122
|
+
}
|
|
17123
|
+
watch() {
|
|
17124
|
+
if (this.watcher || process.env.DASHUP_LIVE_DISCOVERY === "0") return;
|
|
17125
|
+
try {
|
|
17126
|
+
const watcher = this.watcher = watch2(join5(claudeHome(), "sessions"), () => {
|
|
17127
|
+
if (this.notify) return;
|
|
17128
|
+
this.notify = setTimeout(() => {
|
|
17129
|
+
this.notify = void 0;
|
|
17130
|
+
this.changed();
|
|
17131
|
+
}, 100);
|
|
17132
|
+
this.notify.unref();
|
|
17133
|
+
});
|
|
17134
|
+
watcher.on("error", () => {
|
|
17135
|
+
watcher.close();
|
|
17136
|
+
if (this.watcher === watcher) this.watcher = void 0;
|
|
17137
|
+
});
|
|
17138
|
+
} catch {
|
|
17139
|
+
}
|
|
17140
|
+
}
|
|
17141
|
+
stop() {
|
|
17142
|
+
clearTimeout(this.notify);
|
|
17143
|
+
this.watcher?.close();
|
|
17144
|
+
this.watcher = void 0;
|
|
17145
|
+
}
|
|
17146
|
+
};
|
|
17147
|
+
|
|
16733
17148
|
// src/harness/vscode.ts
|
|
16734
17149
|
import Socket3 from "ws";
|
|
16735
17150
|
import { homedir as homedir5 } from "node:os";
|
|
16736
|
-
import { connect as
|
|
16737
|
-
import { promisify as
|
|
17151
|
+
import { connect as connect3 } from "node:net";
|
|
17152
|
+
import { promisify as promisify4 } from "node:util";
|
|
16738
17153
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
16739
|
-
import { execFile as
|
|
16740
|
-
import { createHash as createHash3, randomUUID as
|
|
16741
|
-
import { stat as stat2, readdir as
|
|
16742
|
-
import { join as
|
|
17154
|
+
import { execFile as execFile4 } from "node:child_process";
|
|
17155
|
+
import { createHash as createHash3, randomUUID as randomUUID4 } from "node:crypto";
|
|
17156
|
+
import { stat as stat2, readdir as readdir3, readFile as readFile3 } from "node:fs/promises";
|
|
17157
|
+
import { join as join6, dirname as dirname4, basename as basename2, isAbsolute as isAbsolute3 } from "node:path";
|
|
16743
17158
|
import { WebSocketTransport } from "@microsoft/agent-host-protocol/ws";
|
|
16744
17159
|
import { AhpClient } from "@microsoft/agent-host-protocol/client";
|
|
16745
17160
|
import { chatReducer, sessionReducer, SUPPORTED_PROTOCOL_VERSIONS } from "@microsoft/agent-host-protocol";
|
|
16746
|
-
var
|
|
17161
|
+
var REQUEST_MS3 = 4e3;
|
|
16747
17162
|
var MAX_BYTES2 = 8 * 1024 * 1024;
|
|
16748
17163
|
var MAX_METADATA = 64 * 1024;
|
|
16749
17164
|
var MAX_HOSTS = 16;
|
|
16750
|
-
var
|
|
17165
|
+
var MAX_SESSIONS3 = 100;
|
|
16751
17166
|
var ROOT_CHANNEL = "ahp-root://";
|
|
16752
|
-
var run2 =
|
|
17167
|
+
var run2 = promisify4(execFile4);
|
|
16753
17168
|
var CONFIRMATION_CHOICES = [
|
|
16754
17169
|
{ id: "approve", label: "Allow once", kind: "approve" },
|
|
16755
17170
|
{ id: "deny", label: "Reject", kind: "deny" }
|
|
@@ -16757,10 +17172,10 @@ var CONFIRMATION_CHOICES = [
|
|
|
16757
17172
|
function roots() {
|
|
16758
17173
|
const override = process.env.DASHUP_AHP_DATA ?? process.env.DASHUP_VSCODE_DATA;
|
|
16759
17174
|
if (override !== void 0)
|
|
16760
|
-
return override ? [basename2(override) === "User" ?
|
|
17175
|
+
return override ? [basename2(override) === "User" ? dirname4(override) : override] : [];
|
|
16761
17176
|
if (process.env.DASHUP_LIVE_DISCOVERY === "0") return [];
|
|
16762
|
-
const base = process.platform === "darwin" ?
|
|
16763
|
-
return ["Code", "Code - Insiders"].map((name) =>
|
|
17177
|
+
const base = process.platform === "darwin" ? join6(homedir5(), "Library/Application Support") : process.platform === "win32" ? process.env.APPDATA ?? join6(homedir5(), "AppData/Roaming") : process.env.XDG_CONFIG_HOME ?? join6(homedir5(), ".config");
|
|
17178
|
+
return ["Code", "Code - Insiders"].map((name) => join6(base, name));
|
|
16764
17179
|
}
|
|
16765
17180
|
async function privateFile(path, limit) {
|
|
16766
17181
|
const file = await stat2(path).catch(() => void 0);
|
|
@@ -16777,7 +17192,7 @@ async function privateFile(path, limit) {
|
|
|
16777
17192
|
"-EncodedCommand",
|
|
16778
17193
|
Buffer.from(script, "utf16le").toString("base64")
|
|
16779
17194
|
],
|
|
16780
|
-
{ timeout:
|
|
17195
|
+
{ timeout: REQUEST_MS3, maxBuffer: MAX_METADATA }
|
|
16781
17196
|
);
|
|
16782
17197
|
return stdout.trim().toLowerCase() === "true";
|
|
16783
17198
|
} catch {
|
|
@@ -16789,16 +17204,16 @@ async function privateFile(path, limit) {
|
|
|
16789
17204
|
async function entries() {
|
|
16790
17205
|
const result = /* @__PURE__ */ new Map();
|
|
16791
17206
|
for (const root of roots()) {
|
|
16792
|
-
const registry2 =
|
|
16793
|
-
const files2 = (await
|
|
17207
|
+
const registry2 = join6(root, "agent-host/local-endpoint");
|
|
17208
|
+
const files2 = (await readdir3(join6(registry2, "entries")).catch(() => [])).filter((name) => name.endsWith(".json")).slice(0, MAX_HOSTS);
|
|
16794
17209
|
const sources = [
|
|
16795
|
-
...files2.map((name) =>
|
|
16796
|
-
|
|
17210
|
+
...files2.map((name) => join6(registry2, "entries", name)),
|
|
17211
|
+
join6(registry2, "metadata.json")
|
|
16797
17212
|
];
|
|
16798
17213
|
for (const path of sources) {
|
|
16799
17214
|
try {
|
|
16800
17215
|
if (!await privateFile(path, MAX_METADATA)) continue;
|
|
16801
|
-
const parsed = JSON.parse(await
|
|
17216
|
+
const parsed = JSON.parse(await readFile3(path, "utf8"));
|
|
16802
17217
|
const rows = basename2(path) === "metadata.json" && Array.isArray(parsed) ? parsed : [parsed];
|
|
16803
17218
|
for (const row of rows.slice(0, MAX_HOSTS)) {
|
|
16804
17219
|
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))
|
|
@@ -16818,7 +17233,7 @@ async function entries() {
|
|
|
16818
17233
|
if (!socket?.isSocket() || process.getuid && socket.uid !== process.getuid())
|
|
16819
17234
|
continue;
|
|
16820
17235
|
}
|
|
16821
|
-
if (!await privateFile(
|
|
17236
|
+
if (!await privateFile(dirname4(path), MAX_BYTES2)) continue;
|
|
16822
17237
|
const key = `${root}\0${identity2}`;
|
|
16823
17238
|
if (!result.has(key))
|
|
16824
17239
|
result.set(key, {
|
|
@@ -16931,7 +17346,7 @@ var Host = class {
|
|
|
16931
17346
|
client;
|
|
16932
17347
|
connecting;
|
|
16933
17348
|
stopped = false;
|
|
16934
|
-
clientId = `dashup-${
|
|
17349
|
+
clientId = `dashup-${randomUUID4()}`;
|
|
16935
17350
|
resources = /* @__PURE__ */ new Map();
|
|
16936
17351
|
subscriptions = /* @__PURE__ */ new Set();
|
|
16937
17352
|
receipts = /* @__PURE__ */ new Map();
|
|
@@ -16983,9 +17398,9 @@ var Host = class {
|
|
|
16983
17398
|
if (this.connecting) return this.connecting;
|
|
16984
17399
|
this.connecting = (async () => {
|
|
16985
17400
|
const socket = new Socket3(`ws://localhost/?tkn=${encodeURIComponent(this.entry.token)}`, {
|
|
16986
|
-
createConnection: () =>
|
|
17401
|
+
createConnection: () => connect3(this.entry.path),
|
|
16987
17402
|
maxPayload: MAX_BYTES2,
|
|
16988
|
-
handshakeTimeout:
|
|
17403
|
+
handshakeTimeout: REQUEST_MS3
|
|
16989
17404
|
});
|
|
16990
17405
|
socket.on("error", () => {
|
|
16991
17406
|
});
|
|
@@ -17018,7 +17433,7 @@ var Host = class {
|
|
|
17018
17433
|
}
|
|
17019
17434
|
};
|
|
17020
17435
|
const client = this.client = new AhpClient(transport, {
|
|
17021
|
-
requestTimeoutMs:
|
|
17436
|
+
requestTimeoutMs: REQUEST_MS3,
|
|
17022
17437
|
subscriptionBuffer: 1024
|
|
17023
17438
|
});
|
|
17024
17439
|
this.resources.clear();
|
|
@@ -17082,7 +17497,7 @@ var Host = class {
|
|
|
17082
17497
|
const timer = setTimeout(() => {
|
|
17083
17498
|
this.receipts.delete(handle.clientSeq);
|
|
17084
17499
|
reject(new Error("Native AHP action was not acknowledged; delivery is unknown"));
|
|
17085
|
-
},
|
|
17500
|
+
}, REQUEST_MS3);
|
|
17086
17501
|
this.receipts.set(handle.clientSeq, { resolve: resolve3, reject, timer });
|
|
17087
17502
|
});
|
|
17088
17503
|
this.requests.set(requestId, pending);
|
|
@@ -17092,12 +17507,12 @@ var Host = class {
|
|
|
17092
17507
|
await this.connect();
|
|
17093
17508
|
const catalog = await this.client.request("listSessions", {
|
|
17094
17509
|
channel: ROOT_CHANNEL,
|
|
17095
|
-
limit:
|
|
17510
|
+
limit: MAX_SESSIONS3
|
|
17096
17511
|
});
|
|
17097
17512
|
if (!Array.isArray(catalog.items)) return [];
|
|
17098
17513
|
const result = [];
|
|
17099
17514
|
const active = /* @__PURE__ */ new Set();
|
|
17100
|
-
for (const summary of catalog.items.slice(0,
|
|
17515
|
+
for (const summary of catalog.items.slice(0, MAX_SESSIONS3)) {
|
|
17101
17516
|
try {
|
|
17102
17517
|
const session = await this.subscribe(summary.resource);
|
|
17103
17518
|
if (!session || session.lifecycle !== "ready" || !Array.isArray(session.chats)) continue;
|
|
@@ -17159,7 +17574,7 @@ var Host = class {
|
|
|
17159
17574
|
message: { text: value2, origin: { kind: "user" } }
|
|
17160
17575
|
});
|
|
17161
17576
|
} : void 0,
|
|
17162
|
-
approve: approvals.some((
|
|
17577
|
+
approve: approvals.some((approval3) => approval3.choices.length) ? (requestId, approvalId, choice) => {
|
|
17163
17578
|
const prior = this.requests.get(requestId);
|
|
17164
17579
|
if (prior) return prior;
|
|
17165
17580
|
const current = this.resources.get(summary.resource)?.state;
|
|
@@ -17230,15 +17645,15 @@ var VSCode = class {
|
|
|
17230
17645
|
};
|
|
17231
17646
|
|
|
17232
17647
|
// src/harness/opencode.ts
|
|
17233
|
-
import { join as
|
|
17234
|
-
import { promisify as
|
|
17648
|
+
import { join as join7 } from "node:path";
|
|
17649
|
+
import { promisify as promisify5 } from "node:util";
|
|
17235
17650
|
import { createHash as createHash4 } from "node:crypto";
|
|
17236
|
-
import { execFile as
|
|
17237
|
-
import { stat as stat3, readlink, readdir as
|
|
17238
|
-
var run3 =
|
|
17239
|
-
var
|
|
17651
|
+
import { execFile as execFile5, spawn as spawn4 } from "node:child_process";
|
|
17652
|
+
import { stat as stat3, readlink, readdir as readdir4, readFile as readFile4 } from "node:fs/promises";
|
|
17653
|
+
var run3 = promisify5(execFile5);
|
|
17654
|
+
var REQUEST_MS4 = 4e3;
|
|
17240
17655
|
var MAX_BYTES3 = 8 * 1024 * 1024;
|
|
17241
|
-
var
|
|
17656
|
+
var MAX_SESSIONS4 = 100;
|
|
17242
17657
|
var MAX_PROCESSES2 = 4096;
|
|
17243
17658
|
var MAX_ENDPOINTS = 32;
|
|
17244
17659
|
var EVENT_DELAY_MS = 100;
|
|
@@ -17261,10 +17676,10 @@ function authorization(environment) {
|
|
|
17261
17676
|
}
|
|
17262
17677
|
async function linuxEndpoints() {
|
|
17263
17678
|
const result = [];
|
|
17264
|
-
const processes = (await
|
|
17679
|
+
const processes = (await readdir4("/proc")).filter((id) => /^\d+$/.test(id)).slice(0, MAX_PROCESSES2);
|
|
17265
17680
|
const listeners = /* @__PURE__ */ new Map();
|
|
17266
17681
|
for (const name of ["tcp", "tcp6"]) {
|
|
17267
|
-
const table = await
|
|
17682
|
+
const table = await readFile4(`/proc/net/${name}`, "utf8").catch(() => "");
|
|
17268
17683
|
for (const line of table.split("\n").slice(1)) {
|
|
17269
17684
|
const columns = line.trim().split(/\s+/);
|
|
17270
17685
|
if (columns[3] !== "0A") continue;
|
|
@@ -17280,16 +17695,16 @@ async function linuxEndpoints() {
|
|
|
17280
17695
|
const directory2 = `/proc/${pid}`;
|
|
17281
17696
|
const owner = await stat3(directory2);
|
|
17282
17697
|
if (process.getuid && owner.uid !== process.getuid()) continue;
|
|
17283
|
-
const command2 = (await
|
|
17698
|
+
const command2 = (await readFile4(join7(directory2, "cmdline"), "utf8")).slice(0, 4096);
|
|
17284
17699
|
if (!/(?:^|[\/\0 ])opencode(?:[\0 .]|$)/.test(command2)) continue;
|
|
17285
17700
|
const environment = Object.fromEntries(
|
|
17286
|
-
(await
|
|
17701
|
+
(await readFile4(join7(directory2, "environ"), "utf8")).split("\0").map((entry) => {
|
|
17287
17702
|
const split = entry.indexOf("=");
|
|
17288
17703
|
return [entry.slice(0, split), entry.slice(split + 1)];
|
|
17289
17704
|
})
|
|
17290
17705
|
);
|
|
17291
|
-
for (const fd of (await
|
|
17292
|
-
const target = await readlink(
|
|
17706
|
+
for (const fd of (await readdir4(join7(directory2, "fd"))).slice(0, 1024)) {
|
|
17707
|
+
const target = await readlink(join7(directory2, "fd", fd)).catch(() => "");
|
|
17293
17708
|
const inode = /^socket:\[(\d+)\]$/.exec(target)?.[1];
|
|
17294
17709
|
const url2 = inode ? listeners.get(inode) : void 0;
|
|
17295
17710
|
if (url2) result.push({ url: url2, authorization: authorization(environment) });
|
|
@@ -17318,7 +17733,7 @@ async function endpoints() {
|
|
|
17318
17733
|
const { stdout } = await run3(
|
|
17319
17734
|
"lsof",
|
|
17320
17735
|
["-nP", "-a", "-c", "opencode", "-iTCP", "-sTCP:LISTEN", "-Fn"],
|
|
17321
|
-
{ timeout:
|
|
17736
|
+
{ timeout: REQUEST_MS4, maxBuffer: MAX_BYTES3 }
|
|
17322
17737
|
);
|
|
17323
17738
|
return stdout.split("\n").flatMap((line) => {
|
|
17324
17739
|
const port = /^n(?:127\.0\.0\.1|\[::1\]|\*):(\d+)$/.exec(line)?.[1];
|
|
@@ -17335,7 +17750,7 @@ async function endpoints() {
|
|
|
17335
17750
|
const { stdout } = await run3(
|
|
17336
17751
|
"powershell.exe",
|
|
17337
17752
|
["-NoProfile", "-NonInteractive", "-Command", script],
|
|
17338
|
-
{ timeout:
|
|
17753
|
+
{ timeout: REQUEST_MS4, maxBuffer: MAX_BYTES3 }
|
|
17339
17754
|
);
|
|
17340
17755
|
return stdout.split(/\r?\n/).filter((port) => /^\d+$/.test(port)).slice(0, MAX_ENDPOINTS).map((port) => ({
|
|
17341
17756
|
url: localEndpoint(`http://127.0.0.1:${port}`),
|
|
@@ -17455,7 +17870,7 @@ var Server = class {
|
|
|
17455
17870
|
};
|
|
17456
17871
|
}
|
|
17457
17872
|
async request(path, directory2, payload) {
|
|
17458
|
-
const signal = AbortSignal.timeout(
|
|
17873
|
+
const signal = AbortSignal.timeout(REQUEST_MS4);
|
|
17459
17874
|
const response = await fetch(this.endpoint.url + path, {
|
|
17460
17875
|
method: payload === void 0 ? "GET" : "POST",
|
|
17461
17876
|
headers: {
|
|
@@ -17482,15 +17897,25 @@ var Server = class {
|
|
|
17482
17897
|
this.requests.set(id, pending);
|
|
17483
17898
|
return pending;
|
|
17484
17899
|
}
|
|
17900
|
+
// A session Dashup starts takes OpenCode's default agent and model for its folder.
|
|
17901
|
+
async start(cwd, text3) {
|
|
17902
|
+
const id = (await this.request("/session", cwd, {}))?.id;
|
|
17903
|
+
if (typeof id !== "string") throw new Error("OpenCode did not start a session");
|
|
17904
|
+
await this.request(`/session/${encodeURIComponent(id)}/prompt_async`, cwd, {
|
|
17905
|
+
parts: [{ type: "text", text: text3 }]
|
|
17906
|
+
});
|
|
17907
|
+
this.changed();
|
|
17908
|
+
return id;
|
|
17909
|
+
}
|
|
17485
17910
|
async read() {
|
|
17486
17911
|
const health = await this.request("/global/health");
|
|
17487
17912
|
if (health?.healthy !== true || typeof health.version !== "string") return [];
|
|
17488
17913
|
this.listen();
|
|
17489
|
-
const sessions = await this.request(`/session?limit=${
|
|
17914
|
+
const sessions = await this.request(`/session?limit=${MAX_SESSIONS4}`);
|
|
17490
17915
|
if (!Array.isArray(sessions)) return [];
|
|
17491
17916
|
const result = [];
|
|
17492
17917
|
const states = /* @__PURE__ */ new Map();
|
|
17493
|
-
for (const session of sessions.slice(0,
|
|
17918
|
+
for (const session of sessions.slice(0, MAX_SESSIONS4)) {
|
|
17494
17919
|
if (typeof session?.id !== "string" || typeof session.directory !== "string" || !session.directory)
|
|
17495
17920
|
continue;
|
|
17496
17921
|
try {
|
|
@@ -17566,7 +17991,7 @@ var Server = class {
|
|
|
17566
17991
|
});
|
|
17567
17992
|
this.changed();
|
|
17568
17993
|
}),
|
|
17569
|
-
approve: approvals.some((
|
|
17994
|
+
approve: approvals.some((approval3) => approval3.choices.length) ? (requestId, approvalId, choice) => this.once(requestId, async () => {
|
|
17570
17995
|
if (this.stopped) throw new Error("OpenCode is disconnected");
|
|
17571
17996
|
if (approvalId.startsWith("question:")) {
|
|
17572
17997
|
const requestID = approvalId.slice("question:".length);
|
|
@@ -17615,7 +18040,7 @@ var Server = class {
|
|
|
17615
18040
|
listen() {
|
|
17616
18041
|
if (this.stream || this.retry || this.stopped) return;
|
|
17617
18042
|
const controller = this.stream = new AbortController();
|
|
17618
|
-
const connectTimer = setTimeout(() => controller.abort(),
|
|
18043
|
+
const connectTimer = setTimeout(() => controller.abort(), REQUEST_MS4);
|
|
17619
18044
|
connectTimer.unref();
|
|
17620
18045
|
void (async () => {
|
|
17621
18046
|
const response = await fetch(this.endpoint.url + "/global/event", {
|
|
@@ -17684,15 +18109,57 @@ var Server = class {
|
|
|
17684
18109
|
clearTimeout(this.notify);
|
|
17685
18110
|
}
|
|
17686
18111
|
};
|
|
18112
|
+
function serve(cwd) {
|
|
18113
|
+
const child = spawn4("opencode", ["serve", "--hostname", "127.0.0.1", "--port", "0"], {
|
|
18114
|
+
cwd,
|
|
18115
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
18116
|
+
shell: process.platform === "win32"
|
|
18117
|
+
});
|
|
18118
|
+
const endpoint = new Promise((resolve3, reject) => {
|
|
18119
|
+
let output = "";
|
|
18120
|
+
child.stdout.on("data", (chunk) => {
|
|
18121
|
+
output = (output + String(chunk)).slice(-4096);
|
|
18122
|
+
const port = /https?:\/\/(?:127\.0\.0\.1|localhost):(\d+)/.exec(output)?.[1];
|
|
18123
|
+
if (port)
|
|
18124
|
+
resolve3({
|
|
18125
|
+
url: localEndpoint(`http://127.0.0.1:${port}`),
|
|
18126
|
+
authorization: authorization(process.env)
|
|
18127
|
+
});
|
|
18128
|
+
});
|
|
18129
|
+
child.once("error", () => reject(new Error("OpenCode is not installed on this computer")));
|
|
18130
|
+
child.once("exit", () => reject(new Error("OpenCode stopped before it started serving")));
|
|
18131
|
+
});
|
|
18132
|
+
return { process: child, endpoint };
|
|
18133
|
+
}
|
|
17687
18134
|
var OpenCode = class {
|
|
17688
18135
|
constructor(changed) {
|
|
17689
18136
|
this.changed = changed;
|
|
17690
18137
|
}
|
|
17691
18138
|
changed;
|
|
17692
18139
|
servers = /* @__PURE__ */ new Map();
|
|
18140
|
+
// Servers this connector started, by folder.
|
|
18141
|
+
started = /* @__PURE__ */ new Map();
|
|
18142
|
+
async start(cwd, text3) {
|
|
18143
|
+
let served = this.started.get(cwd);
|
|
18144
|
+
if (!served) this.started.set(cwd, served = serve(cwd));
|
|
18145
|
+
const endpoint = await served.endpoint.catch((error) => {
|
|
18146
|
+
this.started.delete(cwd);
|
|
18147
|
+
throw error;
|
|
18148
|
+
});
|
|
18149
|
+
let server = this.servers.get(endpoint.url);
|
|
18150
|
+
if (!server) {
|
|
18151
|
+
server = new Server(endpoint, this.changed);
|
|
18152
|
+
this.servers.set(endpoint.url, server);
|
|
18153
|
+
}
|
|
18154
|
+
return server.start(cwd, text3);
|
|
18155
|
+
}
|
|
17693
18156
|
async refresh() {
|
|
17694
18157
|
const discovered = await endpoints().catch(() => []);
|
|
17695
18158
|
const current = new Map(discovered.map((endpoint) => [endpoint.url, endpoint]));
|
|
18159
|
+
for (const served of this.started.values()) {
|
|
18160
|
+
const endpoint = await served.endpoint.catch(() => void 0);
|
|
18161
|
+
if (endpoint) current.set(endpoint.url, endpoint);
|
|
18162
|
+
}
|
|
17696
18163
|
for (const [url2, server] of this.servers)
|
|
17697
18164
|
if (!current.has(url2)) {
|
|
17698
18165
|
server.stop();
|
|
@@ -17713,6 +18180,8 @@ var OpenCode = class {
|
|
|
17713
18180
|
stop() {
|
|
17714
18181
|
for (const server of this.servers.values()) server.stop();
|
|
17715
18182
|
this.servers.clear();
|
|
18183
|
+
for (const served of this.started.values()) served.process.kill();
|
|
18184
|
+
this.started.clear();
|
|
17716
18185
|
}
|
|
17717
18186
|
};
|
|
17718
18187
|
|
|
@@ -17721,6 +18190,8 @@ var DISCOVERY_MS = 1e4;
|
|
|
17721
18190
|
var LiveHarnesses = class {
|
|
17722
18191
|
listeners = /* @__PURE__ */ new Set();
|
|
17723
18192
|
codex = new Codex(() => this.changed());
|
|
18193
|
+
headless = new Headless(() => this.changed());
|
|
18194
|
+
claude = new Claude(() => this.changed());
|
|
17724
18195
|
vscode = new VSCode(() => this.changed());
|
|
17725
18196
|
opencode = new OpenCode(() => this.changed());
|
|
17726
18197
|
timer;
|
|
@@ -17734,8 +18205,10 @@ var LiveHarnesses = class {
|
|
|
17734
18205
|
if (this.refreshing) return this.refreshing;
|
|
17735
18206
|
this.refreshing = (async () => {
|
|
17736
18207
|
const results = await Promise.allSettled([
|
|
18208
|
+
this.headless.refresh(),
|
|
17737
18209
|
this.opencode.refresh(),
|
|
17738
18210
|
this.codex.refresh(),
|
|
18211
|
+
this.claude.refresh(),
|
|
17739
18212
|
this.vscode.refresh()
|
|
17740
18213
|
]);
|
|
17741
18214
|
const sessions = /* @__PURE__ */ new Map();
|
|
@@ -17751,6 +18224,27 @@ var LiveHarnesses = class {
|
|
|
17751
18224
|
});
|
|
17752
18225
|
return this.refreshing;
|
|
17753
18226
|
}
|
|
18227
|
+
// The agents whose own command runs on this computer.
|
|
18228
|
+
async agents() {
|
|
18229
|
+
const harnesses = ["claude", "codex", "opencode"];
|
|
18230
|
+
const runs = await Promise.all(
|
|
18231
|
+
harnesses.map((harness) => promisify6(execFile6)(harness, ["--version"], {
|
|
18232
|
+
timeout: 1e4,
|
|
18233
|
+
shell: process.platform === "win32"
|
|
18234
|
+
}).then(
|
|
18235
|
+
() => true,
|
|
18236
|
+
() => false
|
|
18237
|
+
))
|
|
18238
|
+
);
|
|
18239
|
+
return harnesses.filter((_, index) => runs[index]);
|
|
18240
|
+
}
|
|
18241
|
+
// Starts a session in `cwd` with its first message, and resolves with the session's native id.
|
|
18242
|
+
start(harness, cwd, text3) {
|
|
18243
|
+
return { claude: this.headless, codex: this.codex, opencode: this.opencode }[harness].start(
|
|
18244
|
+
cwd,
|
|
18245
|
+
text3
|
|
18246
|
+
);
|
|
18247
|
+
}
|
|
17754
18248
|
watch(onChange) {
|
|
17755
18249
|
this.listeners.add(onChange);
|
|
17756
18250
|
if (!this.timer && process.env.DASHUP_LIVE_DISCOVERY !== "0") {
|
|
@@ -17769,20 +18263,22 @@ var LiveHarnesses = class {
|
|
|
17769
18263
|
this.stopped = true;
|
|
17770
18264
|
clearInterval(this.timer);
|
|
17771
18265
|
this.listeners.clear();
|
|
18266
|
+
this.headless.stop();
|
|
17772
18267
|
this.opencode.stop();
|
|
17773
18268
|
this.codex.stop();
|
|
18269
|
+
this.claude.stop();
|
|
17774
18270
|
this.vscode.stop();
|
|
17775
18271
|
}
|
|
17776
18272
|
};
|
|
17777
18273
|
|
|
17778
18274
|
// src/harness/queue.ts
|
|
17779
|
-
import { randomUUID as
|
|
17780
|
-
import { spawn as
|
|
18275
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
18276
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
17781
18277
|
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
17782
|
-
import { join as
|
|
18278
|
+
import { join as join8, dirname as dirname5, basename as basename3 } from "node:path";
|
|
17783
18279
|
import { statSync as statSync2, existsSync as existsSync5, readdirSync as readdirSync3, readFileSync as readFileSync3, readlinkSync, realpathSync as realpathSync2 } from "node:fs";
|
|
17784
18280
|
var CODEX_VERSION = "0.160.1";
|
|
17785
|
-
var
|
|
18281
|
+
var REQUEST_MS5 = 1e4;
|
|
17786
18282
|
var MAX_BYTES4 = 1024 * 1024;
|
|
17787
18283
|
var MAX_PROCESSES3 = 4096;
|
|
17788
18284
|
var MAX_FILES = 4096;
|
|
@@ -17798,9 +18294,9 @@ function sameFile(left, right) {
|
|
|
17798
18294
|
return left.path === right.path && left.device === right.device && left.inode === right.inode;
|
|
17799
18295
|
}
|
|
17800
18296
|
function packageVersion(executable) {
|
|
17801
|
-
let directory2 =
|
|
17802
|
-
for (let depth = 0; depth < 8; depth++, directory2 =
|
|
17803
|
-
const path =
|
|
18297
|
+
let directory2 = dirname5(executable);
|
|
18298
|
+
for (let depth = 0; depth < 8; depth++, directory2 = dirname5(directory2)) {
|
|
18299
|
+
const path = join8(directory2, "package.json");
|
|
17804
18300
|
if (!existsSync5(path)) continue;
|
|
17805
18301
|
const value2 = JSON.parse(readFileSync3(path, "utf8"));
|
|
17806
18302
|
if (value2.name?.startsWith("@openai/codex-")) return value2.version;
|
|
@@ -17810,40 +18306,40 @@ function packageVersion(executable) {
|
|
|
17810
18306
|
function processOwners(pid) {
|
|
17811
18307
|
const root = `/proc/${pid}`;
|
|
17812
18308
|
if (process.getuid && statSync2(root).uid !== process.getuid()) return [];
|
|
17813
|
-
const executable = realpathSync2.native(
|
|
18309
|
+
const executable = realpathSync2.native(join8(root, "exe"));
|
|
17814
18310
|
if (basename3(executable) !== "codex" || packageVersion(executable) !== CODEX_VERSION) return [];
|
|
17815
|
-
const status2 = readFileSync3(
|
|
18311
|
+
const status2 = readFileSync3(join8(root, "stat"), "utf8");
|
|
17816
18312
|
const start = status2.slice(status2.lastIndexOf(")") + 2).split(" ")[19];
|
|
17817
18313
|
if (!start || !/^\d+$/.test(start)) return [];
|
|
17818
|
-
const fds = readdirSync3(
|
|
18314
|
+
const fds = readdirSync3(join8(root, "fd")).slice(0, MAX_FILES);
|
|
17819
18315
|
const files2 = fds.flatMap((fd) => {
|
|
17820
18316
|
try {
|
|
17821
|
-
return [{ fd, path: readlinkSync(
|
|
18317
|
+
return [{ fd, path: readlinkSync(join8(root, "fd", fd)) }];
|
|
17822
18318
|
} catch {
|
|
17823
18319
|
return [];
|
|
17824
18320
|
}
|
|
17825
18321
|
});
|
|
17826
18322
|
return files2.flatMap(({ fd, path }) => {
|
|
17827
|
-
if (basename3(
|
|
18323
|
+
if (basename3(dirname5(path)) !== "thread-writer-locks" || !path.endsWith(".lock")) return [];
|
|
17828
18324
|
const thread = basename3(path, ".lock");
|
|
17829
18325
|
if (!THREAD_ID.test(thread)) return [];
|
|
17830
18326
|
try {
|
|
17831
|
-
const lock = readFileSync3(
|
|
18327
|
+
const lock = readFileSync3(join8(root, "fdinfo", fd), "utf8");
|
|
17832
18328
|
if (!/FLOCK\s+ADVISORY\s+WRITE/.test(lock)) return [];
|
|
17833
|
-
const home = realpathSync2.native(
|
|
18329
|
+
const home = realpathSync2.native(dirname5(dirname5(path)));
|
|
17834
18330
|
const directory2 = statSync2(home);
|
|
17835
18331
|
if (process.getuid && directory2.uid !== process.getuid()) return [];
|
|
17836
|
-
const queue = identity(
|
|
18332
|
+
const queue = identity(join8(home, "queue_1.sqlite"));
|
|
17837
18333
|
if (!files2.some((file) => {
|
|
17838
18334
|
try {
|
|
17839
|
-
return sameFile(identity(
|
|
18335
|
+
return sameFile(identity(join8(root, "fd", file.fd)), queue);
|
|
17840
18336
|
} catch {
|
|
17841
18337
|
return false;
|
|
17842
18338
|
}
|
|
17843
18339
|
}))
|
|
17844
18340
|
return [];
|
|
17845
18341
|
const rollout = files2.find(
|
|
17846
|
-
(file) => file.path.startsWith(
|
|
18342
|
+
(file) => file.path.startsWith(join8(home, "sessions") + "/") && file.path.endsWith(`-${thread}.jsonl`)
|
|
17847
18343
|
);
|
|
17848
18344
|
if (!rollout) return [];
|
|
17849
18345
|
return [
|
|
@@ -17855,7 +18351,7 @@ function processOwners(pid) {
|
|
|
17855
18351
|
thread,
|
|
17856
18352
|
queue,
|
|
17857
18353
|
version: CODEX_VERSION,
|
|
17858
|
-
cwd: realpathSync2.native(
|
|
18354
|
+
cwd: realpathSync2.native(join8(root, "cwd")),
|
|
17859
18355
|
rollout: identity(rollout.path)
|
|
17860
18356
|
}
|
|
17861
18357
|
];
|
|
@@ -17901,7 +18397,7 @@ function validateOwner(owner) {
|
|
|
17901
18397
|
function validateEvidence(owner) {
|
|
17902
18398
|
if (realpathSync2.native(owner.home) !== owner.home || process.getuid && statSync2(owner.home).uid !== process.getuid() || !sameFile(identity(owner.queue.path), owner.queue) || !sameFile(identity(owner.rollout.path), owner.rollout))
|
|
17903
18399
|
throw new Error("The original native history storage changed");
|
|
17904
|
-
const path =
|
|
18400
|
+
const path = join8(owner.home, "thread_history_1.sqlite");
|
|
17905
18401
|
if (existsSync5(path) && identity(path).path !== path)
|
|
17906
18402
|
throw new Error("Native history is outside the original home");
|
|
17907
18403
|
}
|
|
@@ -17918,7 +18414,7 @@ function nativeReceipt(owner, clientId) {
|
|
|
17918
18414
|
} finally {
|
|
17919
18415
|
queue.close();
|
|
17920
18416
|
}
|
|
17921
|
-
const path =
|
|
18417
|
+
const path = join8(owner.home, "thread_history_1.sqlite");
|
|
17922
18418
|
if (!existsSync5(path)) return { queueId };
|
|
17923
18419
|
const history = new DatabaseSync3(path, { readOnly: true });
|
|
17924
18420
|
try {
|
|
@@ -17974,7 +18470,7 @@ var Writer = class {
|
|
|
17974
18470
|
tail = "";
|
|
17975
18471
|
pending = /* @__PURE__ */ new Map();
|
|
17976
18472
|
constructor(owner) {
|
|
17977
|
-
this.process =
|
|
18473
|
+
this.process = spawn5(owner.executable, ["app-server", "--listen", "stdio://"], {
|
|
17978
18474
|
cwd: owner.cwd,
|
|
17979
18475
|
env: { ...process.env, CODEX_HOME: owner.home },
|
|
17980
18476
|
stdio: ["pipe", "pipe", "ignore"]
|
|
@@ -18014,7 +18510,7 @@ var Writer = class {
|
|
|
18014
18510
|
this.pending.delete(id);
|
|
18015
18511
|
reject(new Error("Native queue acknowledgement was not received"));
|
|
18016
18512
|
this.close();
|
|
18017
|
-
},
|
|
18513
|
+
}, REQUEST_MS5);
|
|
18018
18514
|
this.pending.set(id, { resolve: resolve3, reject, timer });
|
|
18019
18515
|
this.process.stdin.write(JSON.stringify({ id, method, params }) + "\n");
|
|
18020
18516
|
});
|
|
@@ -18061,7 +18557,7 @@ var NativeQueue = class {
|
|
|
18061
18557
|
if (existing) return existing;
|
|
18062
18558
|
if (this.stopped) throw new Error("The connector is stopping");
|
|
18063
18559
|
validateOwner(scope2.owner);
|
|
18064
|
-
const clientUserMessageId =
|
|
18560
|
+
const clientUserMessageId = randomUUID5();
|
|
18065
18561
|
const request2 = { ...scope2, receipt: { state: "unknown", clientUserMessageId } };
|
|
18066
18562
|
let writer;
|
|
18067
18563
|
try {
|
|
@@ -18146,7 +18642,13 @@ var Command = z2.object({
|
|
|
18146
18642
|
z2.object({ kind: z2.literal("thread.stop") }).strict(),
|
|
18147
18643
|
z2.object({ kind: z2.literal("approval.respond"), approvalId: z2.string(), choice: z2.string() }).strict(),
|
|
18148
18644
|
z2.object({ kind: z2.literal("screen.start"), sessionId: z2.string().min(1).max(128) }).strict(),
|
|
18149
|
-
z2.object({ kind: z2.literal("screen.stop"), sessionId: z2.string().min(1).max(128) }).strict()
|
|
18645
|
+
z2.object({ kind: z2.literal("screen.stop"), sessionId: z2.string().min(1).max(128) }).strict(),
|
|
18646
|
+
z2.object({
|
|
18647
|
+
kind: z2.literal("session.start"),
|
|
18648
|
+
harness: z2.enum(["claude", "codex", "opencode"]),
|
|
18649
|
+
text: z2.string().min(1).max(1e5),
|
|
18650
|
+
folder: z2.object({ parent: z2.string().min(1), name: z2.string().min(1).max(255) }).strict().optional()
|
|
18651
|
+
}).strict()
|
|
18150
18652
|
])
|
|
18151
18653
|
}).strict();
|
|
18152
18654
|
var Connector = class {
|
|
@@ -18208,6 +18710,8 @@ var Connector = class {
|
|
|
18208
18710
|
listed = /* @__PURE__ */ new Map();
|
|
18209
18711
|
listedProjects = /* @__PURE__ */ new Map();
|
|
18210
18712
|
overviewQueued = false;
|
|
18713
|
+
// The agents that can start sessions here.
|
|
18714
|
+
startable = [];
|
|
18211
18715
|
snapshots = /* @__PURE__ */ new Set();
|
|
18212
18716
|
nativeSnapshots = /* @__PURE__ */ new Map();
|
|
18213
18717
|
queueReceipts = /* @__PURE__ */ new Map();
|
|
@@ -18221,6 +18725,7 @@ var Connector = class {
|
|
|
18221
18725
|
this.nativeSnapshots.set(thread.id, structuredClone(this.threadState(project, thread)));
|
|
18222
18726
|
}
|
|
18223
18727
|
this.refreshQueues();
|
|
18728
|
+
void this.findAgents();
|
|
18224
18729
|
this.stopWatching = this.discovery.watch(this.rescan);
|
|
18225
18730
|
this.releases.push(this.harnesses.watch(this.rescan));
|
|
18226
18731
|
this.releases.push(
|
|
@@ -18259,15 +18764,15 @@ var Connector = class {
|
|
|
18259
18764
|
for (const project of this.discovery.projects.values())
|
|
18260
18765
|
for (const thread of project.threads) {
|
|
18261
18766
|
const native = this.discovery.locations.get(thread.id)?.live;
|
|
18262
|
-
const
|
|
18263
|
-
if (!
|
|
18767
|
+
const approval3 = native?.approvals[0];
|
|
18768
|
+
if (!approval3) continue;
|
|
18264
18769
|
await this.thread(pair, project, thread);
|
|
18265
18770
|
await this.send(pair, {
|
|
18266
18771
|
type: "attention",
|
|
18267
18772
|
projectId: project.id,
|
|
18268
18773
|
threadId: thread.id,
|
|
18269
|
-
title:
|
|
18270
|
-
body:
|
|
18774
|
+
title: approval3.title,
|
|
18775
|
+
body: approval3.detail,
|
|
18271
18776
|
reason: "approval"
|
|
18272
18777
|
});
|
|
18273
18778
|
}
|
|
@@ -18435,6 +18940,10 @@ var Connector = class {
|
|
|
18435
18940
|
}
|
|
18436
18941
|
return {
|
|
18437
18942
|
type: "projects",
|
|
18943
|
+
version,
|
|
18944
|
+
screen: this.discovery.capabilities.screen,
|
|
18945
|
+
agents: this.startable,
|
|
18946
|
+
folders: this.discovery.folders(),
|
|
18438
18947
|
projects: [...this.listedProjects.values()],
|
|
18439
18948
|
sessions: [
|
|
18440
18949
|
...sessions.filter(busy),
|
|
@@ -18442,6 +18951,11 @@ var Connector = class {
|
|
|
18442
18951
|
]
|
|
18443
18952
|
};
|
|
18444
18953
|
}
|
|
18954
|
+
// Agents change only when someone installs or removes one, so a refresh asks again.
|
|
18955
|
+
async findAgents() {
|
|
18956
|
+
this.startable = await this.harnesses.agents();
|
|
18957
|
+
this.refreshOverview();
|
|
18958
|
+
}
|
|
18445
18959
|
async listProjects(pair) {
|
|
18446
18960
|
const overview = this.overview();
|
|
18447
18961
|
const text3 = JSON.stringify(overview);
|
|
@@ -18507,9 +19021,9 @@ var Connector = class {
|
|
|
18507
19021
|
if (JSON.stringify(prior) === JSON.stringify(snapshot)) continue;
|
|
18508
19022
|
this.nativeSnapshots.set(thread.id, structuredClone(snapshot));
|
|
18509
19023
|
for (const pair of this.activePairs()) await this.thread(pair, project, thread);
|
|
18510
|
-
for (const
|
|
18511
|
-
if (!prior?.approvals.some((item) => item.id ===
|
|
18512
|
-
await this.attention(project.id, thread.id,
|
|
19024
|
+
for (const approval3 of session.approvals)
|
|
19025
|
+
if (!prior?.approvals.some((item) => item.id === approval3.id))
|
|
19026
|
+
await this.attention(project.id, thread.id, approval3.title, approval3.detail, "approval");
|
|
18513
19027
|
if (prior?.thread.status === "working" && thread.status === "idle")
|
|
18514
19028
|
await this.attention(
|
|
18515
19029
|
project.id,
|
|
@@ -18591,18 +19105,21 @@ var Connector = class {
|
|
|
18591
19105
|
async execute(pair, content, envelope) {
|
|
18592
19106
|
let queue = void 0;
|
|
18593
19107
|
try {
|
|
18594
|
-
if (["screen.start", "approval.respond", "thread.stop"].includes(
|
|
19108
|
+
if (["screen.start", "approval.respond", "thread.stop", "session.start"].includes(
|
|
19109
|
+
content.action.kind
|
|
19110
|
+
) && Math.abs(Date.now() - Date.parse(envelope.sentAt)) > EPHEMERAL_TTL_MS)
|
|
18595
19111
|
throw new Error("Command expired. Try again while this computer is online.");
|
|
18596
|
-
|
|
19112
|
+
const outcome = await this.command(pair, content);
|
|
18597
19113
|
delete this.config.unconfirmed[content.requestId];
|
|
18598
|
-
if (
|
|
19114
|
+
if (outcome && "state" in outcome) {
|
|
19115
|
+
queue = outcome;
|
|
18599
19116
|
await this.queueResult(pair, content.requestId, content.projectId, queue);
|
|
18600
19117
|
this.queueReceipts.set(content.requestId, JSON.stringify(queue));
|
|
18601
19118
|
return;
|
|
18602
19119
|
}
|
|
18603
19120
|
await this.send(
|
|
18604
19121
|
pair,
|
|
18605
|
-
{ type: "result", requestId: content.requestId, ok: true },
|
|
19122
|
+
{ type: "result", requestId: content.requestId, ok: true, ...outcome ? { started: outcome } : {} },
|
|
18606
19123
|
content.projectId
|
|
18607
19124
|
);
|
|
18608
19125
|
} catch (error) {
|
|
@@ -18637,7 +19154,7 @@ var Connector = class {
|
|
|
18637
19154
|
}
|
|
18638
19155
|
}
|
|
18639
19156
|
attach(socket) {
|
|
18640
|
-
const sessions = bridgeAcp(socket, (session,
|
|
19157
|
+
const sessions = bridgeAcp(socket, (session, approval3) => {
|
|
18641
19158
|
this.agents.set(session.id, session);
|
|
18642
19159
|
const { project, thread } = this.adopt(session);
|
|
18643
19160
|
if (!this.snapshots.has(session.id)) {
|
|
@@ -18652,15 +19169,15 @@ var Connector = class {
|
|
|
18652
19169
|
}
|
|
18653
19170
|
}).catch((error) => this.report(error));
|
|
18654
19171
|
}
|
|
18655
|
-
if (
|
|
19172
|
+
if (approval3)
|
|
18656
19173
|
this.work = this.work.then(async () => {
|
|
18657
|
-
if (this.agents.get(session.id) !== session || ![...session.approvals.values()].includes(
|
|
19174
|
+
if (this.agents.get(session.id) !== session || ![...session.approvals.values()].includes(approval3))
|
|
18658
19175
|
return;
|
|
18659
19176
|
await this.attention(
|
|
18660
19177
|
project.id,
|
|
18661
19178
|
thread.id,
|
|
18662
|
-
|
|
18663
|
-
|
|
19179
|
+
approval3.title,
|
|
19180
|
+
approval3.detail,
|
|
18664
19181
|
"approval"
|
|
18665
19182
|
);
|
|
18666
19183
|
}).catch((error) => this.report(error));
|
|
@@ -18729,9 +19246,12 @@ var Connector = class {
|
|
|
18729
19246
|
}
|
|
18730
19247
|
async command(pair, command2) {
|
|
18731
19248
|
if (command2.action.kind === "projects.refresh") {
|
|
19249
|
+
await this.findAgents();
|
|
18732
19250
|
await this.changed();
|
|
18733
19251
|
return this.publishProjects(pair);
|
|
18734
19252
|
}
|
|
19253
|
+
if (command2.action.kind === "session.start")
|
|
19254
|
+
return this.startSession(command2.projectId, command2.action);
|
|
18735
19255
|
const project = this.discovery.projects.get(command2.projectId);
|
|
18736
19256
|
if (!project) throw new Error("Project is no longer open on this computer");
|
|
18737
19257
|
const thread = project.threads.find((item) => item.id === command2.threadId);
|
|
@@ -18788,15 +19308,15 @@ var Connector = class {
|
|
|
18788
19308
|
const action = command2.action;
|
|
18789
19309
|
const native = this.discovery.locations.get(thread.id)?.live;
|
|
18790
19310
|
if (native?.approve) {
|
|
18791
|
-
const
|
|
18792
|
-
if (!
|
|
19311
|
+
const approval4 = native.approvals.find((item) => item.id === action.approvalId);
|
|
19312
|
+
if (!approval4?.choices.some((choice) => choice.id === action.choice))
|
|
18793
19313
|
throw new Error("Approval is no longer available");
|
|
18794
|
-
return native.approve(command2.requestId,
|
|
19314
|
+
return native.approve(command2.requestId, approval4.id, action.choice);
|
|
18795
19315
|
}
|
|
18796
19316
|
const agent = this.attached(thread);
|
|
18797
19317
|
if (!agent) throw new Error("Approval requires an attached ACP session. Use Screen.");
|
|
18798
|
-
const [request2,
|
|
18799
|
-
if (!
|
|
19318
|
+
const [request2, approval3] = [...agent.approvals].find(([, item]) => item.id === action.approvalId) ?? [];
|
|
19319
|
+
if (!approval3?.choices.some((choice) => choice.id === action.choice))
|
|
18800
19320
|
throw new Error("Approval is no longer available");
|
|
18801
19321
|
return commandAcp(agent, command2.requestId, {
|
|
18802
19322
|
kind: "approval",
|
|
@@ -18812,6 +19332,37 @@ var Connector = class {
|
|
|
18812
19332
|
return;
|
|
18813
19333
|
}
|
|
18814
19334
|
}
|
|
19335
|
+
// Starts an agent in a project, or in a new folder inside one this computer offers. Commands run
|
|
19336
|
+
// in the work queue, so this refreshes directly rather than queueing a change behind itself.
|
|
19337
|
+
async startSession(projectId, { harness, text: text3, folder }) {
|
|
19338
|
+
if (!this.startable.includes(harness))
|
|
19339
|
+
throw new Error(`${HARNESSES[harness]} doesn't run on this computer`);
|
|
19340
|
+
let cwd;
|
|
19341
|
+
if (folder) {
|
|
19342
|
+
const name = folder.name.trim();
|
|
19343
|
+
if (!this.discovery.folders().includes(folder.parent))
|
|
19344
|
+
throw new Error("That folder is no longer offered for new projects");
|
|
19345
|
+
if (!name || name === "." || name === ".." || /[\\/:*?"<>|\x00-\x1f]/.test(name))
|
|
19346
|
+
throw new Error("Name the folder without slashes or special characters");
|
|
19347
|
+
cwd = join9(folder.parent, name);
|
|
19348
|
+
await mkdir(cwd).catch((error) => {
|
|
19349
|
+
throw new Error(error.code === "EEXIST" ? `${name} already exists` : `Couldn't create ${name}`);
|
|
19350
|
+
});
|
|
19351
|
+
} else {
|
|
19352
|
+
const project = this.discovery.projects.get(projectId);
|
|
19353
|
+
if (!project) throw new Error("Project is no longer open on this computer");
|
|
19354
|
+
if (project.remote)
|
|
19355
|
+
throw new Error(`Install -up on ${project.remote.host} to start sessions there`);
|
|
19356
|
+
cwd = project.path;
|
|
19357
|
+
}
|
|
19358
|
+
const session = await this.harnesses.start(harness, cwd, text3);
|
|
19359
|
+
await this.changed();
|
|
19360
|
+
for (const project of this.discovery.projects.values())
|
|
19361
|
+
for (const thread of project.threads)
|
|
19362
|
+
if (thread.agent?.harness === harness && thread.agent.sessionId === session)
|
|
19363
|
+
return { projectId: project.id, threadId: thread.id };
|
|
19364
|
+
throw new Error(`${HARNESSES[harness]} started, but this computer hasn't listed it yet`);
|
|
19365
|
+
}
|
|
18815
19366
|
report(error) {
|
|
18816
19367
|
process.stderr.write(`${error instanceof Error ? error.message : "Dashup operation failed"}
|
|
18817
19368
|
`);
|
|
@@ -18821,18 +19372,18 @@ var Connector = class {
|
|
|
18821
19372
|
// src/install.ts
|
|
18822
19373
|
import { existsSync as existsSync6 } from "node:fs";
|
|
18823
19374
|
import { homedir as homedir6, userInfo } from "node:os";
|
|
18824
|
-
import { promisify as
|
|
19375
|
+
import { promisify as promisify7 } from "node:util";
|
|
18825
19376
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
18826
|
-
import { execFile as
|
|
18827
|
-
import { cp, mkdir, rm, writeFile } from "node:fs/promises";
|
|
18828
|
-
import { dirname as
|
|
18829
|
-
var run4 =
|
|
19377
|
+
import { execFile as execFile7, spawn as spawn6 } from "node:child_process";
|
|
19378
|
+
import { cp, mkdir as mkdir2, rm, writeFile } from "node:fs/promises";
|
|
19379
|
+
import { dirname as dirname6, join as join10, relative, sep } from "node:path";
|
|
19380
|
+
var run4 = promisify7(execFile7);
|
|
18830
19381
|
var xml = (value2) => value2.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
18831
19382
|
var NODE_FLAGS = ["--disable-warning=ExperimentalWarning"];
|
|
18832
|
-
var serviceFile = () => process.platform === "darwin" ?
|
|
18833
|
-
process.env.APPDATA ??
|
|
19383
|
+
var serviceFile = () => process.platform === "darwin" ? join10(homedir6(), "Library/LaunchAgents/com.dashup.connector.plist") : process.platform === "win32" ? join10(
|
|
19384
|
+
process.env.APPDATA ?? join10(homedir6(), "AppData/Roaming"),
|
|
18834
19385
|
"Microsoft/Windows/Start Menu/Programs/Startup/Dashup.vbs"
|
|
18835
|
-
) :
|
|
19386
|
+
) : join10(homedir6(), ".config/systemd/user/dashup.service");
|
|
18836
19387
|
var startupInstalled = () => existsSync6(serviceFile());
|
|
18837
19388
|
function service(platform, node, entry, config2) {
|
|
18838
19389
|
const command2 = [node, ...NODE_FLAGS, entry];
|
|
@@ -18868,17 +19419,17 @@ async function durableEntry() {
|
|
|
18868
19419
|
);
|
|
18869
19420
|
if (!["_npx", "dlx"].some((cache2) => entry.includes(`${sep}${cache2}${sep}`))) return entry;
|
|
18870
19421
|
const root = entry.slice(0, entry.indexOf(`${sep}node_modules${sep}`));
|
|
18871
|
-
const app =
|
|
19422
|
+
const app = join10(dirname6(configPath()), "app");
|
|
18872
19423
|
await rm(app, { recursive: true, force: true });
|
|
18873
|
-
await cp(
|
|
19424
|
+
await cp(join10(root, "node_modules"), join10(app, "node_modules"), {
|
|
18874
19425
|
recursive: true,
|
|
18875
19426
|
verbatimSymlinks: true
|
|
18876
19427
|
});
|
|
18877
|
-
return
|
|
19428
|
+
return join10(app, relative(root, entry));
|
|
18878
19429
|
}
|
|
18879
19430
|
async function installStartup() {
|
|
18880
19431
|
const path = serviceFile();
|
|
18881
|
-
await
|
|
19432
|
+
await mkdir2(dirname6(path), { recursive: true });
|
|
18882
19433
|
await writeFile(
|
|
18883
19434
|
path,
|
|
18884
19435
|
service(process.platform, process.execPath, await durableEntry(), configPath())
|
|
@@ -18888,7 +19439,7 @@ async function installStartup() {
|
|
|
18888
19439
|
});
|
|
18889
19440
|
await run4("launchctl", ["load", path]);
|
|
18890
19441
|
} else if (process.platform === "win32") {
|
|
18891
|
-
|
|
19442
|
+
spawn6("wscript.exe", [path], { detached: true, stdio: "ignore" }).unref();
|
|
18892
19443
|
} else {
|
|
18893
19444
|
await run4("systemctl", ["--user", "daemon-reload"]);
|
|
18894
19445
|
await run4("systemctl", ["--user", "enable", "dashup.service"]);
|
|
@@ -18908,7 +19459,7 @@ async function uninstallStartup() {
|
|
|
18908
19459
|
await run4("systemctl", ["--user", "disable", "dashup.service"]).catch(() => {
|
|
18909
19460
|
});
|
|
18910
19461
|
await rm(path, { force: true });
|
|
18911
|
-
await rm(
|
|
19462
|
+
await rm(join10(dirname6(configPath()), "app"), { recursive: true, force: true });
|
|
18912
19463
|
}
|
|
18913
19464
|
|
|
18914
19465
|
// src/index.ts
|