@dashup/connector 0.1.10 → 0.1.12
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 +2 -2
- package/claude/.claude-plugin/marketplace.json +5 -0
- package/claude/.claude-plugin/plugin.json +4 -0
- package/claude/hooks/hooks.json +1 -0
- package/claude/hooks/register.ts +69 -0
- package/dist/index.js +278 -163
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -37,7 +37,7 @@ Dashup discovers existing local OpenCode listeners, Codex control sockets, and V
|
|
|
37
37
|
- **OpenCode:** the existing TUI/server supplies conversations, live events, prompts, native tool permissions, and supported single-choice questions. A reply targets the original session ID and preserves its latest native agent, model, and variant. For an existing session, Dashup does not run `opencode serve` or create another one.
|
|
38
38
|
- **Codex:** the existing control socket supplies already-loaded threads. Replies require a recognized native server and a thread that is still loaded and idle immediately before sending. For an existing thread, Dashup never calls `thread/start`, `thread/resume`, or `session/load`. Approval controls appear only for native requests routed to this connection; approvals owned by another client may remain available only in the original UI.
|
|
39
39
|
- **VS Code:** Microsoft's official AHP client subscribes to existing host sessions and chats. Native choices are retained, and delivery is acknowledged by the host. Older extension-host chats without an accessible Agent Host remain saved history. AHP chat IDs are namespaced rather than guessed to equal CLI thread IDs. Non-file remote AHP workspace URIs are not presented as local execution environments.
|
|
40
|
-
- **Claude Code:** existing transcripts are readable without starting Claude.
|
|
40
|
+
- **Claude Code:** existing transcripts are readable without starting Claude. `dashup install` adds Dashup's plugin to Claude Code (`dashup@dashup`, read in place from this package's `claude/` folder), and `dashup uninstall` removes it. A session that starts with the plugin holds a stream open to `~/.dashup/claude.sock` through `curl`, and takes each reply as the person's own words; a reply that starts with `/` runs that command. Claude Code's inbox socket is not used, because Claude presents anything posted there as another session's message, without the person's authority. Sessions opened before the plugin was installed, and sessions on Windows, stay read-only.
|
|
41
41
|
|
|
42
42
|
Native listener discovery supports the host's available process/socket interfaces. File and socket formats are versioned implementation boundaries; unavailable or unfamiliar interfaces leave history readable and controls disabled. Existing editor and agent processes continue independently when the Dashup connector disconnects. The PC's saved SSH relationship is retained; reading it does not grant access to a remote machine's CLI store.
|
|
43
43
|
|
|
@@ -47,7 +47,7 @@ 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
|
-
Stop interrupts the running turn through each live interface: OpenCode's session abort, Codex `turn/interrupt`, the AHP client's turn cancellation,
|
|
50
|
+
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
51
|
|
|
52
52
|
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.
|
|
53
53
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "modules": ["./register.ts"] }
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { EngineInterface, Register } from 'claude-code';
|
|
2
|
+
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// Replies from the Dashup app
|
|
5
|
+
// =============================================================================
|
|
6
|
+
|
|
7
|
+
// Claude Code reads what other processes post to a session's inbox as another session's request,
|
|
8
|
+
// never the person's. Dashup's connector instead streams what the person sends this session from
|
|
9
|
+
// their phone, one delivery per line, and the session takes each message as their own. `now`
|
|
10
|
+
// first ends the running turn, so a delivery without text only stops it.
|
|
11
|
+
type Delivery = { text?: string; now?: true };
|
|
12
|
+
|
|
13
|
+
const RETRY_MS = 10_000;
|
|
14
|
+
|
|
15
|
+
export const register: Register = (on) => {
|
|
16
|
+
let turnId: string | undefined;
|
|
17
|
+
let stream: AsyncGenerator<unknown, unknown> | undefined;
|
|
18
|
+
on('turn.start', ($, e, next) => {
|
|
19
|
+
if (e.agentId === undefined) turnId = e.turnId;
|
|
20
|
+
return next(e);
|
|
21
|
+
});
|
|
22
|
+
// `/clear` starts another conversation in this process, which streams under its own id.
|
|
23
|
+
on('session.end', ($, e, next) => {
|
|
24
|
+
void stream?.return(undefined);
|
|
25
|
+
return next(e);
|
|
26
|
+
});
|
|
27
|
+
on('session.start', async ($, e, next) => {
|
|
28
|
+
const started = await next(e);
|
|
29
|
+
void (async () => {
|
|
30
|
+
const socketPath = `${await $.env.get('HOME')}/.dashup/claude.sock`;
|
|
31
|
+
for (;;) {
|
|
32
|
+
// A request held open here would hold the session's queue; a child's stream does not.
|
|
33
|
+
const lines = (stream = $.process.spawn({
|
|
34
|
+
argv: [
|
|
35
|
+
'curl',
|
|
36
|
+
'-sfN',
|
|
37
|
+
'--unix-socket',
|
|
38
|
+
socketPath,
|
|
39
|
+
`http://dashup/sessions/${await $.session.id()}`,
|
|
40
|
+
],
|
|
41
|
+
}));
|
|
42
|
+
let buffer = '';
|
|
43
|
+
try {
|
|
44
|
+
for await (const { stream: name, text } of lines) {
|
|
45
|
+
if (name !== 'stdout') continue;
|
|
46
|
+
buffer += text;
|
|
47
|
+
for (let end = buffer.indexOf('\n'); end !== -1; end = buffer.indexOf('\n')) {
|
|
48
|
+
await take($, JSON.parse(buffer.slice(0, end)) as Delivery, turnId);
|
|
49
|
+
buffer = buffer.slice(end + 1);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
} catch {
|
|
53
|
+
// curl is missing, or the stream broke off mid-line.
|
|
54
|
+
}
|
|
55
|
+
// Dashup is not running on this computer, or is restarting.
|
|
56
|
+
await $.clock.sleep(RETRY_MS);
|
|
57
|
+
}
|
|
58
|
+
})();
|
|
59
|
+
return started;
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// A message runs once the session is idle; the stream must not wait for that.
|
|
64
|
+
async function take($: EngineInterface, { text, now }: Delivery, turnId?: string): Promise<void> {
|
|
65
|
+
if (now && turnId) await $.turn.abort({ turnId }).catch(() => {});
|
|
66
|
+
const command = text?.match(/^\/(\S+)\s*([\s\S]*)$/);
|
|
67
|
+
if (command) void $.command.run({ command: command[1]!, args: command[2] }).catch(() => {});
|
|
68
|
+
else if (text) void $.prompt.submit({ text, asUser: true }).catch(() => {});
|
|
69
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -2149,15 +2149,15 @@ var require_short_unique_id = __commonJS({
|
|
|
2149
2149
|
this.lowerBound = this.dictRange[0];
|
|
2150
2150
|
this.upperBound = this.dictRange[1];
|
|
2151
2151
|
const isAscending = this.lowerBound <= this.upperBound;
|
|
2152
|
-
const
|
|
2152
|
+
const start2 = this.lowerBound;
|
|
2153
2153
|
const end = this.upperBound;
|
|
2154
2154
|
if (isAscending) {
|
|
2155
|
-
for (let i =
|
|
2155
|
+
for (let i = start2; i < end; i++) {
|
|
2156
2156
|
finalDict[dictIdx++] = String.fromCharCode(i);
|
|
2157
2157
|
this.dictIndex = i;
|
|
2158
2158
|
}
|
|
2159
2159
|
} else {
|
|
2160
|
-
for (let i =
|
|
2160
|
+
for (let i = start2; i > end; i--) {
|
|
2161
2161
|
finalDict[dictIdx++] = String.fromCharCode(i);
|
|
2162
2162
|
this.dictIndex = i;
|
|
2163
2163
|
}
|
|
@@ -2706,7 +2706,7 @@ var require_pluralize = __commonJS({
|
|
|
2706
2706
|
|
|
2707
2707
|
// src/index.ts
|
|
2708
2708
|
import { randomBytes as randomBytes5, timingSafeEqual } from "node:crypto";
|
|
2709
|
-
import { createServer as
|
|
2709
|
+
import { createServer as createServer3 } from "node:http";
|
|
2710
2710
|
import * as ui2 from "@clack/prompts";
|
|
2711
2711
|
import { z as z3 } from "zod";
|
|
2712
2712
|
|
|
@@ -2728,6 +2728,7 @@ var PROTOCOL_VERSION = 1;
|
|
|
2728
2728
|
var MAX_ENVELOPE_BYTES = 8 * 1024 * 1024;
|
|
2729
2729
|
var HISTORY_PAGE_ITEMS = 30;
|
|
2730
2730
|
var HISTORY_TEXT_CHARS = 4e3;
|
|
2731
|
+
var HISTORY_TOOL_CHARS = 500;
|
|
2731
2732
|
var HISTORY_PAGE_BYTES = 256 * 1024;
|
|
2732
2733
|
var HISTORY_BATCH_ITEMS = 50;
|
|
2733
2734
|
var HISTORY_BATCH_BYTES = HISTORY_PAGE_BYTES;
|
|
@@ -2741,9 +2742,11 @@ var MESSAGE_ROLES = ["user", "assistant", "system", "tool"];
|
|
|
2741
2742
|
var HARNESSES = {
|
|
2742
2743
|
claude: "Claude Code",
|
|
2743
2744
|
codex: "Codex",
|
|
2745
|
+
kimi: "Kimi",
|
|
2744
2746
|
opencode: "OpenCode",
|
|
2745
2747
|
other: "Agent"
|
|
2746
2748
|
};
|
|
2749
|
+
var isStartHarness = (harness) => harness !== "other" && harness !== "kimi";
|
|
2747
2750
|
|
|
2748
2751
|
// ../../node_modules/.pnpm/@noble+ciphers@2.4.0/node_modules/@noble/ciphers/utils.js
|
|
2749
2752
|
function isBytes(a) {
|
|
@@ -3868,16 +3871,16 @@ function attachAcp(socket, onEvent) {
|
|
|
3868
3871
|
async function proxyAcp(config2, executable, args2) {
|
|
3869
3872
|
if (!config2.localSecret) throw new Error("Start dashup watch before using the ACP proxy");
|
|
3870
3873
|
const socket = await new Promise((resolve3, reject) => {
|
|
3871
|
-
const
|
|
3874
|
+
const connect3 = request({
|
|
3872
3875
|
hostname: "127.0.0.1",
|
|
3873
3876
|
port: connectorPort(),
|
|
3874
3877
|
method: "CONNECT",
|
|
3875
3878
|
path: "/acp",
|
|
3876
3879
|
headers: { Authorization: `Bearer ${config2.localSecret}` }
|
|
3877
3880
|
});
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
+
connect3.on("connect", (response, stream) => response.statusCode === 200 ? resolve3(stream) : reject(new Error("Dashup ACP connection was rejected")));
|
|
3882
|
+
connect3.on("error", reject);
|
|
3883
|
+
connect3.end();
|
|
3881
3884
|
});
|
|
3882
3885
|
const child = spawn(executable, args2, { stdio: ["pipe", "pipe", "inherit"], windowsHide: true });
|
|
3883
3886
|
const local = /* @__PURE__ */ new Set();
|
|
@@ -4045,7 +4048,7 @@ import * as ui from "@clack/prompts";
|
|
|
4045
4048
|
import { z } from "zod";
|
|
4046
4049
|
|
|
4047
4050
|
// package.json
|
|
4048
|
-
var version = "0.1.
|
|
4051
|
+
var version = "0.1.12";
|
|
4049
4052
|
|
|
4050
4053
|
// src/pair.ts
|
|
4051
4054
|
var Invitation = z.object({
|
|
@@ -4199,6 +4202,7 @@ var sessionKey = (session) => `${session.harness}\0${session.id}`;
|
|
|
4199
4202
|
function agentHarness(name) {
|
|
4200
4203
|
if (/claude/i.test(name)) return "claude";
|
|
4201
4204
|
if (/codex/i.test(name)) return "codex";
|
|
4205
|
+
if (/kimi/i.test(name)) return "kimi";
|
|
4202
4206
|
if (/opencode/i.test(name)) return "opencode";
|
|
4203
4207
|
return void 0;
|
|
4204
4208
|
}
|
|
@@ -4239,13 +4243,13 @@ function records(buffer) {
|
|
|
4239
4243
|
function unchanged(left, right) {
|
|
4240
4244
|
return left.ino === right.ino && left.size === right.size && left.mtimeMs === right.mtimeMs;
|
|
4241
4245
|
}
|
|
4242
|
-
function readRange(path,
|
|
4246
|
+
function readRange(path, start2, length) {
|
|
4243
4247
|
const fd = openSync2(path, "r");
|
|
4244
4248
|
try {
|
|
4245
4249
|
const buffer = Buffer.alloc(length);
|
|
4246
4250
|
let offset = 0;
|
|
4247
4251
|
while (offset < length) {
|
|
4248
|
-
const count = readSync(fd, buffer, offset, length - offset,
|
|
4252
|
+
const count = readSync(fd, buffer, offset, length - offset, start2 + offset);
|
|
4249
4253
|
if (!count) break;
|
|
4250
4254
|
offset += count;
|
|
4251
4255
|
}
|
|
@@ -4273,12 +4277,12 @@ function described(path, until, describe2) {
|
|
|
4273
4277
|
}
|
|
4274
4278
|
let tail = [];
|
|
4275
4279
|
for (let bytes = SAMPLE_BYTES; stamp.size > head.length; bytes *= 2) {
|
|
4276
|
-
const
|
|
4277
|
-
const buffer = readRange(path,
|
|
4280
|
+
const start2 = Math.max(head.length, stamp.size - bytes);
|
|
4281
|
+
const buffer = readRange(path, start2, stamp.size - start2);
|
|
4278
4282
|
tail = records(
|
|
4279
|
-
readRange(path,
|
|
4283
|
+
readRange(path, start2 - 1, 1)[0] === 10 ? buffer : buffer.subarray(buffer.indexOf(10) + 1)
|
|
4280
4284
|
);
|
|
4281
|
-
if (
|
|
4285
|
+
if (start2 === head.length || tail.some(until)) break;
|
|
4282
4286
|
}
|
|
4283
4287
|
const value2 = describe2([...records(head), ...tail], stamp);
|
|
4284
4288
|
metadata.set(path, { stamp, value: value2 });
|
|
@@ -4295,8 +4299,8 @@ function journal(path, latest = false) {
|
|
|
4295
4299
|
return tail2 ? [...saved.rows, tail2] : saved.rows;
|
|
4296
4300
|
}
|
|
4297
4301
|
if (!saved || saved.stamp.ino !== stamp.ino || stamp.size <= saved.offset || latest && stamp.size - saved.start > 2 * LATEST_BYTES) {
|
|
4298
|
-
const
|
|
4299
|
-
saved = { stamp, start, offset:
|
|
4302
|
+
const start2 = latest ? Math.max(0, stamp.size - LATEST_BYTES) : 0;
|
|
4303
|
+
saved = { stamp, start: start2, offset: start2, tail: Buffer.alloc(0), rows: [] };
|
|
4300
4304
|
}
|
|
4301
4305
|
while (saved.offset < stamp.size) {
|
|
4302
4306
|
const chunk = readRange(path, saved.offset, Math.min(READ_BYTES, stamp.size - saved.offset));
|
|
@@ -4390,7 +4394,6 @@ function prose(id, role, content) {
|
|
|
4390
4394
|
const files2 = attachments(id, content);
|
|
4391
4395
|
return value2 || files2.length ? [{ id, role, text: value2, ...files2.length ? { attachments: files2 } : {} }] : [];
|
|
4392
4396
|
}
|
|
4393
|
-
var SUMMARY_LIMIT = 200;
|
|
4394
4397
|
var SUMMARY_FIELDS = [
|
|
4395
4398
|
"command",
|
|
4396
4399
|
"cmd",
|
|
@@ -4412,11 +4415,15 @@ function describe(input) {
|
|
|
4412
4415
|
);
|
|
4413
4416
|
const value2 = typeof input === "string" ? input : named2 ?? (fields && Object.keys(fields).length ? JSON.stringify(fields) : "");
|
|
4414
4417
|
const paths = [...value2.matchAll(PATCH_FILE)].map((match) => match[2]).join(", ");
|
|
4415
|
-
|
|
4416
|
-
|
|
4418
|
+
return (paths || value2.trim()).slice(0, HISTORY_TOOL_CHARS) || void 0;
|
|
4419
|
+
}
|
|
4420
|
+
function said(input, summary) {
|
|
4421
|
+
const value2 = object(input)?.description;
|
|
4422
|
+
const line = typeof value2 === "string" ? value2.trim().split("\n")[0] : "";
|
|
4423
|
+
return line && line !== summary ? line.slice(0, HISTORY_TOOL_CHARS) : void 0;
|
|
4417
4424
|
}
|
|
4418
|
-
function elapsed(
|
|
4419
|
-
const ms = instant(end) - instant(
|
|
4425
|
+
function elapsed(start2, end) {
|
|
4426
|
+
const ms = instant(end) - instant(start2);
|
|
4420
4427
|
return Number.isFinite(ms) ? Math.max(0, Math.round(ms)) : void 0;
|
|
4421
4428
|
}
|
|
4422
4429
|
function added(path, content) {
|
|
@@ -4429,18 +4436,22 @@ function added(path, content) {
|
|
|
4429
4436
|
].join("\n");
|
|
4430
4437
|
}
|
|
4431
4438
|
var binary = (diff) => diff.slice(0, 8e3).includes("\0");
|
|
4432
|
-
function step(id, name, input, result) {
|
|
4439
|
+
function step(id, name, input, result, started) {
|
|
4433
4440
|
const summary = describe(input);
|
|
4441
|
+
const label = said(input, summary);
|
|
4442
|
+
const at = instant(started);
|
|
4434
4443
|
const diff = result?.status === "ok" && result.diff && !binary(result.diff) ? result.diff : void 0;
|
|
4435
4444
|
return {
|
|
4436
4445
|
id,
|
|
4437
4446
|
role: "tool",
|
|
4438
4447
|
text: diff || result?.text || "",
|
|
4439
4448
|
tool: {
|
|
4440
|
-
name: typeof name === "string" && name ? name.slice(0,
|
|
4449
|
+
name: typeof name === "string" && name ? name.slice(0, HISTORY_TOOL_CHARS) : "Tool",
|
|
4450
|
+
...label ? { label } : {},
|
|
4441
4451
|
...summary ? { summary } : {},
|
|
4442
4452
|
...result?.status ? { status: result.status } : {},
|
|
4443
4453
|
...result?.status && result.ms !== void 0 ? { ms: result.ms } : {},
|
|
4454
|
+
...!result?.status && Number.isFinite(at) ? { at: new Date(at).toISOString() } : {},
|
|
4444
4455
|
...diff ? { diff: true } : {}
|
|
4445
4456
|
},
|
|
4446
4457
|
...result?.attachments?.length ? { attachments: result.attachments } : {}
|
|
@@ -4511,7 +4522,8 @@ function claudeConversation(conversation, rows) {
|
|
|
4511
4522
|
ms: elapsed(row.timestamp, result.row.timestamp),
|
|
4512
4523
|
diff: claudeChange(object(result.row.toolUseResult)),
|
|
4513
4524
|
attachments: attachments(call, result.part.content)
|
|
4514
|
-
}
|
|
4525
|
+
},
|
|
4526
|
+
row.timestamp
|
|
4515
4527
|
)
|
|
4516
4528
|
];
|
|
4517
4529
|
})
|
|
@@ -4600,7 +4612,8 @@ function codexMessages(path) {
|
|
|
4600
4612
|
ms: elapsed(row.timestamp, output.timestamp),
|
|
4601
4613
|
diff: patch && applyPatch(patch),
|
|
4602
4614
|
attachments: attachments(call, output.payload.output)
|
|
4603
|
-
}
|
|
4615
|
+
},
|
|
4616
|
+
row.timestamp
|
|
4604
4617
|
)
|
|
4605
4618
|
];
|
|
4606
4619
|
});
|
|
@@ -4699,7 +4712,8 @@ function openCodeParts(id, role, parts2) {
|
|
|
4699
4712
|
ms: elapsed(state.time?.start, state.time?.end),
|
|
4700
4713
|
diff: openCodeChange(state),
|
|
4701
4714
|
attachments: attachments(key, state.attachments)
|
|
4702
|
-
}
|
|
4715
|
+
},
|
|
4716
|
+
state.time?.start
|
|
4703
4717
|
)
|
|
4704
4718
|
];
|
|
4705
4719
|
});
|
|
@@ -5060,6 +5074,7 @@ var Discovery = class {
|
|
|
5060
5074
|
read: true,
|
|
5061
5075
|
send: Boolean(session.send),
|
|
5062
5076
|
stop: Boolean(session.stop),
|
|
5077
|
+
now: Boolean(session.now),
|
|
5063
5078
|
approve: Boolean(session.approve)
|
|
5064
5079
|
}
|
|
5065
5080
|
});
|
|
@@ -5276,7 +5291,7 @@ var Discovery = class {
|
|
|
5276
5291
|
// src/connector.ts
|
|
5277
5292
|
import { z as z2 } from "zod";
|
|
5278
5293
|
import { join as join9 } from "node:path";
|
|
5279
|
-
import { mkdir } from "node:fs/promises";
|
|
5294
|
+
import { mkdir as mkdir2 } from "node:fs/promises";
|
|
5280
5295
|
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
5281
5296
|
|
|
5282
5297
|
// ../../node_modules/.pnpm/engine.io-client@6.6.7/node_modules/engine.io-client/build/esm-debug/transports/polling-xhr.node.js
|
|
@@ -7421,10 +7436,10 @@ var Decoder = class _Decoder extends Emitter {
|
|
|
7421
7436
|
throw new Error("unknown packet type " + p.type);
|
|
7422
7437
|
}
|
|
7423
7438
|
if (p.type === PacketType.BINARY_EVENT || p.type === PacketType.BINARY_ACK) {
|
|
7424
|
-
const
|
|
7439
|
+
const start2 = i + 1;
|
|
7425
7440
|
while (str.charAt(++i) !== "-" && i != str.length) {
|
|
7426
7441
|
}
|
|
7427
|
-
const buf = str.substring(
|
|
7442
|
+
const buf = str.substring(start2, i);
|
|
7428
7443
|
if (buf != Number(buf) || str.charAt(i) !== "-") {
|
|
7429
7444
|
throw new Error("Illegal attachments");
|
|
7430
7445
|
}
|
|
@@ -7437,7 +7452,7 @@ var Decoder = class _Decoder extends Emitter {
|
|
|
7437
7452
|
p.attachments = n;
|
|
7438
7453
|
}
|
|
7439
7454
|
if ("/" === str.charAt(i + 1)) {
|
|
7440
|
-
const
|
|
7455
|
+
const start2 = i + 1;
|
|
7441
7456
|
while (++i) {
|
|
7442
7457
|
const c = str.charAt(i);
|
|
7443
7458
|
if ("," === c)
|
|
@@ -7445,13 +7460,13 @@ var Decoder = class _Decoder extends Emitter {
|
|
|
7445
7460
|
if (i === str.length)
|
|
7446
7461
|
break;
|
|
7447
7462
|
}
|
|
7448
|
-
p.nsp = str.substring(
|
|
7463
|
+
p.nsp = str.substring(start2, i);
|
|
7449
7464
|
} else {
|
|
7450
7465
|
p.nsp = "/";
|
|
7451
7466
|
}
|
|
7452
7467
|
const next = str.charAt(i + 1);
|
|
7453
7468
|
if ("" !== next && Number(next) == next) {
|
|
7454
|
-
const
|
|
7469
|
+
const start2 = i + 1;
|
|
7455
7470
|
while (++i) {
|
|
7456
7471
|
const c = str.charAt(i);
|
|
7457
7472
|
if (null == c || Number(c) != c) {
|
|
@@ -7461,7 +7476,7 @@ var Decoder = class _Decoder extends Emitter {
|
|
|
7461
7476
|
if (i === str.length)
|
|
7462
7477
|
break;
|
|
7463
7478
|
}
|
|
7464
|
-
p.id = Number(str.substring(
|
|
7479
|
+
p.id = Number(str.substring(start2, i + 1));
|
|
7465
7480
|
}
|
|
7466
7481
|
if (str.charAt(++i)) {
|
|
7467
7482
|
const payload = this.tryParse(str.substr(i));
|
|
@@ -9671,7 +9686,7 @@ var putShortMSB = (s, b) => {
|
|
|
9671
9686
|
s.pending_buf[s.pending++] = b >>> 8 & 255;
|
|
9672
9687
|
s.pending_buf[s.pending++] = b & 255;
|
|
9673
9688
|
};
|
|
9674
|
-
var read_buf = (strm, buf,
|
|
9689
|
+
var read_buf = (strm, buf, start2, size) => {
|
|
9675
9690
|
let len = strm.avail_in;
|
|
9676
9691
|
if (len > size) {
|
|
9677
9692
|
len = size;
|
|
@@ -9680,11 +9695,11 @@ var read_buf = (strm, buf, start, size) => {
|
|
|
9680
9695
|
return 0;
|
|
9681
9696
|
}
|
|
9682
9697
|
strm.avail_in -= len;
|
|
9683
|
-
buf.set(strm.input.subarray(strm.next_in, strm.next_in + len),
|
|
9698
|
+
buf.set(strm.input.subarray(strm.next_in, strm.next_in + len), start2);
|
|
9684
9699
|
if (strm.state.wrap === 1) {
|
|
9685
|
-
strm.adler = adler32_1(strm.adler, buf, len,
|
|
9700
|
+
strm.adler = adler32_1(strm.adler, buf, len, start2);
|
|
9686
9701
|
} else if (strm.state.wrap === 2) {
|
|
9687
|
-
strm.adler = crc32_1(strm.adler, buf, len,
|
|
9702
|
+
strm.adler = crc32_1(strm.adler, buf, len, start2);
|
|
9688
9703
|
}
|
|
9689
9704
|
strm.next_in += len;
|
|
9690
9705
|
strm.total_in += len;
|
|
@@ -11049,7 +11064,7 @@ var deflate_1$1 = {
|
|
|
11049
11064
|
};
|
|
11050
11065
|
var BAD$1 = 16209;
|
|
11051
11066
|
var TYPE$1 = 16191;
|
|
11052
|
-
var inffast = function inflate_fast(strm,
|
|
11067
|
+
var inffast = function inflate_fast(strm, start2) {
|
|
11053
11068
|
let _in;
|
|
11054
11069
|
let last;
|
|
11055
11070
|
let _out;
|
|
@@ -11079,7 +11094,7 @@ var inffast = function inflate_fast(strm, start) {
|
|
|
11079
11094
|
last = _in + (strm.avail_in - 5);
|
|
11080
11095
|
_out = strm.next_out;
|
|
11081
11096
|
output = strm.output;
|
|
11082
|
-
beg = _out - (
|
|
11097
|
+
beg = _out - (start2 - strm.avail_out);
|
|
11083
11098
|
end = _out + (strm.avail_out - 257);
|
|
11084
11099
|
dmax = state.dmax;
|
|
11085
11100
|
wsize = state.wsize;
|
|
@@ -16172,12 +16187,13 @@ var ID = /^[a-f0-9]{64}$/;
|
|
|
16172
16187
|
var ROLES = new Set(MESSAGE_ROLES);
|
|
16173
16188
|
var ROUTING_FIELDS = ["pairing", "sender", "recipient", "project", "thread"];
|
|
16174
16189
|
var MIME = /^[\w.+-]{1,64}\/[\w.+-]{1,64}$/;
|
|
16175
|
-
var TOOL_TEXT = 500;
|
|
16176
16190
|
function validAttachment(value2) {
|
|
16177
16191
|
return typeof value2 === "object" && value2 !== null && !Array.isArray(value2) && typeof value2.id === "string" && ID.test(value2.id) && typeof value2.mime === "string" && MIME.test(value2.mime) && Number.isSafeInteger(value2.bytes) && value2.bytes >= 0 && (value2.name === void 0 || typeof value2.name === "string" && value2.name.length <= ATTACHMENT_NAME_CHARS);
|
|
16178
16192
|
}
|
|
16179
16193
|
function validTool(value2) {
|
|
16180
|
-
return typeof value2 === "object" && value2 !== null && !Array.isArray(value2) && typeof value2.name === "string" && value2.name.length <=
|
|
16194
|
+
return typeof value2 === "object" && value2 !== null && !Array.isArray(value2) && typeof value2.name === "string" && value2.name.length <= HISTORY_TOOL_CHARS && [value2.label, value2.summary].every(
|
|
16195
|
+
(text3) => text3 === void 0 || typeof text3 === "string" && text3.length <= HISTORY_TOOL_CHARS
|
|
16196
|
+
) && (value2.at === void 0 || typeof value2.at === "string" && !Number.isNaN(Date.parse(value2.at))) && (value2.status === void 0 || value2.status === "ok" || value2.status === "error") && (value2.ms === void 0 || Number.isSafeInteger(value2.ms) && value2.ms >= 0) && (value2.diff === void 0 || typeof value2.diff === "boolean");
|
|
16181
16197
|
}
|
|
16182
16198
|
function validMessage(value2) {
|
|
16183
16199
|
return typeof value2 === "object" && value2 !== null && !Array.isArray(value2) && typeof value2.id === "string" && ID.test(value2.id) && ROLES.has(value2.role) && typeof value2.text === "string" && value2.text.length <= HISTORY_TEXT_CHARS && Number.isSafeInteger(value2.part) && Number.isSafeInteger(value2.parts) && value2.part >= 0 && value2.parts > value2.part && (value2.tool === void 0 || value2.part === 0 && validTool(value2.tool)) && (value2.full === void 0 || value2.part === 0 && validAttachment(value2.full)) && (value2.attachments === void 0 || value2.part === 0 && Array.isArray(value2.attachments) && value2.attachments.length <= HISTORY_ATTACHMENTS && value2.attachments.every(validAttachment));
|
|
@@ -16186,14 +16202,14 @@ var preview = (text3) => text3.split("\n", OUTPUT_PREVIEW.lines).join("\n").slic
|
|
|
16186
16202
|
function splitHistoryMessages(messages5, opaqueId2) {
|
|
16187
16203
|
return messages5.flatMap((message) => {
|
|
16188
16204
|
const text3 = [];
|
|
16189
|
-
let
|
|
16205
|
+
let start2 = 0;
|
|
16190
16206
|
do {
|
|
16191
|
-
let end = Math.min(
|
|
16207
|
+
let end = Math.min(start2 + HISTORY_TEXT_CHARS, message.text.length);
|
|
16192
16208
|
const code = message.text.charCodeAt(end - 1);
|
|
16193
16209
|
if (end < message.text.length && code >= 55296 && code <= 56319) end--;
|
|
16194
|
-
text3.push(message.text.slice(
|
|
16195
|
-
|
|
16196
|
-
} while (
|
|
16210
|
+
text3.push(message.text.slice(start2, end));
|
|
16211
|
+
start2 = end;
|
|
16212
|
+
} while (start2 < message.text.length);
|
|
16197
16213
|
const id = opaqueId2(message.id);
|
|
16198
16214
|
return text3.map((part, index) => ({
|
|
16199
16215
|
id,
|
|
@@ -16290,9 +16306,9 @@ async function publishHistory(config2, pair, project, thread, messages5) {
|
|
|
16290
16306
|
const generation = ++local.sequence;
|
|
16291
16307
|
saveConfig(config2);
|
|
16292
16308
|
const all = historyMessages(local.key, project, thread.id, messages5);
|
|
16293
|
-
let
|
|
16294
|
-
while (
|
|
16295
|
-
const fragments =
|
|
16309
|
+
let start2 = Math.max(0, all.length - HISTORY_KEPT);
|
|
16310
|
+
while (start2 < all.length && (all[start2].part ?? 0) > 0) start2++;
|
|
16311
|
+
const fragments = start2 ? [...historyMessages(local.key, project, thread.id, [EARLIER]), ...all.slice(start2)] : all;
|
|
16296
16312
|
const summary = { generation, total: fragments.length, updatedAt: thread.updatedAt };
|
|
16297
16313
|
const batch = {
|
|
16298
16314
|
pairing: pair.id,
|
|
@@ -16346,8 +16362,8 @@ function kept(message) {
|
|
|
16346
16362
|
files2.push(attached(`${message.id}@${at}`, mime, data));
|
|
16347
16363
|
return "";
|
|
16348
16364
|
});
|
|
16349
|
-
const
|
|
16350
|
-
const full =
|
|
16365
|
+
const start2 = message.role === "tool" && !message.tool?.diff ? preview(text3) : text3;
|
|
16366
|
+
const full = start2.length < text3.length ? {
|
|
16351
16367
|
id: `${message.id}:full`,
|
|
16352
16368
|
mime: "text/plain",
|
|
16353
16369
|
bytes: Buffer.byteLength(text3),
|
|
@@ -16355,7 +16371,7 @@ function kept(message) {
|
|
|
16355
16371
|
} : void 0;
|
|
16356
16372
|
return {
|
|
16357
16373
|
...message,
|
|
16358
|
-
text:
|
|
16374
|
+
text: start2,
|
|
16359
16375
|
...full ? { full } : {},
|
|
16360
16376
|
...files2.length ? { attachments: files2 } : {}
|
|
16361
16377
|
};
|
|
@@ -16494,6 +16510,8 @@ var Client = class {
|
|
|
16494
16510
|
pending = /* @__PURE__ */ new Map();
|
|
16495
16511
|
approvals = /* @__PURE__ */ new Map();
|
|
16496
16512
|
requests = /* @__PURE__ */ new Map();
|
|
16513
|
+
// The reply each thread is writing, as far as it has streamed, and the item it belongs to.
|
|
16514
|
+
typing = /* @__PURE__ */ new Map();
|
|
16497
16515
|
async connect() {
|
|
16498
16516
|
if (this.socket?.readyState === WebSocket2.OPEN) return;
|
|
16499
16517
|
if (this.connecting) return this.connecting;
|
|
@@ -16528,13 +16546,20 @@ var Client = class {
|
|
|
16528
16546
|
return;
|
|
16529
16547
|
}
|
|
16530
16548
|
if (typeof message.method !== "string") return;
|
|
16549
|
+
const params = message.params;
|
|
16550
|
+
if (message.method === "item/agentMessage/delta" && typeof params?.threadId === "string" && typeof params.delta === "string") {
|
|
16551
|
+
const known = this.typing.get(params.threadId);
|
|
16552
|
+
this.typing.set(params.threadId, {
|
|
16553
|
+
item: params.itemId,
|
|
16554
|
+
text: (known && known.item === params.itemId ? known.text : "") + params.delta
|
|
16555
|
+
});
|
|
16556
|
+
}
|
|
16531
16557
|
if (message.method === "serverRequest/resolved") {
|
|
16532
16558
|
for (const [id, pending] of this.approvals)
|
|
16533
16559
|
if (pending.requestId === message.params?.requestId) this.approvals.delete(id);
|
|
16534
16560
|
}
|
|
16535
16561
|
if ((message.method === "item/commandExecution/requestApproval" || message.method === "item/fileChange/requestApproval") && (typeof message.id === "string" || typeof message.id === "number") && typeof message.params?.threadId === "string") {
|
|
16536
16562
|
const id = randomUUID2();
|
|
16537
|
-
const params = message.params;
|
|
16538
16563
|
this.approvals.set(id, {
|
|
16539
16564
|
requestId: message.id,
|
|
16540
16565
|
threadId: params.threadId,
|
|
@@ -16644,6 +16669,7 @@ var Client = class {
|
|
|
16644
16669
|
updatedAt: new Date(Number(thread.updatedAt ?? 0) * 1e3).toISOString(),
|
|
16645
16670
|
status: approvals.length ? "attention" : status(thread),
|
|
16646
16671
|
messages: messages2(thread),
|
|
16672
|
+
typing: this.typing.get(id)?.text,
|
|
16647
16673
|
approvals,
|
|
16648
16674
|
// An interrupt for a finished turn is never answered, so the active turn is read first.
|
|
16649
16675
|
stop: this.controls ? (requestId) => this.once(requestId, async () => {
|
|
@@ -16766,17 +16792,17 @@ var NOTIFY_MS = 100;
|
|
|
16766
16792
|
function hunk(before, after, offset = 0) {
|
|
16767
16793
|
const old = before ? before.replace(/\n$/, "").split("\n") : [];
|
|
16768
16794
|
const next = after ? after.replace(/\n$/, "").split("\n") : [];
|
|
16769
|
-
let
|
|
16770
|
-
while (
|
|
16795
|
+
let start2 = 0;
|
|
16796
|
+
while (start2 < old.length && start2 < next.length && old[start2] === next[start2]) start2++;
|
|
16771
16797
|
let end = 0;
|
|
16772
|
-
while (end < old.length -
|
|
16798
|
+
while (end < old.length - start2 && end < next.length - start2 && old[old.length - 1 - end] === next[next.length - 1 - end])
|
|
16773
16799
|
end++;
|
|
16774
|
-
const removed = old.slice(
|
|
16775
|
-
const added2 = next.slice(
|
|
16800
|
+
const removed = old.slice(start2, old.length - end);
|
|
16801
|
+
const added2 = next.slice(start2, next.length - end);
|
|
16776
16802
|
return {
|
|
16777
16803
|
lines: [
|
|
16778
|
-
`@@ -${offset +
|
|
16779
|
-
...old.slice(Math.max(0,
|
|
16804
|
+
`@@ -${offset + start2 + 1},${removed.length} +${offset + start2 + 1},${added2.length} @@`,
|
|
16805
|
+
...old.slice(Math.max(0, start2 - 3), start2).map((line) => ` ${line}`),
|
|
16780
16806
|
...removed.map((line) => `-${line}`),
|
|
16781
16807
|
...added2.map((line) => `+${line}`),
|
|
16782
16808
|
...old.slice(old.length - end, old.length - end + 3).map((line) => ` ${line}`)
|
|
@@ -16851,12 +16877,21 @@ var Session = class {
|
|
|
16851
16877
|
turn;
|
|
16852
16878
|
// Messages written to this turn's process that Claude Code has not finished answering.
|
|
16853
16879
|
open = 0;
|
|
16880
|
+
// The text Claude Code is writing, as far as it has streamed.
|
|
16881
|
+
typing = "";
|
|
16854
16882
|
approvals = /* @__PURE__ */ new Map();
|
|
16855
16883
|
requests = /* @__PURE__ */ new Map();
|
|
16856
16884
|
write(value2) {
|
|
16857
16885
|
this.turn?.stdin.write(`${JSON.stringify(value2)}
|
|
16858
16886
|
`);
|
|
16859
16887
|
}
|
|
16888
|
+
interrupt() {
|
|
16889
|
+
this.write({
|
|
16890
|
+
type: "control_request",
|
|
16891
|
+
request_id: randomUUID3(),
|
|
16892
|
+
request: { subtype: "interrupt" }
|
|
16893
|
+
});
|
|
16894
|
+
}
|
|
16860
16895
|
once(id, action) {
|
|
16861
16896
|
const known = this.requests.get(id);
|
|
16862
16897
|
if (known) return known;
|
|
@@ -16866,13 +16901,16 @@ var Session = class {
|
|
|
16866
16901
|
this.requests.set(id, result);
|
|
16867
16902
|
return result;
|
|
16868
16903
|
}
|
|
16869
|
-
// Resolves once Claude Code takes the message, or says why it could not start.
|
|
16870
|
-
|
|
16904
|
+
// Resolves once Claude Code takes the message, or says why it could not start. `now` stops the
|
|
16905
|
+
// turn under way, so Claude Code takes the message at once.
|
|
16906
|
+
say(text3, now = false) {
|
|
16871
16907
|
this.messages.push({ id: randomUUID3(), role: "user", text: text3 });
|
|
16908
|
+
this.typing = "";
|
|
16872
16909
|
this.status = "working";
|
|
16873
16910
|
this.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
16874
16911
|
const message = { type: "user", message: { role: "user", content: text3 } };
|
|
16875
16912
|
if (this.turn) {
|
|
16913
|
+
if (now) this.interrupt();
|
|
16876
16914
|
this.open++;
|
|
16877
16915
|
this.write(message);
|
|
16878
16916
|
this.changed();
|
|
@@ -16887,6 +16925,7 @@ var Session = class {
|
|
|
16887
16925
|
"--output-format",
|
|
16888
16926
|
"stream-json",
|
|
16889
16927
|
"--verbose",
|
|
16928
|
+
"--include-partial-messages",
|
|
16890
16929
|
"--permission-prompt-tool",
|
|
16891
16930
|
"stdio",
|
|
16892
16931
|
...this.saved ? ["--resume", this.id] : ["--session-id", this.id]
|
|
@@ -16944,6 +16983,14 @@ var Session = class {
|
|
|
16944
16983
|
this.status = "attention";
|
|
16945
16984
|
} else if (message.type === "result") {
|
|
16946
16985
|
if (--this.open <= 0) this.turn?.stdin.end();
|
|
16986
|
+
} else if (message.type === "stream_event" && !message.parent_tool_use_id) {
|
|
16987
|
+
const event = message.event;
|
|
16988
|
+
if (event?.type === "content_block_start") this.typing = "";
|
|
16989
|
+
else if (event?.type === "content_block_delta" && event.delta?.type === "text_delta")
|
|
16990
|
+
this.typing += String(event.delta.text ?? "");
|
|
16991
|
+
else return false;
|
|
16992
|
+
this.changed();
|
|
16993
|
+
return false;
|
|
16947
16994
|
} else return false;
|
|
16948
16995
|
this.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
16949
16996
|
this.changed();
|
|
@@ -16958,15 +17005,13 @@ var Session = class {
|
|
|
16958
17005
|
updatedAt: this.updatedAt,
|
|
16959
17006
|
status: this.status,
|
|
16960
17007
|
messages: [...this.messages],
|
|
17008
|
+
typing: this.typing,
|
|
16961
17009
|
approvals: [...this.approvals.values()].map((pending) => pending.approval),
|
|
16962
|
-
send: (requestId, text3) => this.once(requestId, () => this.say(text3)),
|
|
17010
|
+
send: (requestId, text3, now) => this.once(requestId, () => this.say(text3, now)),
|
|
17011
|
+
now: true,
|
|
16963
17012
|
stop: this.turn ? (requestId) => this.once(requestId, async () => {
|
|
16964
17013
|
if (!this.turn) throw new Error("Nothing is running in this Claude Code session");
|
|
16965
|
-
this.
|
|
16966
|
-
type: "control_request",
|
|
16967
|
-
request_id: randomUUID3(),
|
|
16968
|
-
request: { subtype: "interrupt" }
|
|
16969
|
-
});
|
|
17014
|
+
this.interrupt();
|
|
16970
17015
|
}) : void 0,
|
|
16971
17016
|
approve: this.approvals.size ? (requestId, approvalId, choice) => this.once(requestId, async () => {
|
|
16972
17017
|
const pending = this.approvals.get(approvalId);
|
|
@@ -17040,15 +17085,13 @@ var Headless = class {
|
|
|
17040
17085
|
};
|
|
17041
17086
|
|
|
17042
17087
|
// src/harness/claude.ts
|
|
17043
|
-
import { join as join5 } from "node:path";
|
|
17044
|
-
import { connect as connect2 } from "node:net";
|
|
17088
|
+
import { dirname as dirname4, join as join5 } from "node:path";
|
|
17045
17089
|
import { watch as watch2 } from "node:fs";
|
|
17046
|
-
import {
|
|
17047
|
-
|
|
17090
|
+
import { mkdir, readdir as readdir2, readFile as readFile2, rm } from "node:fs/promises";
|
|
17091
|
+
import { createServer as createServer2 } from "node:http";
|
|
17048
17092
|
var MAX_SESSIONS2 = 100;
|
|
17049
|
-
var REQUEST_MS2 = 4e3;
|
|
17050
17093
|
var MAX_REQUESTS2 = 1e3;
|
|
17051
|
-
var
|
|
17094
|
+
var SESSION = /^\/sessions\/([\w-]{1,100})$/;
|
|
17052
17095
|
var STATUS = {
|
|
17053
17096
|
busy: "working",
|
|
17054
17097
|
idle: "idle",
|
|
@@ -17073,39 +17116,22 @@ async function registrations() {
|
|
|
17073
17116
|
);
|
|
17074
17117
|
return found.flat();
|
|
17075
17118
|
}
|
|
17076
|
-
async function post(session, path, text3) {
|
|
17077
|
-
const file = await lstat(path);
|
|
17078
|
-
if (!file.isSocket() || process.getuid && file.uid !== process.getuid())
|
|
17079
|
-
throw new Error("Claude Code inbox is not owned by this user");
|
|
17080
|
-
const body2 = text3.replace(new RegExp(`<(?=/?${ENVELOPE})`, "gi"), "<\\");
|
|
17081
|
-
const line = JSON.stringify({
|
|
17082
|
-
type: "user",
|
|
17083
|
-
session_id: session.sessionId,
|
|
17084
|
-
priority: "next",
|
|
17085
|
-
message: { role: "user", content: `<${ENVELOPE} from-name="Dashup">
|
|
17086
|
-
${body2}
|
|
17087
|
-
</${ENVELOPE}>` }
|
|
17088
|
-
});
|
|
17089
|
-
await new Promise((resolve3, reject) => {
|
|
17090
|
-
const socket = connect2(path);
|
|
17091
|
-
socket.setTimeout(REQUEST_MS2, () => socket.destroy(new Error("timed out")));
|
|
17092
|
-
socket.once("error", () => reject(new Error("Claude Code is not accepting messages")));
|
|
17093
|
-
socket.end(`${line}
|
|
17094
|
-
`, resolve3);
|
|
17095
|
-
});
|
|
17096
|
-
}
|
|
17097
17119
|
var Claude = class {
|
|
17098
|
-
constructor(changed) {
|
|
17120
|
+
constructor(changed, socket = join5(dirname4(configPath()), "claude.sock")) {
|
|
17099
17121
|
this.changed = changed;
|
|
17122
|
+
this.socket = socket;
|
|
17100
17123
|
}
|
|
17101
17124
|
changed;
|
|
17125
|
+
socket;
|
|
17102
17126
|
watcher;
|
|
17103
17127
|
notify;
|
|
17128
|
+
server;
|
|
17129
|
+
streams = /* @__PURE__ */ new Map();
|
|
17104
17130
|
requests = /* @__PURE__ */ new Map();
|
|
17105
17131
|
async refresh() {
|
|
17106
17132
|
this.watch();
|
|
17107
17133
|
return (await registrations()).map((session) => {
|
|
17108
|
-
const
|
|
17134
|
+
const plugin = this.streams.has(session.sessionId);
|
|
17109
17135
|
return {
|
|
17110
17136
|
id: session.sessionId,
|
|
17111
17137
|
harness: "claude",
|
|
@@ -17114,14 +17140,47 @@ var Claude = class {
|
|
|
17114
17140
|
status: STATUS[session.status ?? ""] ?? "unknown",
|
|
17115
17141
|
messages: [],
|
|
17116
17142
|
approvals: [],
|
|
17117
|
-
send:
|
|
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
|
|
17118
17146
|
};
|
|
17119
17147
|
});
|
|
17120
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.
|
|
17121
17179
|
once(id, action) {
|
|
17122
17180
|
const known = this.requests.get(id);
|
|
17123
17181
|
if (known) return known;
|
|
17124
|
-
if (this.requests.size >= MAX_REQUESTS2)
|
|
17182
|
+
if (this.requests.size >= MAX_REQUESTS2)
|
|
17183
|
+
this.requests.delete(this.requests.keys().next().value);
|
|
17125
17184
|
const result = action();
|
|
17126
17185
|
this.requests.set(id, result);
|
|
17127
17186
|
return result;
|
|
@@ -17148,23 +17207,26 @@ var Claude = class {
|
|
|
17148
17207
|
clearTimeout(this.notify);
|
|
17149
17208
|
this.watcher?.close();
|
|
17150
17209
|
this.watcher = void 0;
|
|
17210
|
+
this.server?.closeAllConnections();
|
|
17211
|
+
this.server?.close();
|
|
17212
|
+
this.server = void 0;
|
|
17151
17213
|
}
|
|
17152
17214
|
};
|
|
17153
17215
|
|
|
17154
17216
|
// src/harness/vscode.ts
|
|
17155
17217
|
import Socket3 from "ws";
|
|
17156
17218
|
import { homedir as homedir5 } from "node:os";
|
|
17157
|
-
import { connect as
|
|
17219
|
+
import { connect as connect2 } from "node:net";
|
|
17158
17220
|
import { promisify as promisify3 } from "node:util";
|
|
17159
17221
|
import { fileURLToPath } from "node:url";
|
|
17160
17222
|
import { execFile as execFile3 } from "node:child_process";
|
|
17161
17223
|
import { createHash as createHash3, randomUUID as randomUUID4 } from "node:crypto";
|
|
17162
17224
|
import { stat as stat2, readdir as readdir3, readFile as readFile3 } from "node:fs/promises";
|
|
17163
|
-
import { join as join6, dirname as
|
|
17225
|
+
import { join as join6, dirname as dirname5, basename as basename2, isAbsolute as isAbsolute3 } from "node:path";
|
|
17164
17226
|
import { WebSocketTransport } from "@microsoft/agent-host-protocol/ws";
|
|
17165
17227
|
import { AhpClient } from "@microsoft/agent-host-protocol/client";
|
|
17166
17228
|
import { chatReducer, sessionReducer, SUPPORTED_PROTOCOL_VERSIONS } from "@microsoft/agent-host-protocol";
|
|
17167
|
-
var
|
|
17229
|
+
var REQUEST_MS2 = 4e3;
|
|
17168
17230
|
var MAX_BYTES2 = 8 * 1024 * 1024;
|
|
17169
17231
|
var MAX_METADATA = 64 * 1024;
|
|
17170
17232
|
var MAX_HOSTS = 16;
|
|
@@ -17178,7 +17240,7 @@ var CONFIRMATION_CHOICES = [
|
|
|
17178
17240
|
function roots() {
|
|
17179
17241
|
const override = process.env.DASHUP_AHP_DATA ?? process.env.DASHUP_VSCODE_DATA;
|
|
17180
17242
|
if (override !== void 0)
|
|
17181
|
-
return override ? [basename2(override) === "User" ?
|
|
17243
|
+
return override ? [basename2(override) === "User" ? dirname5(override) : override] : [];
|
|
17182
17244
|
if (process.env.DASHUP_LIVE_DISCOVERY === "0") return [];
|
|
17183
17245
|
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");
|
|
17184
17246
|
return ["Code", "Code - Insiders"].map((name) => join6(base, name));
|
|
@@ -17198,7 +17260,7 @@ async function privateFile(path, limit) {
|
|
|
17198
17260
|
"-EncodedCommand",
|
|
17199
17261
|
Buffer.from(script, "utf16le").toString("base64")
|
|
17200
17262
|
],
|
|
17201
|
-
{ timeout:
|
|
17263
|
+
{ timeout: REQUEST_MS2, maxBuffer: MAX_METADATA }
|
|
17202
17264
|
);
|
|
17203
17265
|
return stdout.trim().toLowerCase() === "true";
|
|
17204
17266
|
} catch {
|
|
@@ -17239,7 +17301,7 @@ async function entries() {
|
|
|
17239
17301
|
if (!socket?.isSocket() || process.getuid && socket.uid !== process.getuid())
|
|
17240
17302
|
continue;
|
|
17241
17303
|
}
|
|
17242
|
-
if (!await privateFile(
|
|
17304
|
+
if (!await privateFile(dirname5(path), MAX_BYTES2)) continue;
|
|
17243
17305
|
const key = `${root}\0${identity2}`;
|
|
17244
17306
|
if (!result.has(key))
|
|
17245
17307
|
result.set(key, {
|
|
@@ -17403,9 +17465,9 @@ var Host = class {
|
|
|
17403
17465
|
if (this.connecting) return this.connecting;
|
|
17404
17466
|
this.connecting = (async () => {
|
|
17405
17467
|
const socket = new Socket3(`ws://localhost/?tkn=${encodeURIComponent(this.entry.token)}`, {
|
|
17406
|
-
createConnection: () =>
|
|
17468
|
+
createConnection: () => connect2(this.entry.path),
|
|
17407
17469
|
maxPayload: MAX_BYTES2,
|
|
17408
|
-
handshakeTimeout:
|
|
17470
|
+
handshakeTimeout: REQUEST_MS2
|
|
17409
17471
|
});
|
|
17410
17472
|
socket.on("error", () => {
|
|
17411
17473
|
});
|
|
@@ -17438,7 +17500,7 @@ var Host = class {
|
|
|
17438
17500
|
}
|
|
17439
17501
|
};
|
|
17440
17502
|
const client = this.client = new AhpClient(transport, {
|
|
17441
|
-
requestTimeoutMs:
|
|
17503
|
+
requestTimeoutMs: REQUEST_MS2,
|
|
17442
17504
|
subscriptionBuffer: 1024
|
|
17443
17505
|
});
|
|
17444
17506
|
this.resources.clear();
|
|
@@ -17502,7 +17564,7 @@ var Host = class {
|
|
|
17502
17564
|
const timer = setTimeout(() => {
|
|
17503
17565
|
this.receipts.delete(handle.clientSeq);
|
|
17504
17566
|
reject(new Error("Native AHP action was not acknowledged; delivery is unknown"));
|
|
17505
|
-
},
|
|
17567
|
+
}, REQUEST_MS2);
|
|
17506
17568
|
this.receipts.set(handle.clientSeq, { resolve: resolve3, reject, timer });
|
|
17507
17569
|
});
|
|
17508
17570
|
this.requests.set(requestId, pending);
|
|
@@ -17656,7 +17718,7 @@ import { createHash as createHash4 } from "node:crypto";
|
|
|
17656
17718
|
import { execFile as execFile4, spawn as spawn3 } from "node:child_process";
|
|
17657
17719
|
import { stat as stat3, readlink, readdir as readdir4, readFile as readFile4 } from "node:fs/promises";
|
|
17658
17720
|
var run2 = promisify4(execFile4);
|
|
17659
|
-
var
|
|
17721
|
+
var REQUEST_MS3 = 4e3;
|
|
17660
17722
|
var MAX_BYTES3 = 8 * 1024 * 1024;
|
|
17661
17723
|
var MAX_SESSIONS4 = 100;
|
|
17662
17724
|
var MAX_PROCESSES2 = 4096;
|
|
@@ -17738,7 +17800,7 @@ async function endpoints() {
|
|
|
17738
17800
|
const { stdout } = await run2(
|
|
17739
17801
|
"lsof",
|
|
17740
17802
|
["-nP", "-a", "-c", "opencode", "-iTCP", "-sTCP:LISTEN", "-Fn"],
|
|
17741
|
-
{ timeout:
|
|
17803
|
+
{ timeout: REQUEST_MS3, maxBuffer: MAX_BYTES3 }
|
|
17742
17804
|
);
|
|
17743
17805
|
return stdout.split("\n").flatMap((line) => {
|
|
17744
17806
|
const port = /^n(?:127\.0\.0\.1|\[::1\]|\*):(\d+)$/.exec(line)?.[1];
|
|
@@ -17755,7 +17817,7 @@ async function endpoints() {
|
|
|
17755
17817
|
const { stdout } = await run2(
|
|
17756
17818
|
"powershell.exe",
|
|
17757
17819
|
["-NoProfile", "-NonInteractive", "-Command", script],
|
|
17758
|
-
{ timeout:
|
|
17820
|
+
{ timeout: REQUEST_MS3, maxBuffer: MAX_BYTES3 }
|
|
17759
17821
|
);
|
|
17760
17822
|
return stdout.split(/\r?\n/).filter((port) => /^\d+$/.test(port)).slice(0, MAX_ENDPOINTS).map((port) => ({
|
|
17761
17823
|
url: localEndpoint(`http://127.0.0.1:${port}`),
|
|
@@ -17875,7 +17937,7 @@ var Server = class {
|
|
|
17875
17937
|
};
|
|
17876
17938
|
}
|
|
17877
17939
|
async request(path, directory2, payload) {
|
|
17878
|
-
const signal = AbortSignal.timeout(
|
|
17940
|
+
const signal = AbortSignal.timeout(REQUEST_MS3);
|
|
17879
17941
|
const response = await fetch(this.endpoint.url + path, {
|
|
17880
17942
|
method: payload === void 0 ? "GET" : "POST",
|
|
17881
17943
|
headers: {
|
|
@@ -18051,7 +18113,7 @@ var Server = class {
|
|
|
18051
18113
|
listen() {
|
|
18052
18114
|
if (this.stream || this.retry || this.stopped) return;
|
|
18053
18115
|
const controller = this.stream = new AbortController();
|
|
18054
|
-
const connectTimer = setTimeout(() => controller.abort(),
|
|
18116
|
+
const connectTimer = setTimeout(() => controller.abort(), REQUEST_MS3);
|
|
18055
18117
|
connectTimer.unref();
|
|
18056
18118
|
void (async () => {
|
|
18057
18119
|
const response = await fetch(this.endpoint.url + "/global/event", {
|
|
@@ -18273,6 +18335,10 @@ var LiveHarnesses = class {
|
|
|
18273
18335
|
}
|
|
18274
18336
|
watch(onChange) {
|
|
18275
18337
|
this.listeners.add(onChange);
|
|
18338
|
+
this.claude.listen().catch((error) => process.stderr.write(
|
|
18339
|
+
`Claude Code sessions cannot take replies: ${error instanceof Error ? error.message : error}
|
|
18340
|
+
`
|
|
18341
|
+
));
|
|
18276
18342
|
if (!this.timer && process.env.DASHUP_LIVE_DISCOVERY !== "0") {
|
|
18277
18343
|
this.timer = setInterval(() => this.changed(), DISCOVERY_MS);
|
|
18278
18344
|
this.timer.unref();
|
|
@@ -18301,10 +18367,10 @@ var LiveHarnesses = class {
|
|
|
18301
18367
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
18302
18368
|
import { spawn as spawn4 } from "node:child_process";
|
|
18303
18369
|
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
18304
|
-
import { join as join8, dirname as
|
|
18370
|
+
import { join as join8, dirname as dirname6, basename as basename3 } from "node:path";
|
|
18305
18371
|
import { statSync as statSync2, existsSync as existsSync4, readdirSync as readdirSync3, readFileSync as readFileSync4, readlinkSync, realpathSync as realpathSync2 } from "node:fs";
|
|
18306
18372
|
var CODEX_VERSION = "0.160.1";
|
|
18307
|
-
var
|
|
18373
|
+
var REQUEST_MS4 = 1e4;
|
|
18308
18374
|
var MAX_BYTES4 = 1024 * 1024;
|
|
18309
18375
|
var MAX_PROCESSES3 = 4096;
|
|
18310
18376
|
var MAX_FILES = 4096;
|
|
@@ -18320,8 +18386,8 @@ function sameFile(left, right) {
|
|
|
18320
18386
|
return left.path === right.path && left.device === right.device && left.inode === right.inode;
|
|
18321
18387
|
}
|
|
18322
18388
|
function packageVersion(executable) {
|
|
18323
|
-
let directory2 =
|
|
18324
|
-
for (let depth = 0; depth < 8; depth++, directory2 =
|
|
18389
|
+
let directory2 = dirname6(executable);
|
|
18390
|
+
for (let depth = 0; depth < 8; depth++, directory2 = dirname6(directory2)) {
|
|
18325
18391
|
const path = join8(directory2, "package.json");
|
|
18326
18392
|
if (!existsSync4(path)) continue;
|
|
18327
18393
|
const value2 = JSON.parse(readFileSync4(path, "utf8"));
|
|
@@ -18335,8 +18401,8 @@ function processOwners(pid) {
|
|
|
18335
18401
|
const executable = realpathSync2.native(join8(root, "exe"));
|
|
18336
18402
|
if (basename3(executable) !== "codex" || packageVersion(executable) !== CODEX_VERSION) return [];
|
|
18337
18403
|
const status2 = readFileSync4(join8(root, "stat"), "utf8");
|
|
18338
|
-
const
|
|
18339
|
-
if (!
|
|
18404
|
+
const start2 = status2.slice(status2.lastIndexOf(")") + 2).split(" ")[19];
|
|
18405
|
+
if (!start2 || !/^\d+$/.test(start2)) return [];
|
|
18340
18406
|
const fds = readdirSync3(join8(root, "fd")).slice(0, MAX_FILES);
|
|
18341
18407
|
const files2 = fds.flatMap((fd) => {
|
|
18342
18408
|
try {
|
|
@@ -18346,13 +18412,13 @@ function processOwners(pid) {
|
|
|
18346
18412
|
}
|
|
18347
18413
|
});
|
|
18348
18414
|
return files2.flatMap(({ fd, path }) => {
|
|
18349
|
-
if (basename3(
|
|
18415
|
+
if (basename3(dirname6(path)) !== "thread-writer-locks" || !path.endsWith(".lock")) return [];
|
|
18350
18416
|
const thread = basename3(path, ".lock");
|
|
18351
18417
|
if (!THREAD_ID.test(thread)) return [];
|
|
18352
18418
|
try {
|
|
18353
18419
|
const lock = readFileSync4(join8(root, "fdinfo", fd), "utf8");
|
|
18354
18420
|
if (!/FLOCK\s+ADVISORY\s+WRITE/.test(lock)) return [];
|
|
18355
|
-
const home = realpathSync2.native(
|
|
18421
|
+
const home = realpathSync2.native(dirname6(dirname6(path)));
|
|
18356
18422
|
const directory2 = statSync2(home);
|
|
18357
18423
|
if (process.getuid && directory2.uid !== process.getuid()) return [];
|
|
18358
18424
|
const queue = identity(join8(home, "queue_1.sqlite"));
|
|
@@ -18371,7 +18437,7 @@ function processOwners(pid) {
|
|
|
18371
18437
|
return [
|
|
18372
18438
|
{
|
|
18373
18439
|
pid,
|
|
18374
|
-
start,
|
|
18440
|
+
start: start2,
|
|
18375
18441
|
executable,
|
|
18376
18442
|
home,
|
|
18377
18443
|
thread,
|
|
@@ -18536,7 +18602,7 @@ var Writer = class {
|
|
|
18536
18602
|
this.pending.delete(id);
|
|
18537
18603
|
reject(new Error("Native queue acknowledgement was not received"));
|
|
18538
18604
|
this.close();
|
|
18539
|
-
},
|
|
18605
|
+
}, REQUEST_MS4);
|
|
18540
18606
|
this.pending.set(id, { resolve: resolve3, reject, timer });
|
|
18541
18607
|
this.process.stdin.write(JSON.stringify({ id, method, params }) + "\n");
|
|
18542
18608
|
});
|
|
@@ -18655,7 +18721,7 @@ var NativeQueue = class {
|
|
|
18655
18721
|
// src/connector.ts
|
|
18656
18722
|
var EPHEMERAL_TTL_MS = 6e4;
|
|
18657
18723
|
var HOME_RECENT = 20;
|
|
18658
|
-
var FOLLOW_MS =
|
|
18724
|
+
var FOLLOW_MS = 2 * 6e4;
|
|
18659
18725
|
var RESCAN_MS = 1e3;
|
|
18660
18726
|
var Command = z2.object({
|
|
18661
18727
|
type: z2.literal("command"),
|
|
@@ -18665,7 +18731,12 @@ var Command = z2.object({
|
|
|
18665
18731
|
action: z2.discriminatedUnion("kind", [
|
|
18666
18732
|
z2.object({ kind: z2.literal("projects.refresh") }).strict(),
|
|
18667
18733
|
z2.object({ kind: z2.literal("thread.read") }).strict(),
|
|
18668
|
-
z2.object({ kind: z2.literal("thread.
|
|
18734
|
+
z2.object({ kind: z2.literal("thread.follow") }).strict(),
|
|
18735
|
+
z2.object({
|
|
18736
|
+
kind: z2.literal("thread.send"),
|
|
18737
|
+
text: z2.string().min(1).max(1e5),
|
|
18738
|
+
now: z2.boolean().optional()
|
|
18739
|
+
}).strict(),
|
|
18669
18740
|
z2.object({ kind: z2.literal("thread.queue"), text: z2.string().min(1).max(1e5) }).strict(),
|
|
18670
18741
|
z2.object({ kind: z2.literal("thread.stop") }).strict(),
|
|
18671
18742
|
z2.object({ kind: z2.literal("approval.respond"), approvalId: z2.string(), choice: z2.string() }).strict(),
|
|
@@ -19059,6 +19130,27 @@ var Connector = class {
|
|
|
19059
19130
|
for (const pair of this.activePairs()) await this.listProjects(pair);
|
|
19060
19131
|
await this.publishQueueReceipts();
|
|
19061
19132
|
}
|
|
19133
|
+
// Publishes a conversation for the phone, which then gets what changes in it.
|
|
19134
|
+
async read(pair, project, thread) {
|
|
19135
|
+
const messages5 = this.discovery.read(thread, this.attached(thread)?.messages);
|
|
19136
|
+
const history = await publishHistory(this.config, pair, project.id, thread, messages5);
|
|
19137
|
+
this.follows.set(`${pair.id}:${thread.id}`, {
|
|
19138
|
+
pair,
|
|
19139
|
+
project: project.id,
|
|
19140
|
+
thread: thread.id,
|
|
19141
|
+
until: Date.now() + FOLLOW_MS,
|
|
19142
|
+
copy: copy(messages5),
|
|
19143
|
+
updatedAt: thread.updatedAt,
|
|
19144
|
+
typing: ""
|
|
19145
|
+
});
|
|
19146
|
+
return this.thread(pair, project, thread, history);
|
|
19147
|
+
}
|
|
19148
|
+
// The reply an agent this computer runs is still writing, until the conversation holds it.
|
|
19149
|
+
typing(thread, messages5) {
|
|
19150
|
+
const typing = this.discovery.locations.get(thread.id)?.live?.typing ?? "";
|
|
19151
|
+
const last = messages5.filter((message) => message.role === "assistant").at(-1);
|
|
19152
|
+
return typing && !last?.text.startsWith(typing) ? typing : "";
|
|
19153
|
+
}
|
|
19062
19154
|
// A phone reading a conversation gets its messages as they change, before anything that says the
|
|
19063
19155
|
// conversation changed, so it never asks for a copy it already has.
|
|
19064
19156
|
async stream() {
|
|
@@ -19067,18 +19159,19 @@ var Connector = class {
|
|
|
19067
19159
|
let messages5;
|
|
19068
19160
|
try {
|
|
19069
19161
|
if (thread && follow.until > Date.now() && activePair(this.config, follow.pair))
|
|
19070
|
-
messages5 =
|
|
19071
|
-
follow.copy,
|
|
19072
|
-
this.discovery.read(thread, this.attached(thread)?.messages)
|
|
19073
|
-
);
|
|
19162
|
+
messages5 = this.discovery.read(thread, this.attached(thread)?.messages);
|
|
19074
19163
|
} catch {
|
|
19075
19164
|
}
|
|
19076
19165
|
if (!thread || !messages5) {
|
|
19077
19166
|
this.follows.delete(key);
|
|
19078
19167
|
continue;
|
|
19079
19168
|
}
|
|
19080
|
-
|
|
19169
|
+
const changed = changes(follow.copy, messages5);
|
|
19170
|
+
const typing = this.typing(thread, messages5);
|
|
19171
|
+
if (!changed.length && thread.updatedAt === follow.updatedAt && typing === follow.typing)
|
|
19172
|
+
continue;
|
|
19081
19173
|
follow.updatedAt = thread.updatedAt;
|
|
19174
|
+
follow.typing = typing;
|
|
19082
19175
|
await this.send(follow.pair, {
|
|
19083
19176
|
type: "thread-tail",
|
|
19084
19177
|
projectId: follow.project,
|
|
@@ -19088,8 +19181,9 @@ var Connector = class {
|
|
|
19088
19181
|
this.config.pairs[follow.pair.id].key,
|
|
19089
19182
|
follow.project,
|
|
19090
19183
|
follow.thread,
|
|
19091
|
-
|
|
19092
|
-
)
|
|
19184
|
+
changed
|
|
19185
|
+
),
|
|
19186
|
+
...typing ? { typing } : {}
|
|
19093
19187
|
});
|
|
19094
19188
|
}
|
|
19095
19189
|
}
|
|
@@ -19100,7 +19194,7 @@ var Connector = class {
|
|
|
19100
19194
|
for (const thread of project.threads) {
|
|
19101
19195
|
const location2 = this.discovery.locations.get(thread.id);
|
|
19102
19196
|
const harness = thread.agent?.harness;
|
|
19103
|
-
if (location2?.history && !location2.live && !project.remote && !thread.capabilities.queue && harness && harness
|
|
19197
|
+
if (location2?.history && !location2.live && !project.remote && !thread.capabilities.queue && harness && isStartHarness(harness) && this.startable.includes(harness))
|
|
19104
19198
|
thread.capabilities = { ...thread.capabilities, send: true };
|
|
19105
19199
|
}
|
|
19106
19200
|
}
|
|
@@ -19164,6 +19258,7 @@ var Connector = class {
|
|
|
19164
19258
|
throw new Error("Command expired. Try again while this computer is online.");
|
|
19165
19259
|
const outcome = await this.command(pair, content);
|
|
19166
19260
|
delete this.config.unconfirmed[content.requestId];
|
|
19261
|
+
if (content.action.kind === "thread.follow") return;
|
|
19167
19262
|
if (outcome && "state" in outcome) {
|
|
19168
19263
|
queue = outcome;
|
|
19169
19264
|
await this.queueResult(pair, content.requestId, content.projectId, queue);
|
|
@@ -19177,6 +19272,7 @@ var Connector = class {
|
|
|
19177
19272
|
);
|
|
19178
19273
|
} catch (error) {
|
|
19179
19274
|
delete this.config.unconfirmed[content.requestId];
|
|
19275
|
+
if (content.action.kind === "thread.follow") return;
|
|
19180
19276
|
if (queue) {
|
|
19181
19277
|
await this.queueResult(pair, content.requestId, content.projectId, queue);
|
|
19182
19278
|
return;
|
|
@@ -19309,19 +19405,15 @@ var Connector = class {
|
|
|
19309
19405
|
if (!project) throw new Error("Project is no longer open on this computer");
|
|
19310
19406
|
const thread = project.threads.find((item) => item.id === command2.threadId);
|
|
19311
19407
|
switch (command2.action.kind) {
|
|
19312
|
-
case "thread.read":
|
|
19408
|
+
case "thread.read":
|
|
19313
19409
|
if (!thread) throw new Error("Thread not found");
|
|
19314
|
-
|
|
19315
|
-
|
|
19316
|
-
|
|
19317
|
-
|
|
19318
|
-
|
|
19319
|
-
|
|
19320
|
-
|
|
19321
|
-
copy: copy(messages5),
|
|
19322
|
-
updatedAt: thread.updatedAt
|
|
19323
|
-
});
|
|
19324
|
-
return this.thread(pair, project, thread, history);
|
|
19410
|
+
return this.read(pair, project, thread);
|
|
19411
|
+
case "thread.follow": {
|
|
19412
|
+
if (!thread) throw new Error("Thread not found");
|
|
19413
|
+
const follow = this.follows.get(`${pair.id}:${thread.id}`);
|
|
19414
|
+
if (!follow) return this.read(pair, project, thread);
|
|
19415
|
+
follow.until = Date.now() + FOLLOW_MS;
|
|
19416
|
+
return;
|
|
19325
19417
|
}
|
|
19326
19418
|
case "attachment.read": {
|
|
19327
19419
|
if (!thread) throw new Error("Thread not found");
|
|
@@ -19341,11 +19433,17 @@ var Connector = class {
|
|
|
19341
19433
|
"This session has no live reply connection. Use the existing agent on your computer."
|
|
19342
19434
|
);
|
|
19343
19435
|
const location2 = this.discovery.locations.get(thread.id);
|
|
19344
|
-
if (location2?.live?.send)
|
|
19436
|
+
if (location2?.live?.send)
|
|
19437
|
+
return location2.live.send(command2.requestId, command2.action.text, command2.action.now);
|
|
19345
19438
|
const agent = this.attached(thread);
|
|
19346
19439
|
const saved = thread.agent;
|
|
19347
|
-
if (!agent && location2?.history && saved && saved.harness
|
|
19348
|
-
await this.harnesses.resume(
|
|
19440
|
+
if (!agent && location2?.history && saved && isStartHarness(saved.harness)) {
|
|
19441
|
+
await this.harnesses.resume(
|
|
19442
|
+
saved.harness,
|
|
19443
|
+
saved.sessionId,
|
|
19444
|
+
project.path,
|
|
19445
|
+
command2.action.text
|
|
19446
|
+
);
|
|
19349
19447
|
await this.changed();
|
|
19350
19448
|
return;
|
|
19351
19449
|
}
|
|
@@ -19419,7 +19517,7 @@ var Connector = class {
|
|
|
19419
19517
|
if (!name || name === "." || name === ".." || /[\\/:*?"<>|\x00-\x1f]/.test(name))
|
|
19420
19518
|
throw new Error("Name the folder without slashes or special characters");
|
|
19421
19519
|
cwd = join9(folder.parent, name);
|
|
19422
|
-
await
|
|
19520
|
+
await mkdir2(cwd).catch((error) => {
|
|
19423
19521
|
throw new Error(error.code === "EEXIST" ? `${name} already exists` : `Couldn't create ${name}`);
|
|
19424
19522
|
});
|
|
19425
19523
|
} else {
|
|
@@ -19449,8 +19547,8 @@ import { homedir as homedir6, userInfo } from "node:os";
|
|
|
19449
19547
|
import { promisify as promisify6 } from "node:util";
|
|
19450
19548
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
19451
19549
|
import { execFile as execFile6, spawn as spawn5 } from "node:child_process";
|
|
19452
|
-
import { cp, mkdir as
|
|
19453
|
-
import { dirname as
|
|
19550
|
+
import { cp, mkdir as mkdir3, rm as rm2, writeFile } from "node:fs/promises";
|
|
19551
|
+
import { dirname as dirname7, join as join10, relative, sep } from "node:path";
|
|
19454
19552
|
var run3 = promisify6(execFile6);
|
|
19455
19553
|
var xml = (value2) => value2.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
19456
19554
|
var NODE_FLAGS = ["--disable-warning=ExperimentalWarning"];
|
|
@@ -19496,21 +19594,34 @@ async function durableEntry() {
|
|
|
19496
19594
|
);
|
|
19497
19595
|
if (!["_npx", "dlx"].some((cache2) => entry.includes(`${sep}${cache2}${sep}`))) return entry;
|
|
19498
19596
|
const root = entry.slice(0, entry.indexOf(`${sep}node_modules${sep}`));
|
|
19499
|
-
const app = join10(
|
|
19500
|
-
await
|
|
19597
|
+
const app = join10(dirname7(configPath()), "app");
|
|
19598
|
+
await rm2(app, { recursive: true, force: true });
|
|
19501
19599
|
await cp(join10(root, "node_modules"), join10(app, "node_modules"), {
|
|
19502
19600
|
recursive: true,
|
|
19503
19601
|
verbatimSymlinks: true
|
|
19504
19602
|
});
|
|
19505
19603
|
return join10(app, relative(root, entry));
|
|
19506
19604
|
}
|
|
19605
|
+
var claude2 = (...args2) => run3("claude", ["plugin", ...args2], { timeout: 6e4 });
|
|
19606
|
+
async function installClaude(entry) {
|
|
19607
|
+
if (process.platform === "win32") return false;
|
|
19608
|
+
try {
|
|
19609
|
+
await claude2("marketplace", "add", join10(dirname7(entry), "..", "claude"));
|
|
19610
|
+
await claude2("install", "dashup@dashup", "--scope", "user");
|
|
19611
|
+
return true;
|
|
19612
|
+
} catch {
|
|
19613
|
+
return false;
|
|
19614
|
+
}
|
|
19615
|
+
}
|
|
19507
19616
|
async function installStartup() {
|
|
19508
19617
|
const path = serviceFile();
|
|
19509
|
-
|
|
19510
|
-
await
|
|
19511
|
-
|
|
19512
|
-
|
|
19513
|
-
);
|
|
19618
|
+
const entry = await durableEntry();
|
|
19619
|
+
await mkdir3(dirname7(path), { recursive: true });
|
|
19620
|
+
await writeFile(path, service(process.platform, process.execPath, entry, configPath()));
|
|
19621
|
+
const restart = await installClaude(entry) ? "Restart Claude Code sessions that are already open to answer them from your phone." : void 0;
|
|
19622
|
+
return [await start(path), restart].filter(Boolean).join(" ") || void 0;
|
|
19623
|
+
}
|
|
19624
|
+
async function start(path) {
|
|
19514
19625
|
if (process.platform === "darwin") {
|
|
19515
19626
|
await run3("launchctl", ["unload", path]).catch(() => {
|
|
19516
19627
|
});
|
|
@@ -19530,13 +19641,17 @@ async function installStartup() {
|
|
|
19530
19641
|
}
|
|
19531
19642
|
async function uninstallStartup() {
|
|
19532
19643
|
const path = serviceFile();
|
|
19644
|
+
await claude2("uninstall", "dashup@dashup").catch(() => {
|
|
19645
|
+
});
|
|
19646
|
+
await claude2("marketplace", "remove", "dashup").catch(() => {
|
|
19647
|
+
});
|
|
19533
19648
|
if (process.platform === "darwin") await run3("launchctl", ["unload", path]).catch(() => {
|
|
19534
19649
|
});
|
|
19535
19650
|
else if (process.platform !== "win32")
|
|
19536
19651
|
await systemctl("disable", "dashup.service").catch(() => {
|
|
19537
19652
|
});
|
|
19538
|
-
await
|
|
19539
|
-
await
|
|
19653
|
+
await rm2(path, { force: true });
|
|
19654
|
+
await rm2(join10(dirname7(configPath()), "app"), { recursive: true, force: true });
|
|
19540
19655
|
}
|
|
19541
19656
|
|
|
19542
19657
|
// src/index.ts
|
|
@@ -19739,7 +19854,7 @@ async function main() {
|
|
|
19739
19854
|
"To pair this computer, run dashup pair in a terminal, or add --account with your Dashup email."
|
|
19740
19855
|
);
|
|
19741
19856
|
let connector;
|
|
19742
|
-
const server =
|
|
19857
|
+
const server = createServer3(async (request2, response) => {
|
|
19743
19858
|
if (!authorized(request2)) {
|
|
19744
19859
|
response.writeHead(403).end();
|
|
19745
19860
|
return;
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dashup/connector",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"dashup": "dist/index.js"
|
|
7
7
|
},
|
|
8
8
|
"files": [
|
|
9
|
-
"dist"
|
|
9
|
+
"dist",
|
|
10
|
+
"claude"
|
|
10
11
|
],
|
|
11
12
|
"publishConfig": {
|
|
12
13
|
"access": "public"
|
|
@@ -29,11 +30,11 @@
|
|
|
29
30
|
"tsup": "^8.5.1",
|
|
30
31
|
"tsx": "^4.22.4",
|
|
31
32
|
"typescript": "^5.9.3",
|
|
32
|
-
"@dashup/
|
|
33
|
-
"@dashup/protocol": "0.1.0",
|
|
33
|
+
"@dashup/models": "0.1.0",
|
|
34
34
|
"@parcae/lynx": "0.1.0",
|
|
35
35
|
"@parcae/sdk": "0.8.1",
|
|
36
|
-
"@dashup/
|
|
36
|
+
"@dashup/typescript-config": "0.0.0",
|
|
37
|
+
"@dashup/protocol": "0.1.0"
|
|
37
38
|
},
|
|
38
39
|
"scripts": {
|
|
39
40
|
"dashup": "tsx src/index.ts",
|