@dashup/connector 0.1.13 → 0.1.15
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 +3 -1
- package/dist/index.js +310 -162
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ dashup alert --project PROJECT_ID --title "Dashup is connected"
|
|
|
22
22
|
|
|
23
23
|
The connector automatically reads existing Claude Code, Codex, and OpenCode conversations alongside Zed workspaces and VS Code chat history. It honors the harness's existing data directory, authentication, and native session IDs. Native stores are opened read-only; discovery never launches an agent, creates or resumes a conversation, or changes editor settings. Only a session the phone starts runs an agent (see below). Opening the same supported harness session through Zed and its native history uses one Dashup thread when their actual session identities match. Matching directory names alone never merges local and SSH projects.
|
|
24
24
|
|
|
25
|
-
A thread's title is the agent's own (a rename, Codex's stored title, Claude's title), else the first message the person typed: Codex's injected AGENTS.md and environment messages and Claude Code's slash commands are skipped,
|
|
25
|
+
A thread's title is the agent's own (a rename, Codex's stored title, Claude's title), else the first message the person typed: Codex's injected AGENTS.md and environment messages and Claude Code's slash commands are skipped, and a Codex subagent is named for its task. In the conversation, Codex's injected messages and Claude Code's own meta rows stay out, and a Claude Code slash command reads as typed, followed by its output. A thread's time is its last conversation message in the agent's own history; editor bookkeeping such as Zed's sidebar restamps never moves it. Subagent sessions (OpenCode children, Codex spawned agents) carry their parent thread. Tool calls are joined with their results into one step with status, duration and, for file edits, a unified diff. Saved history has unknown live status; the phone can answer it by resuming it (see Starting sessions from the phone). File changes publish project metadata; only an approval, a question, an error or a finished turn sends an alert. A workspace becomes a project once it has a conversation. After every change the connector publishes its overview: every project, and the conversations Home shows (anything that needs the person or is working, then the newest 20). The phone renders Home from the overview alone and decodes a project's full snapshot only when a page opens it. Only a scan that read every source retires a project it published earlier and left off. Each phone read request reads a thread's native history once and publishes a snapshot of separately encrypted message fragments to the database. The phone reads bounded latest and earlier pages; realtime thread updates carry only summary and approval metadata. Native history remains the source, and an incomplete upload never replaces the previous published snapshot. Native live interfaces provide actual status and pending choices. Tool integrations can also queue explicit encrypted alerts with `alert`.
|
|
26
26
|
|
|
27
27
|
Project paths, SSH details, thread content, approvals, and alerts are encrypted separately for every paired phone. The encrypted outbox survives a connector restart. Parcae handles realtime delivery and reconnect; replay counters are saved before commands execute. Revoking a pairing discards its connector key and queued content.
|
|
28
28
|
|
|
@@ -47,6 +47,8 @@ Only an explicit Queue reply starts a temporary official app-server helper to in
|
|
|
47
47
|
|
|
48
48
|
Delivery requires that exact typed client-message ID in the original thread's native history database. A response receipt requires a later native assistant item in the same turn; it does not mean the agent finished. Receipts remain encrypted through the ordinary Dashup relay. History is read on request; the phone asks again while a thread is open and its session changes. Native approvals owned by the private-stdio process remain unsupported and require its original UI. Queue text never substitutes for a native approval.
|
|
49
49
|
|
|
50
|
+
A running Claude Code session shows the model its newest reply ran on and, where that model takes them, fast mode and ultracode, as its transcript records them. The choices and their names are the models the session's own Claude Code binary offers hosts in its initialize answer, or `claude`'s where the system does not name the binary. The phone changes them through the plugin with Claude Code's own `/model`, `/fast` and `/effort ultracode` commands, and in a session the phone started with control requests at the start of each turn. Claude Code records a change when the next turn starts, so it shows from the next reply. A change expires with other commands after 60 seconds.
|
|
51
|
+
|
|
50
52
|
Stop interrupts the running turn through each live interface: OpenCode's session abort, Codex `turn/interrupt`, the AHP client's turn cancellation, ACP `session/cancel`, and the Claude Code plugin's turn abort. It refuses when nothing is running and expires with other commands after 60 seconds, so a late stop cannot end a newer turn.
|
|
51
53
|
|
|
52
54
|
Live discovery is bounded to 100 sessions per endpoint. OpenCode supplies the latest 200 messages; the VS Code host supplies its current chat snapshot. Paged history prefers the native saved store when available and otherwise uses the existing live snapshot. Approvals whose actual commands or file contents cannot be previewed are approved on the computer; rejection remains available when the native protocol supports it.
|
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
|
}
|
|
@@ -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.15";
|
|
4052
4052
|
|
|
4053
4053
|
// src/pair.ts
|
|
4054
4054
|
var Invitation = z.object({
|
|
@@ -4459,6 +4459,13 @@ function step(id, name, input, result, started) {
|
|
|
4459
4459
|
}
|
|
4460
4460
|
var claudeHome = () => process.env.CLAUDE_CONFIG_DIR ?? join2(homedir2(), ".claude");
|
|
4461
4461
|
var claudeMessage = (row) => (row.type === "user" || row.type === "assistant") && !row.isSidechain;
|
|
4462
|
+
var OUTPUT_TAGS = /<\/?local-command-std\w+>/g;
|
|
4463
|
+
var claudeOutput = (row) => row.type === "system" && row.subtype === "local_command" ? text(row.content).replace(OUTPUT_TAGS, "").trim() : void 0;
|
|
4464
|
+
function claudeCommand(value2) {
|
|
4465
|
+
const name = /<command-name>([^<]*)<\/command-name>/.exec(value2)?.[1];
|
|
4466
|
+
const args2 = /<command-args>([^<]*)<\/command-args>/.exec(value2)?.[1] ?? "";
|
|
4467
|
+
return name === void 0 ? void 0 : `${name} ${args2}`.trim();
|
|
4468
|
+
}
|
|
4462
4469
|
function claudeChange(change2) {
|
|
4463
4470
|
if (typeof change2?.filePath !== "string") return void 0;
|
|
4464
4471
|
const hunks = list(change2.structuredPatch);
|
|
@@ -4480,7 +4487,8 @@ function claudeMessages(path) {
|
|
|
4480
4487
|
for (const row of rows) {
|
|
4481
4488
|
if (typeof row.uuid === "string" && !row.isSidechain) {
|
|
4482
4489
|
chain.set(row.uuid, row);
|
|
4483
|
-
if (row.type === "user" || row.type === "assistant")
|
|
4490
|
+
if (row.type === "user" || row.type === "assistant" || claudeOutput(row) !== void 0)
|
|
4491
|
+
leaf = row.uuid;
|
|
4484
4492
|
}
|
|
4485
4493
|
if (typeof row.leafUuid === "string" && chain.has(row.leafUuid)) leaf = row.leafUuid;
|
|
4486
4494
|
}
|
|
@@ -4502,9 +4510,13 @@ function claudeConversation(conversation, rows) {
|
|
|
4502
4510
|
for (const part of row.type === "user" ? list(row.message?.content) : [])
|
|
4503
4511
|
if (part?.type === "tool_result") results.set(part.tool_use_id, { part, row });
|
|
4504
4512
|
return conversation.flatMap((row, index) => {
|
|
4505
|
-
if (row.type !== "user" && row.type !== "assistant") return [];
|
|
4506
4513
|
const id = String(row.uuid ?? row.message?.id ?? index);
|
|
4507
4514
|
const content = row.message?.content;
|
|
4515
|
+
const output = claudeOutput(row);
|
|
4516
|
+
const command2 = row.type === "user" ? claudeCommand(text(content)) : void 0;
|
|
4517
|
+
if (output !== void 0) return prose(id, "system", output);
|
|
4518
|
+
if (row.type !== "user" && row.type !== "assistant" || row.isMeta) return [];
|
|
4519
|
+
if (command2 !== void 0) return [{ id, role: "user", text: command2 }];
|
|
4508
4520
|
return [
|
|
4509
4521
|
...prose(id, row.type, content),
|
|
4510
4522
|
...list(content).flatMap((part, position) => {
|
|
@@ -4530,6 +4542,23 @@ function claudeConversation(conversation, rows) {
|
|
|
4530
4542
|
];
|
|
4531
4543
|
});
|
|
4532
4544
|
}
|
|
4545
|
+
function claudeSettings(path) {
|
|
4546
|
+
const rows = journal(path, true);
|
|
4547
|
+
const settings = {};
|
|
4548
|
+
for (let index = rows.length - 1; index >= 0; index--) {
|
|
4549
|
+
const row = rows[index];
|
|
4550
|
+
const message = object(row.message);
|
|
4551
|
+
const marker = row.type === "attachment" ? object(row.attachment)?.type : void 0;
|
|
4552
|
+
if (settings.model === void 0 && row.type === "assistant" && !row.isSidechain && typeof message?.model === "string" && message.model !== "<synthetic>") {
|
|
4553
|
+
settings.model = message.model;
|
|
4554
|
+
settings.fast = object(message.usage)?.speed === "fast";
|
|
4555
|
+
}
|
|
4556
|
+
if (settings.ultracode === void 0 && (marker === "ultra_effort_enter" || marker === "ultra_effort_exit"))
|
|
4557
|
+
settings.ultracode = marker === "ultra_effort_enter";
|
|
4558
|
+
if (settings.model !== void 0 && settings.ultracode !== void 0) break;
|
|
4559
|
+
}
|
|
4560
|
+
return { ultracode: false, ...settings };
|
|
4561
|
+
}
|
|
4533
4562
|
function claude(root, scan) {
|
|
4534
4563
|
for (const path of files(join2(root, "projects"), scan, 1).filter((file) => file.endsWith(".jsonl"))) {
|
|
4535
4564
|
try {
|
|
@@ -4559,7 +4588,8 @@ function claude(root, scan) {
|
|
|
4559
4588
|
harness: "claude",
|
|
4560
4589
|
...session,
|
|
4561
4590
|
files: [path],
|
|
4562
|
-
read: () => claudeMessages(path)
|
|
4591
|
+
read: () => claudeMessages(path),
|
|
4592
|
+
settings: () => claudeSettings(path)
|
|
4563
4593
|
});
|
|
4564
4594
|
} catch {
|
|
4565
4595
|
scan.warnings.push(`Cannot read Claude Code session: ${path}`);
|
|
@@ -4748,8 +4778,8 @@ function databaseMessages(path, session) {
|
|
|
4748
4778
|
}
|
|
4749
4779
|
}
|
|
4750
4780
|
function codexSubagent(row) {
|
|
4751
|
-
const
|
|
4752
|
-
const task = typeof
|
|
4781
|
+
const spawn7 = object(object(parse(String(row.source ?? "")))?.subagent)?.thread_spawn;
|
|
4782
|
+
const task = typeof spawn7?.agent_path === "string" ? spawn7.agent_path.split("/").pop() : "";
|
|
4753
4783
|
return [row.agent_nickname, task?.replace(/_/g, " ")].filter(Boolean).join(" \xB7 ");
|
|
4754
4784
|
}
|
|
4755
4785
|
var codexState;
|
|
@@ -5069,13 +5099,15 @@ var Discovery = class {
|
|
|
5069
5099
|
title: session.title ?? found.thread.title,
|
|
5070
5100
|
status: session.status,
|
|
5071
5101
|
updatedAt: location2.history?.updatedAt ?? session.updatedAt,
|
|
5102
|
+
options: session.options?.(location2.history?.settings?.() ?? {}),
|
|
5072
5103
|
capabilities: {
|
|
5073
5104
|
...found.thread.capabilities,
|
|
5074
5105
|
read: true,
|
|
5075
5106
|
send: Boolean(session.send),
|
|
5076
5107
|
stop: Boolean(session.stop),
|
|
5077
5108
|
now: Boolean(session.now),
|
|
5078
|
-
approve: Boolean(session.approve)
|
|
5109
|
+
approve: Boolean(session.approve),
|
|
5110
|
+
configure: Boolean(session.configure)
|
|
5079
5111
|
}
|
|
5080
5112
|
});
|
|
5081
5113
|
return found;
|
|
@@ -16780,15 +16812,224 @@ var Codex = class {
|
|
|
16780
16812
|
// src/harness/headless.ts
|
|
16781
16813
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
16782
16814
|
import { readFileSync as readFileSync3 } from "node:fs";
|
|
16783
|
-
import { createInterface as
|
|
16815
|
+
import { createInterface as createInterface3 } from "node:readline";
|
|
16816
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
16817
|
+
|
|
16818
|
+
// src/harness/claude.ts
|
|
16819
|
+
import { homedir as homedir5 } from "node:os";
|
|
16820
|
+
import { dirname as dirname4, join as join5 } from "node:path";
|
|
16784
16821
|
import { spawn as spawn2 } from "node:child_process";
|
|
16822
|
+
import { createInterface as createInterface2 } from "node:readline";
|
|
16823
|
+
import { watch as watch2 } from "node:fs";
|
|
16824
|
+
import { mkdir, readdir as readdir2, readFile as readFile2, readlink, rm } from "node:fs/promises";
|
|
16825
|
+
import { createServer as createServer2 } from "node:http";
|
|
16826
|
+
var MODELS_MS = 3e4;
|
|
16827
|
+
var COMMANDS = {
|
|
16828
|
+
model: (value2) => `/model ${value2}`,
|
|
16829
|
+
fast: (value2) => `/fast ${value2 ? "on" : "off"}`,
|
|
16830
|
+
ultracode: (value2) => `/effort ultracode ${value2 ? "on" : "off"}`
|
|
16831
|
+
};
|
|
16832
|
+
var offered = /* @__PURE__ */ new Map();
|
|
16833
|
+
var asking = /* @__PURE__ */ new Set();
|
|
16834
|
+
var forgetModels = () => offered.clear();
|
|
16835
|
+
function claudeModels(changed, command2 = "claude", version2 = "") {
|
|
16836
|
+
const key = `${command2}\0${version2}`;
|
|
16837
|
+
if (!offered.has(key) && !asking.has(key)) {
|
|
16838
|
+
asking.add(key);
|
|
16839
|
+
void listModels(command2).then((models) => {
|
|
16840
|
+
asking.delete(key);
|
|
16841
|
+
if (!models.length) return;
|
|
16842
|
+
offered.set(key, models);
|
|
16843
|
+
changed();
|
|
16844
|
+
});
|
|
16845
|
+
}
|
|
16846
|
+
return offered.get(key) ?? [];
|
|
16847
|
+
}
|
|
16848
|
+
function listModels(command2) {
|
|
16849
|
+
return new Promise((resolve3) => {
|
|
16850
|
+
const child = spawn2(
|
|
16851
|
+
command2,
|
|
16852
|
+
["-p", "--input-format", "stream-json", "--output-format", "stream-json", "--verbose"],
|
|
16853
|
+
{ cwd: homedir5(), stdio: "pipe", shell: process.platform === "win32" }
|
|
16854
|
+
);
|
|
16855
|
+
const timer = setTimeout(() => child.kill(), MODELS_MS);
|
|
16856
|
+
child.stdin.on("error", () => {
|
|
16857
|
+
});
|
|
16858
|
+
child.once("error", () => resolve3([]));
|
|
16859
|
+
child.once("exit", () => {
|
|
16860
|
+
clearTimeout(timer);
|
|
16861
|
+
resolve3([]);
|
|
16862
|
+
});
|
|
16863
|
+
createInterface2({ input: child.stdout }).on("line", (line) => {
|
|
16864
|
+
try {
|
|
16865
|
+
const message = JSON.parse(line);
|
|
16866
|
+
if (message?.type !== "control_response" || message.response?.request_id !== "models")
|
|
16867
|
+
return;
|
|
16868
|
+
const models = message.response.response?.models;
|
|
16869
|
+
resolve3(
|
|
16870
|
+
(Array.isArray(models) ? models : []).filter(
|
|
16871
|
+
(model) => typeof model?.value === "string" && typeof model.displayName === "string"
|
|
16872
|
+
)
|
|
16873
|
+
);
|
|
16874
|
+
child.stdin.end();
|
|
16875
|
+
} catch {
|
|
16876
|
+
}
|
|
16877
|
+
});
|
|
16878
|
+
child.stdin.write(
|
|
16879
|
+
`${JSON.stringify({ type: "control_request", request_id: "models", request: { subtype: "initialize" } })}
|
|
16880
|
+
`
|
|
16881
|
+
);
|
|
16882
|
+
});
|
|
16883
|
+
}
|
|
16884
|
+
function claudeOptions(models, recorded) {
|
|
16885
|
+
const ran = typeof recorded.model === "string" ? recorded.model : "";
|
|
16886
|
+
const current = models.find((model) => model.value !== "default" && model.resolvedModel === ran) ?? models.find((model) => model.value === ran);
|
|
16887
|
+
const choices = models.map((model) => ({ value: model.value, name: model.displayName }));
|
|
16888
|
+
if (ran && !current) choices.push({ value: ran, name: ran });
|
|
16889
|
+
if (!choices.length) return [];
|
|
16890
|
+
return [
|
|
16891
|
+
{ id: "model", name: "Model", value: current?.value ?? ran, choices },
|
|
16892
|
+
...current?.supportsFastMode ? [{ id: "fast", name: "Fast", value: recorded.fast === true }] : [],
|
|
16893
|
+
...current?.supportedEffortLevels?.includes("xhigh") ? [{ id: "ultracode", name: "Ultracode", value: recorded.ultracode === true }] : []
|
|
16894
|
+
];
|
|
16895
|
+
}
|
|
16896
|
+
var MAX_SESSIONS2 = 100;
|
|
16897
|
+
var MAX_REQUESTS = 1e3;
|
|
16898
|
+
var SESSION = /^\/sessions\/([\w-]{1,100})$/;
|
|
16899
|
+
var STATUS = {
|
|
16900
|
+
busy: "working",
|
|
16901
|
+
idle: "idle",
|
|
16902
|
+
shell: "idle",
|
|
16903
|
+
waiting: "attention"
|
|
16904
|
+
};
|
|
16905
|
+
async function registrations() {
|
|
16906
|
+
if (process.env.DASHUP_LIVE_DISCOVERY === "0") return [];
|
|
16907
|
+
const directory2 = join5(claudeHome(), "sessions");
|
|
16908
|
+
const names = await readdir2(directory2).catch(() => []);
|
|
16909
|
+
const found = await Promise.all(
|
|
16910
|
+
names.filter((name) => /^\d+\.json$/.test(name)).slice(0, MAX_SESSIONS2).map(async (name) => {
|
|
16911
|
+
try {
|
|
16912
|
+
const value2 = JSON.parse(await readFile2(join5(directory2, name), "utf8"));
|
|
16913
|
+
if (typeof value2.sessionId !== "string" || typeof value2.cwd !== "string") return [];
|
|
16914
|
+
process.kill(value2.pid, 0);
|
|
16915
|
+
const binary2 = await readlink(`/proc/${value2.pid}/exe`).catch(() => "");
|
|
16916
|
+
return [{ ...value2, command: /[\\/]claude(?:\.exe)?$/.test(binary2) ? binary2 : "claude" }];
|
|
16917
|
+
} catch {
|
|
16918
|
+
return [];
|
|
16919
|
+
}
|
|
16920
|
+
})
|
|
16921
|
+
);
|
|
16922
|
+
return found.flat();
|
|
16923
|
+
}
|
|
16924
|
+
var Claude = class {
|
|
16925
|
+
constructor(changed, socket = join5(dirname4(configPath()), "claude.sock")) {
|
|
16926
|
+
this.changed = changed;
|
|
16927
|
+
this.socket = socket;
|
|
16928
|
+
}
|
|
16929
|
+
changed;
|
|
16930
|
+
socket;
|
|
16931
|
+
watcher;
|
|
16932
|
+
notify;
|
|
16933
|
+
server;
|
|
16934
|
+
streams = /* @__PURE__ */ new Map();
|
|
16935
|
+
requests = /* @__PURE__ */ new Map();
|
|
16936
|
+
async refresh() {
|
|
16937
|
+
this.watch();
|
|
16938
|
+
return (await registrations()).map((session) => {
|
|
16939
|
+
const plugin = this.streams.has(session.sessionId);
|
|
16940
|
+
const models = claudeModels(this.changed, session.command, session.version);
|
|
16941
|
+
return {
|
|
16942
|
+
id: session.sessionId,
|
|
16943
|
+
harness: "claude",
|
|
16944
|
+
cwd: session.cwd,
|
|
16945
|
+
updatedAt: new Date(session.updatedAt ?? 0).toISOString(),
|
|
16946
|
+
status: STATUS[session.status ?? ""] ?? "unknown",
|
|
16947
|
+
messages: [],
|
|
16948
|
+
approvals: [],
|
|
16949
|
+
send: plugin ? (requestId, text3, now) => this.once(requestId, () => this.deliver(session.sessionId, now ? { text: text3, now } : { text: text3 })) : void 0,
|
|
16950
|
+
stop: plugin ? (requestId) => this.once(requestId, () => this.deliver(session.sessionId, { now: true })) : void 0,
|
|
16951
|
+
now: plugin,
|
|
16952
|
+
options: (recorded) => claudeOptions(models, recorded),
|
|
16953
|
+
configure: plugin ? (requestId, option2, value2) => this.once(requestId, () => this.deliver(session.sessionId, { text: COMMANDS[option2](value2) })) : void 0
|
|
16954
|
+
};
|
|
16955
|
+
});
|
|
16956
|
+
}
|
|
16957
|
+
// Only the connector serves the plugin; one-off commands such as `dashup doctor` read alone.
|
|
16958
|
+
async listen() {
|
|
16959
|
+
if (this.server || process.env.DASHUP_LIVE_DISCOVERY === "0") return;
|
|
16960
|
+
const server = this.server = createServer2((request2, response) => {
|
|
16961
|
+
const id = request2.method === "GET" ? SESSION.exec(request2.url ?? "")?.[1] : void 0;
|
|
16962
|
+
if (!id) return void response.writeHead(404).end();
|
|
16963
|
+
this.streams.get(id)?.end();
|
|
16964
|
+
this.streams.set(id, response);
|
|
16965
|
+
response.once("close", () => {
|
|
16966
|
+
if (this.streams.get(id) === response) this.streams.delete(id);
|
|
16967
|
+
this.changed();
|
|
16968
|
+
});
|
|
16969
|
+
response.writeHead(200, { "Content-Type": "application/x-ndjson" }).flushHeaders();
|
|
16970
|
+
this.changed();
|
|
16971
|
+
});
|
|
16972
|
+
await mkdir(dirname4(this.socket), { recursive: true, mode: 448 });
|
|
16973
|
+
await rm(this.socket, { force: true });
|
|
16974
|
+
if (this.server !== server) return;
|
|
16975
|
+
await new Promise((resolve3, reject) => {
|
|
16976
|
+
server.once("error", reject);
|
|
16977
|
+
server.listen(this.socket, resolve3);
|
|
16978
|
+
});
|
|
16979
|
+
}
|
|
16980
|
+
deliver(id, delivery) {
|
|
16981
|
+
const stream = this.streams.get(id);
|
|
16982
|
+
if (!stream) return Promise.reject(new Error("Claude Code is not taking messages"));
|
|
16983
|
+
return new Promise((resolve3, reject) => stream.write(`${JSON.stringify(delivery)}
|
|
16984
|
+
`, (error) => error ? reject(error) : resolve3()));
|
|
16985
|
+
}
|
|
16986
|
+
// Each request delivers at most once.
|
|
16987
|
+
once(id, action) {
|
|
16988
|
+
const known = this.requests.get(id);
|
|
16989
|
+
if (known) return known;
|
|
16990
|
+
if (this.requests.size >= MAX_REQUESTS)
|
|
16991
|
+
this.requests.delete(this.requests.keys().next().value);
|
|
16992
|
+
const result = action();
|
|
16993
|
+
this.requests.set(id, result);
|
|
16994
|
+
return result;
|
|
16995
|
+
}
|
|
16996
|
+
watch() {
|
|
16997
|
+
if (this.watcher || process.env.DASHUP_LIVE_DISCOVERY === "0") return;
|
|
16998
|
+
try {
|
|
16999
|
+
const watcher = this.watcher = watch2(join5(claudeHome(), "sessions"), () => {
|
|
17000
|
+
if (this.notify) return;
|
|
17001
|
+
this.notify = setTimeout(() => {
|
|
17002
|
+
this.notify = void 0;
|
|
17003
|
+
this.changed();
|
|
17004
|
+
}, 100);
|
|
17005
|
+
this.notify.unref();
|
|
17006
|
+
});
|
|
17007
|
+
watcher.on("error", () => {
|
|
17008
|
+
watcher.close();
|
|
17009
|
+
if (this.watcher === watcher) this.watcher = void 0;
|
|
17010
|
+
});
|
|
17011
|
+
} catch {
|
|
17012
|
+
}
|
|
17013
|
+
}
|
|
17014
|
+
stop() {
|
|
17015
|
+
clearTimeout(this.notify);
|
|
17016
|
+
this.watcher?.close();
|
|
17017
|
+
this.watcher = void 0;
|
|
17018
|
+
this.server?.closeAllConnections();
|
|
17019
|
+
this.server?.close();
|
|
17020
|
+
this.server = void 0;
|
|
17021
|
+
}
|
|
17022
|
+
};
|
|
17023
|
+
|
|
17024
|
+
// src/harness/headless.ts
|
|
16785
17025
|
var CHOICES = [
|
|
16786
17026
|
{ id: "accept", label: "Allow once" },
|
|
16787
17027
|
{ id: "decline", label: "Reject" }
|
|
16788
17028
|
];
|
|
16789
17029
|
var EDITS = /* @__PURE__ */ new Set(["Edit", "MultiEdit", "NotebookEdit", "Write"]);
|
|
16790
|
-
var
|
|
17030
|
+
var MAX_REQUESTS2 = 1e3;
|
|
16791
17031
|
var NOTIFY_MS = 100;
|
|
17032
|
+
var SWITCHES = { fast: "fastMode", ultracode: "ultracode" };
|
|
16792
17033
|
function hunk(before, after, offset = 0) {
|
|
16793
17034
|
const old = before ? before.replace(/\n$/, "").split("\n") : [];
|
|
16794
17035
|
const next = after ? after.replace(/\n$/, "").split("\n") : [];
|
|
@@ -16881,6 +17122,8 @@ var Session = class {
|
|
|
16881
17122
|
typing = "";
|
|
16882
17123
|
approvals = /* @__PURE__ */ new Map();
|
|
16883
17124
|
requests = /* @__PURE__ */ new Map();
|
|
17125
|
+
// What the person chose for this session, applied to each turn as it starts.
|
|
17126
|
+
chosen = {};
|
|
16884
17127
|
write(value2) {
|
|
16885
17128
|
this.turn?.stdin.write(`${JSON.stringify(value2)}
|
|
16886
17129
|
`);
|
|
@@ -16892,10 +17135,17 @@ var Session = class {
|
|
|
16892
17135
|
request: { subtype: "interrupt" }
|
|
16893
17136
|
});
|
|
16894
17137
|
}
|
|
17138
|
+
apply(option2, value2) {
|
|
17139
|
+
this.write({
|
|
17140
|
+
type: "control_request",
|
|
17141
|
+
request_id: randomUUID3(),
|
|
17142
|
+
request: option2 === "model" ? { subtype: "set_model", model: value2 } : { subtype: "apply_flag_settings", settings: { [SWITCHES[option2]]: value2 } }
|
|
17143
|
+
});
|
|
17144
|
+
}
|
|
16895
17145
|
once(id, action) {
|
|
16896
17146
|
const known = this.requests.get(id);
|
|
16897
17147
|
if (known) return known;
|
|
16898
|
-
if (this.requests.size >=
|
|
17148
|
+
if (this.requests.size >= MAX_REQUESTS2)
|
|
16899
17149
|
throw new Error("Claude Code command history is full; restart Dashup before sending");
|
|
16900
17150
|
const result = action();
|
|
16901
17151
|
this.requests.set(id, result);
|
|
@@ -16916,7 +17166,7 @@ var Session = class {
|
|
|
16916
17166
|
this.changed();
|
|
16917
17167
|
return Promise.resolve();
|
|
16918
17168
|
}
|
|
16919
|
-
const turn = this.turn =
|
|
17169
|
+
const turn = this.turn = spawn3(
|
|
16920
17170
|
"claude",
|
|
16921
17171
|
[
|
|
16922
17172
|
"-p",
|
|
@@ -16940,7 +17190,7 @@ var Session = class {
|
|
|
16940
17190
|
errors = (errors + String(chunk)).slice(-2e3);
|
|
16941
17191
|
});
|
|
16942
17192
|
const started = new Promise((resolve3, reject) => {
|
|
16943
|
-
|
|
17193
|
+
createInterface3({ input: turn.stdout }).on("line", (line) => {
|
|
16944
17194
|
if (this.receive(line)) resolve3();
|
|
16945
17195
|
});
|
|
16946
17196
|
const end = (reason) => {
|
|
@@ -16955,6 +17205,7 @@ var Session = class {
|
|
|
16955
17205
|
turn.once("error", () => end("Claude Code is not installed on this computer"));
|
|
16956
17206
|
turn.once("exit", () => end(errors.trim() || "Claude Code stopped before starting"));
|
|
16957
17207
|
});
|
|
17208
|
+
for (const [option2, value2] of Object.entries(this.chosen)) this.apply(option2, value2);
|
|
16958
17209
|
this.write(message);
|
|
16959
17210
|
this.changed();
|
|
16960
17211
|
return started;
|
|
@@ -16996,7 +17247,7 @@ var Session = class {
|
|
|
16996
17247
|
this.changed();
|
|
16997
17248
|
return init;
|
|
16998
17249
|
}
|
|
16999
|
-
live() {
|
|
17250
|
+
live(models) {
|
|
17000
17251
|
return {
|
|
17001
17252
|
id: this.id,
|
|
17002
17253
|
cwd: this.cwd,
|
|
@@ -17009,6 +17260,11 @@ var Session = class {
|
|
|
17009
17260
|
approvals: [...this.approvals.values()].map((pending) => pending.approval),
|
|
17010
17261
|
send: (requestId, text3, now) => this.once(requestId, () => this.say(text3, now)),
|
|
17011
17262
|
now: true,
|
|
17263
|
+
options: (recorded) => claudeOptions(models, recorded),
|
|
17264
|
+
configure: (requestId, option2, value2) => this.once(requestId, async () => {
|
|
17265
|
+
this.chosen[option2] = value2;
|
|
17266
|
+
this.apply(option2, value2);
|
|
17267
|
+
}),
|
|
17012
17268
|
stop: this.turn ? (requestId) => this.once(requestId, async () => {
|
|
17013
17269
|
if (!this.turn) throw new Error("Nothing is running in this Claude Code session");
|
|
17014
17270
|
this.interrupt();
|
|
@@ -17076,7 +17332,8 @@ var Headless = class {
|
|
|
17076
17332
|
}
|
|
17077
17333
|
}
|
|
17078
17334
|
async refresh() {
|
|
17079
|
-
|
|
17335
|
+
const models = claudeModels(this.changed);
|
|
17336
|
+
return [...this.sessions.values()].map((session) => session.live(models));
|
|
17080
17337
|
}
|
|
17081
17338
|
stop() {
|
|
17082
17339
|
clearTimeout(this.notify);
|
|
@@ -17084,138 +17341,9 @@ var Headless = class {
|
|
|
17084
17341
|
}
|
|
17085
17342
|
};
|
|
17086
17343
|
|
|
17087
|
-
// src/harness/claude.ts
|
|
17088
|
-
import { dirname as dirname4, join as join5 } from "node:path";
|
|
17089
|
-
import { watch as watch2 } from "node:fs";
|
|
17090
|
-
import { mkdir, readdir as readdir2, readFile as readFile2, rm } from "node:fs/promises";
|
|
17091
|
-
import { createServer as createServer2 } from "node:http";
|
|
17092
|
-
var MAX_SESSIONS2 = 100;
|
|
17093
|
-
var MAX_REQUESTS2 = 1e3;
|
|
17094
|
-
var SESSION = /^\/sessions\/([\w-]{1,100})$/;
|
|
17095
|
-
var STATUS = {
|
|
17096
|
-
busy: "working",
|
|
17097
|
-
idle: "idle",
|
|
17098
|
-
shell: "idle",
|
|
17099
|
-
waiting: "attention"
|
|
17100
|
-
};
|
|
17101
|
-
async function registrations() {
|
|
17102
|
-
if (process.env.DASHUP_LIVE_DISCOVERY === "0") return [];
|
|
17103
|
-
const directory2 = join5(claudeHome(), "sessions");
|
|
17104
|
-
const names = await readdir2(directory2).catch(() => []);
|
|
17105
|
-
const found = await Promise.all(
|
|
17106
|
-
names.filter((name) => /^\d+\.json$/.test(name)).slice(0, MAX_SESSIONS2).map(async (name) => {
|
|
17107
|
-
try {
|
|
17108
|
-
const value2 = JSON.parse(await readFile2(join5(directory2, name), "utf8"));
|
|
17109
|
-
if (typeof value2.sessionId !== "string" || typeof value2.cwd !== "string") return [];
|
|
17110
|
-
process.kill(value2.pid, 0);
|
|
17111
|
-
return [value2];
|
|
17112
|
-
} catch {
|
|
17113
|
-
return [];
|
|
17114
|
-
}
|
|
17115
|
-
})
|
|
17116
|
-
);
|
|
17117
|
-
return found.flat();
|
|
17118
|
-
}
|
|
17119
|
-
var Claude = class {
|
|
17120
|
-
constructor(changed, socket = join5(dirname4(configPath()), "claude.sock")) {
|
|
17121
|
-
this.changed = changed;
|
|
17122
|
-
this.socket = socket;
|
|
17123
|
-
}
|
|
17124
|
-
changed;
|
|
17125
|
-
socket;
|
|
17126
|
-
watcher;
|
|
17127
|
-
notify;
|
|
17128
|
-
server;
|
|
17129
|
-
streams = /* @__PURE__ */ new Map();
|
|
17130
|
-
requests = /* @__PURE__ */ new Map();
|
|
17131
|
-
async refresh() {
|
|
17132
|
-
this.watch();
|
|
17133
|
-
return (await registrations()).map((session) => {
|
|
17134
|
-
const plugin = this.streams.has(session.sessionId);
|
|
17135
|
-
return {
|
|
17136
|
-
id: session.sessionId,
|
|
17137
|
-
harness: "claude",
|
|
17138
|
-
cwd: session.cwd,
|
|
17139
|
-
updatedAt: new Date(session.updatedAt ?? 0).toISOString(),
|
|
17140
|
-
status: STATUS[session.status ?? ""] ?? "unknown",
|
|
17141
|
-
messages: [],
|
|
17142
|
-
approvals: [],
|
|
17143
|
-
send: plugin ? (requestId, text3, now) => this.once(requestId, () => this.deliver(session.sessionId, now ? { text: text3, now } : { text: text3 })) : void 0,
|
|
17144
|
-
stop: plugin ? (requestId) => this.once(requestId, () => this.deliver(session.sessionId, { now: true })) : void 0,
|
|
17145
|
-
now: plugin
|
|
17146
|
-
};
|
|
17147
|
-
});
|
|
17148
|
-
}
|
|
17149
|
-
// Only the connector serves the plugin; one-off commands such as `dashup doctor` read alone.
|
|
17150
|
-
async listen() {
|
|
17151
|
-
if (this.server || process.env.DASHUP_LIVE_DISCOVERY === "0") return;
|
|
17152
|
-
const server = this.server = createServer2((request2, response) => {
|
|
17153
|
-
const id = request2.method === "GET" ? SESSION.exec(request2.url ?? "")?.[1] : void 0;
|
|
17154
|
-
if (!id) return void response.writeHead(404).end();
|
|
17155
|
-
this.streams.get(id)?.end();
|
|
17156
|
-
this.streams.set(id, response);
|
|
17157
|
-
response.once("close", () => {
|
|
17158
|
-
if (this.streams.get(id) === response) this.streams.delete(id);
|
|
17159
|
-
this.changed();
|
|
17160
|
-
});
|
|
17161
|
-
response.writeHead(200, { "Content-Type": "application/x-ndjson" }).flushHeaders();
|
|
17162
|
-
this.changed();
|
|
17163
|
-
});
|
|
17164
|
-
await mkdir(dirname4(this.socket), { recursive: true, mode: 448 });
|
|
17165
|
-
await rm(this.socket, { force: true });
|
|
17166
|
-
if (this.server !== server) return;
|
|
17167
|
-
await new Promise((resolve3, reject) => {
|
|
17168
|
-
server.once("error", reject);
|
|
17169
|
-
server.listen(this.socket, resolve3);
|
|
17170
|
-
});
|
|
17171
|
-
}
|
|
17172
|
-
deliver(id, delivery) {
|
|
17173
|
-
const stream = this.streams.get(id);
|
|
17174
|
-
if (!stream) return Promise.reject(new Error("Claude Code is not taking messages"));
|
|
17175
|
-
return new Promise((resolve3, reject) => stream.write(`${JSON.stringify(delivery)}
|
|
17176
|
-
`, (error) => error ? reject(error) : resolve3()));
|
|
17177
|
-
}
|
|
17178
|
-
// Each request delivers at most once.
|
|
17179
|
-
once(id, action) {
|
|
17180
|
-
const known = this.requests.get(id);
|
|
17181
|
-
if (known) return known;
|
|
17182
|
-
if (this.requests.size >= MAX_REQUESTS2)
|
|
17183
|
-
this.requests.delete(this.requests.keys().next().value);
|
|
17184
|
-
const result = action();
|
|
17185
|
-
this.requests.set(id, result);
|
|
17186
|
-
return result;
|
|
17187
|
-
}
|
|
17188
|
-
watch() {
|
|
17189
|
-
if (this.watcher || process.env.DASHUP_LIVE_DISCOVERY === "0") return;
|
|
17190
|
-
try {
|
|
17191
|
-
const watcher = this.watcher = watch2(join5(claudeHome(), "sessions"), () => {
|
|
17192
|
-
if (this.notify) return;
|
|
17193
|
-
this.notify = setTimeout(() => {
|
|
17194
|
-
this.notify = void 0;
|
|
17195
|
-
this.changed();
|
|
17196
|
-
}, 100);
|
|
17197
|
-
this.notify.unref();
|
|
17198
|
-
});
|
|
17199
|
-
watcher.on("error", () => {
|
|
17200
|
-
watcher.close();
|
|
17201
|
-
if (this.watcher === watcher) this.watcher = void 0;
|
|
17202
|
-
});
|
|
17203
|
-
} catch {
|
|
17204
|
-
}
|
|
17205
|
-
}
|
|
17206
|
-
stop() {
|
|
17207
|
-
clearTimeout(this.notify);
|
|
17208
|
-
this.watcher?.close();
|
|
17209
|
-
this.watcher = void 0;
|
|
17210
|
-
this.server?.closeAllConnections();
|
|
17211
|
-
this.server?.close();
|
|
17212
|
-
this.server = void 0;
|
|
17213
|
-
}
|
|
17214
|
-
};
|
|
17215
|
-
|
|
17216
17344
|
// src/harness/vscode.ts
|
|
17217
17345
|
import Socket3 from "ws";
|
|
17218
|
-
import { homedir as
|
|
17346
|
+
import { homedir as homedir6 } from "node:os";
|
|
17219
17347
|
import { connect as connect2 } from "node:net";
|
|
17220
17348
|
import { promisify as promisify3 } from "node:util";
|
|
17221
17349
|
import { fileURLToPath } from "node:url";
|
|
@@ -17242,7 +17370,7 @@ function roots() {
|
|
|
17242
17370
|
if (override !== void 0)
|
|
17243
17371
|
return override ? [basename2(override) === "User" ? dirname5(override) : override] : [];
|
|
17244
17372
|
if (process.env.DASHUP_LIVE_DISCOVERY === "0") return [];
|
|
17245
|
-
const base = process.platform === "darwin" ? join6(
|
|
17373
|
+
const base = process.platform === "darwin" ? join6(homedir6(), "Library/Application Support") : process.platform === "win32" ? process.env.APPDATA ?? join6(homedir6(), "AppData/Roaming") : process.env.XDG_CONFIG_HOME ?? join6(homedir6(), ".config");
|
|
17246
17374
|
return ["Code", "Code - Insiders"].map((name) => join6(base, name));
|
|
17247
17375
|
}
|
|
17248
17376
|
async function privateFile(path, limit) {
|
|
@@ -17715,8 +17843,8 @@ var VSCode = class {
|
|
|
17715
17843
|
import { join as join7 } from "node:path";
|
|
17716
17844
|
import { promisify as promisify4 } from "node:util";
|
|
17717
17845
|
import { createHash as createHash4 } from "node:crypto";
|
|
17718
|
-
import { execFile as execFile4, spawn as
|
|
17719
|
-
import { stat as stat3, readlink, readdir as readdir4, readFile as readFile4 } from "node:fs/promises";
|
|
17846
|
+
import { execFile as execFile4, spawn as spawn4 } from "node:child_process";
|
|
17847
|
+
import { stat as stat3, readlink as readlink2, readdir as readdir4, readFile as readFile4 } from "node:fs/promises";
|
|
17720
17848
|
var run2 = promisify4(execFile4);
|
|
17721
17849
|
var REQUEST_MS3 = 4e3;
|
|
17722
17850
|
var MAX_BYTES3 = 8 * 1024 * 1024;
|
|
@@ -17771,7 +17899,7 @@ async function linuxEndpoints() {
|
|
|
17771
17899
|
})
|
|
17772
17900
|
);
|
|
17773
17901
|
for (const fd of (await readdir4(join7(directory2, "fd"))).slice(0, 1024)) {
|
|
17774
|
-
const target = await
|
|
17902
|
+
const target = await readlink2(join7(directory2, "fd", fd)).catch(() => "");
|
|
17775
17903
|
const inode = /^socket:\[(\d+)\]$/.exec(target)?.[1];
|
|
17776
17904
|
const url2 = inode ? listeners.get(inode) : void 0;
|
|
17777
17905
|
if (url2) result.push({ url: url2, authorization: authorization(environment) });
|
|
@@ -18183,7 +18311,7 @@ var Server = class {
|
|
|
18183
18311
|
}
|
|
18184
18312
|
};
|
|
18185
18313
|
function serve(cwd) {
|
|
18186
|
-
const child =
|
|
18314
|
+
const child = spawn4("opencode", ["serve", "--hostname", "127.0.0.1", "--port", "0"], {
|
|
18187
18315
|
cwd,
|
|
18188
18316
|
stdio: ["ignore", "pipe", "ignore"],
|
|
18189
18317
|
shell: process.platform === "win32"
|
|
@@ -18304,9 +18432,10 @@ var LiveHarnesses = class {
|
|
|
18304
18432
|
});
|
|
18305
18433
|
return this.refreshing;
|
|
18306
18434
|
}
|
|
18307
|
-
// The agents whose own command runs on this computer.
|
|
18435
|
+
// The agents whose own command runs on this computer. Claude Code is asked its models again.
|
|
18308
18436
|
async agents() {
|
|
18309
18437
|
const harnesses = ["claude", "codex", "opencode"];
|
|
18438
|
+
forgetModels();
|
|
18310
18439
|
const runs = await Promise.all(
|
|
18311
18440
|
harnesses.map((harness) => promisify5(execFile5)(harness, ["--version"], {
|
|
18312
18441
|
timeout: 1e4,
|
|
@@ -18365,7 +18494,7 @@ var LiveHarnesses = class {
|
|
|
18365
18494
|
|
|
18366
18495
|
// src/harness/queue.ts
|
|
18367
18496
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
18368
|
-
import { spawn as
|
|
18497
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
18369
18498
|
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
18370
18499
|
import { join as join8, dirname as dirname6, basename as basename3 } from "node:path";
|
|
18371
18500
|
import { statSync as statSync2, existsSync as existsSync4, readdirSync as readdirSync3, readFileSync as readFileSync4, readlinkSync, realpathSync as realpathSync2 } from "node:fs";
|
|
@@ -18562,7 +18691,7 @@ var Writer = class {
|
|
|
18562
18691
|
tail = "";
|
|
18563
18692
|
pending = /* @__PURE__ */ new Map();
|
|
18564
18693
|
constructor(owner) {
|
|
18565
|
-
this.process =
|
|
18694
|
+
this.process = spawn5(owner.executable, ["app-server", "--listen", "stdio://"], {
|
|
18566
18695
|
cwd: owner.cwd,
|
|
18567
18696
|
env: { ...process.env, CODEX_HOME: owner.home },
|
|
18568
18697
|
stdio: ["pipe", "pipe", "ignore"]
|
|
@@ -18739,6 +18868,11 @@ var Command = z2.object({
|
|
|
18739
18868
|
}).strict(),
|
|
18740
18869
|
z2.object({ kind: z2.literal("thread.queue"), text: z2.string().min(1).max(1e5) }).strict(),
|
|
18741
18870
|
z2.object({ kind: z2.literal("thread.stop") }).strict(),
|
|
18871
|
+
z2.object({
|
|
18872
|
+
kind: z2.literal("thread.configure"),
|
|
18873
|
+
option: z2.string().min(1).max(100),
|
|
18874
|
+
value: z2.union([z2.string().min(1).max(200), z2.boolean()])
|
|
18875
|
+
}).strict(),
|
|
18742
18876
|
z2.object({ kind: z2.literal("approval.respond"), approvalId: z2.string(), choice: z2.string() }).strict(),
|
|
18743
18877
|
z2.object({ kind: z2.literal("attachment.read"), id: z2.string().regex(/^[a-f0-9]{64}$/) }).strict(),
|
|
18744
18878
|
z2.object({
|
|
@@ -19078,13 +19212,15 @@ var Connector = class {
|
|
|
19078
19212
|
const detached = {
|
|
19079
19213
|
...snapshot.thread,
|
|
19080
19214
|
status: "unknown",
|
|
19215
|
+
options: void 0,
|
|
19081
19216
|
capabilities: {
|
|
19082
19217
|
...snapshot.thread.capabilities,
|
|
19083
19218
|
send: false,
|
|
19084
19219
|
approve: false,
|
|
19085
19220
|
// A separate queue owner can remain valid after its live socket detaches.
|
|
19086
19221
|
queue: thread?.capabilities.queue ?? false,
|
|
19087
|
-
stop: false
|
|
19222
|
+
stop: false,
|
|
19223
|
+
configure: false
|
|
19088
19224
|
}
|
|
19089
19225
|
};
|
|
19090
19226
|
if (thread) Object.assign(thread, detached);
|
|
@@ -19254,7 +19390,9 @@ var Connector = class {
|
|
|
19254
19390
|
async execute(pair, content, envelope) {
|
|
19255
19391
|
let queue = void 0;
|
|
19256
19392
|
try {
|
|
19257
|
-
if (["approval.respond", "thread.stop", "session.start"].includes(
|
|
19393
|
+
if (["approval.respond", "thread.stop", "thread.configure", "session.start"].includes(
|
|
19394
|
+
content.action.kind
|
|
19395
|
+
) && Math.abs(Date.now() - Date.parse(envelope.sentAt)) > EPHEMERAL_TTL_MS)
|
|
19258
19396
|
throw new Error("Command expired. Try again while this computer is online.");
|
|
19259
19397
|
const outcome = await this.command(pair, content);
|
|
19260
19398
|
delete this.config.unconfirmed[content.requestId];
|
|
@@ -19461,6 +19599,16 @@ var Connector = class {
|
|
|
19461
19599
|
);
|
|
19462
19600
|
return commandAcp(agent, command2.requestId, { kind: "cancel" });
|
|
19463
19601
|
}
|
|
19602
|
+
case "thread.configure": {
|
|
19603
|
+
const { option: option2, value: value2 } = command2.action;
|
|
19604
|
+
const native = thread && this.discovery.locations.get(thread.id)?.live;
|
|
19605
|
+
const known = thread?.options?.find((item) => item.id === option2);
|
|
19606
|
+
if (!native?.configure || !known)
|
|
19607
|
+
throw new Error("This session can only be changed on your computer.");
|
|
19608
|
+
if (typeof value2 !== typeof known.value || known.choices && !known.choices.some((choice) => choice.value === value2))
|
|
19609
|
+
throw new Error(`${known.name} can't be set to that.`);
|
|
19610
|
+
return native.configure(command2.requestId, option2, value2);
|
|
19611
|
+
}
|
|
19464
19612
|
case "thread.queue": {
|
|
19465
19613
|
if (!thread) throw new Error("Thread not found");
|
|
19466
19614
|
this.nativeQueue.refresh();
|
|
@@ -19543,10 +19691,10 @@ var Connector = class {
|
|
|
19543
19691
|
|
|
19544
19692
|
// src/install.ts
|
|
19545
19693
|
import { existsSync as existsSync5 } from "node:fs";
|
|
19546
|
-
import { homedir as
|
|
19694
|
+
import { homedir as homedir7, userInfo } from "node:os";
|
|
19547
19695
|
import { promisify as promisify6 } from "node:util";
|
|
19548
19696
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
19549
|
-
import { execFile as execFile6, spawn as
|
|
19697
|
+
import { execFile as execFile6, spawn as spawn6 } from "node:child_process";
|
|
19550
19698
|
import { cp, mkdir as mkdir3, rm as rm2, writeFile } from "node:fs/promises";
|
|
19551
19699
|
import { dirname as dirname7, join as join10, relative, sep } from "node:path";
|
|
19552
19700
|
var run3 = promisify6(execFile6);
|
|
@@ -19555,10 +19703,10 @@ var NODE_FLAGS = ["--disable-warning=ExperimentalWarning"];
|
|
|
19555
19703
|
var systemctl = (...args2) => run3("systemctl", ["--user", ...args2], {
|
|
19556
19704
|
env: { ...process.env, XDG_RUNTIME_DIR: process.env.XDG_RUNTIME_DIR ?? `/run/user/${userInfo().uid}` }
|
|
19557
19705
|
});
|
|
19558
|
-
var serviceFile = () => process.platform === "darwin" ? join10(
|
|
19559
|
-
process.env.APPDATA ?? join10(
|
|
19706
|
+
var serviceFile = () => process.platform === "darwin" ? join10(homedir7(), "Library/LaunchAgents/com.dashup.connector.plist") : process.platform === "win32" ? join10(
|
|
19707
|
+
process.env.APPDATA ?? join10(homedir7(), "AppData/Roaming"),
|
|
19560
19708
|
"Microsoft/Windows/Start Menu/Programs/Startup/Dashup.vbs"
|
|
19561
|
-
) : join10(
|
|
19709
|
+
) : join10(homedir7(), ".config/systemd/user/dashup.service");
|
|
19562
19710
|
var startupInstalled = () => existsSync5(serviceFile());
|
|
19563
19711
|
function service(platform, node, entry, config2) {
|
|
19564
19712
|
const command2 = [node, ...NODE_FLAGS, entry];
|
|
@@ -19627,7 +19775,7 @@ async function start(path) {
|
|
|
19627
19775
|
});
|
|
19628
19776
|
await run3("launchctl", ["load", path]);
|
|
19629
19777
|
} else if (process.platform === "win32") {
|
|
19630
|
-
|
|
19778
|
+
spawn6("wscript.exe", [path], { detached: true, stdio: "ignore" }).unref();
|
|
19631
19779
|
} else {
|
|
19632
19780
|
const note = process.env.DISPLAY || process.env.WAYLAND_DISPLAY ? void 0 : await run3("loginctl", ["enable-linger"]).then(
|
|
19633
19781
|
() => void 0,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dashup/connector",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"dashup": "dist/index.js"
|
|
@@ -30,11 +30,11 @@
|
|
|
30
30
|
"tsup": "^8.5.1",
|
|
31
31
|
"tsx": "^4.22.4",
|
|
32
32
|
"typescript": "^5.9.3",
|
|
33
|
-
"@dashup/models": "0.1.0",
|
|
34
33
|
"@dashup/protocol": "0.1.0",
|
|
35
|
-
"@dashup/typescript-config": "0.0.0",
|
|
36
34
|
"@parcae/lynx": "0.1.0",
|
|
37
|
-
"@parcae/sdk": "0.8.1"
|
|
35
|
+
"@parcae/sdk": "0.8.1",
|
|
36
|
+
"@dashup/typescript-config": "0.0.0",
|
|
37
|
+
"@dashup/models": "0.1.0"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
40
|
"dashup": "tsx src/index.ts",
|