@dashup/connector 0.1.14 → 0.1.16
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 +4 -4
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/hooks/register.ts +77 -0
- package/dist/index.js +357 -191
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -34,10 +34,10 @@ Each history read stages a new encrypted database snapshot and publishes it only
|
|
|
34
34
|
|
|
35
35
|
Dashup discovers existing local OpenCode listeners, Codex control sockets, and VS Code Agent Host endpoints. These connections remain local; their tokens are never sent to Parcae or the phone. Native endpoints are checked for local ownership or loopback access, and redirects and remote endpoint URLs are rejected.
|
|
36
36
|
|
|
37
|
-
- **OpenCode:** the existing TUI/server supplies conversations, live events, prompts, native tool permissions, and
|
|
37
|
+
- **OpenCode:** the existing TUI/server supplies conversations, live events, prompts, native tool permissions, and questions, which the phone answers or skips. 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. `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.
|
|
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. The plugin also puts each permission request the session waits on to the phone: a tool to allow once or under the rule Claude Code suggests, questions, or a plan. The dialog on the computer, or the editor hosting the session, stays open, and the first answer wins. Claude Code lets only the person there or its host approve a plan, so the phone can only send a running session's plan back with what to change. 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
|
-
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
|
|
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
51
|
|
|
52
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.
|
|
53
53
|
|
|
@@ -55,7 +55,7 @@ Live discovery is bounded to 100 sessions per endpoint. OpenCode supplies the la
|
|
|
55
55
|
|
|
56
56
|
## Starting sessions from the phone
|
|
57
57
|
|
|
58
|
-
The phone starts a session in a local project, or in a new folder inside one of the folders that already hold this computer's projects, with Claude Code, Codex or OpenCode: whichever of `claude`, `codex` and `opencode` runs here. The agent works with its own settings and permission rules, and asks the phone before anything they don't allow. Claude Code runs headless (`claude -p`) for each turn and resumes the session for the next; Codex threads start in Codex's own app-server daemon (`codex app-server daemon start`) and outlive the connector; OpenCode sessions run on an `opencode serve` the connector starts for that folder. Claude Code's permission requests arrive with the command, or the diff an edit or write would make.
|
|
58
|
+
The phone starts a session in a local project, or in a new folder inside one of the folders that already hold this computer's projects, with Claude Code, Codex or OpenCode: whichever of `claude`, `codex` and `opencode` runs here. The agent works with its own settings and permission rules, and asks the phone before anything they don't allow. Claude Code runs headless (`claude -p`) for each turn and resumes the session for the next; Codex threads start in Codex's own app-server daemon (`codex app-server daemon start`) and outlive the connector; OpenCode sessions run on an `opencode serve` the connector starts for that folder. Claude Code's permission requests arrive with the command, or the diff an edit or write would make, and the rule that would stop it asking again; the phone answers its questions and plans as Claude Code's host.
|
|
59
59
|
|
|
60
60
|
A saved conversation is answered the same way when its agent runs here, its project is local, and nothing else has it open: Claude Code resumes it (`claude -p --resume`), Codex loads it in its daemon (`thread/resume`), and OpenCode serves its folder. One a native Codex process owns takes queued messages instead. A start expires with other commands after 60 seconds.
|
|
61
61
|
|
package/claude/hooks/register.ts
CHANGED
|
@@ -13,9 +13,85 @@ type Delivery = { text?: string; now?: true };
|
|
|
13
13
|
|
|
14
14
|
const RETRY_MS = 10_000;
|
|
15
15
|
|
|
16
|
+
// =============================================================================
|
|
17
|
+
// Permission requests
|
|
18
|
+
// =============================================================================
|
|
19
|
+
|
|
20
|
+
// The connector holds each request this session waits on open until the phone answers it. The
|
|
21
|
+
// dialog here, the editor hosting the session or the person's own hooks may answer first. No hook
|
|
22
|
+
// hears the dialog's answer, so the call it settles closes the request: both hooks see the call,
|
|
23
|
+
// with its fields in different orders.
|
|
24
|
+
function call(agent: string | undefined, tool: string, input: unknown): string {
|
|
25
|
+
const sorted = (value: unknown): unknown =>
|
|
26
|
+
Array.isArray(value)
|
|
27
|
+
? value.map(sorted)
|
|
28
|
+
: value && typeof value === 'object'
|
|
29
|
+
? Object.fromEntries(
|
|
30
|
+
Object.keys(value)
|
|
31
|
+
.sort()
|
|
32
|
+
.map((key) => [key, sorted((value as Record<string, unknown>)[key])]),
|
|
33
|
+
)
|
|
34
|
+
: value;
|
|
35
|
+
return JSON.stringify([agent ?? '', tool, sorted(input)]);
|
|
36
|
+
}
|
|
37
|
+
|
|
16
38
|
export const register: Register = (on) => {
|
|
17
39
|
let turnId: string | undefined;
|
|
18
40
|
let stream: AsyncGenerator<unknown, unknown> | undefined;
|
|
41
|
+
// The requests the phone is asked, by the call each waits on.
|
|
42
|
+
const asks = new Map<string, AsyncGenerator<unknown, unknown>>();
|
|
43
|
+
on('classic.PermissionRequest', async ($, e, next) => {
|
|
44
|
+
const key = call(e.agent_id, e.tool_name, e.tool_input);
|
|
45
|
+
const ask = $.process.spawn({
|
|
46
|
+
argv: [
|
|
47
|
+
'curl',
|
|
48
|
+
'-sfN',
|
|
49
|
+
'--unix-socket',
|
|
50
|
+
`${await $.env.get('HOME')}/.dashup/claude.sock`,
|
|
51
|
+
'--data-binary',
|
|
52
|
+
'@-',
|
|
53
|
+
`http://dashup/sessions/${await $.session.id()}/prompts`,
|
|
54
|
+
],
|
|
55
|
+
input: JSON.stringify({
|
|
56
|
+
tool : e.tool_name,
|
|
57
|
+
input : e.tool_input,
|
|
58
|
+
suggestions : e.permission_suggestions,
|
|
59
|
+
}),
|
|
60
|
+
});
|
|
61
|
+
const open: AsyncGenerator<unknown, unknown> = ask;
|
|
62
|
+
asks.set(key, open);
|
|
63
|
+
const beneath = next(e).then((result) => {
|
|
64
|
+
if (result.decision) void open.return(undefined);
|
|
65
|
+
return result;
|
|
66
|
+
});
|
|
67
|
+
let answer = '';
|
|
68
|
+
try {
|
|
69
|
+
for await (const { stream: name, text } of ask) if (name === 'stdout') answer += text;
|
|
70
|
+
} catch {
|
|
71
|
+
// curl is missing.
|
|
72
|
+
} finally {
|
|
73
|
+
if (asks.get(key) === open) asks.delete(key);
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
return answer ? { decision: JSON.parse(answer) } : beneath;
|
|
77
|
+
} catch {
|
|
78
|
+
// The connector stopped mid-answer.
|
|
79
|
+
return beneath;
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
on('tool.call', async ($, e, next) => {
|
|
83
|
+
try {
|
|
84
|
+
return await next(e);
|
|
85
|
+
} finally {
|
|
86
|
+
if (asks.size) {
|
|
87
|
+
const { tool, tool_use_id, consent, agentId, requestMeta, ...input } = e as Record<
|
|
88
|
+
string,
|
|
89
|
+
unknown
|
|
90
|
+
>;
|
|
91
|
+
void asks.get(call(agentId as string | undefined, String(tool), input))?.return(undefined);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
});
|
|
19
95
|
on('turn.start', ($, e, next) => {
|
|
20
96
|
if (e.agentId === undefined) turnId = e.turnId;
|
|
21
97
|
return next(e);
|
|
@@ -23,6 +99,7 @@ export const register: Register = (on) => {
|
|
|
23
99
|
// `/clear` starts another conversation in this process, which streams under its own id.
|
|
24
100
|
on('session.end', ($, e, next) => {
|
|
25
101
|
void stream?.return(undefined);
|
|
102
|
+
for (const ask of asks.values()) void ask.return(undefined);
|
|
26
103
|
return next(e);
|
|
27
104
|
});
|
|
28
105
|
on('session.start', async ($, e, next) => {
|
package/dist/index.js
CHANGED
|
@@ -3802,7 +3802,7 @@ function bridgeAcp(socket, onUpdate) {
|
|
|
3802
3802
|
if (session && direction === "agent" && message.method === methods.client.session.requestPermission) {
|
|
3803
3803
|
const tool = message.params.toolCall ?? {};
|
|
3804
3804
|
const diff = (tool.content ?? []).find((part) => part.type === "diff");
|
|
3805
|
-
const
|
|
3805
|
+
const approval2 = {
|
|
3806
3806
|
id: randomUUID(),
|
|
3807
3807
|
title: String(message.params.title ?? tool.title ?? "Agent needs approval"),
|
|
3808
3808
|
detail: String(message.params.description ?? ""),
|
|
@@ -3820,10 +3820,10 @@ ${String(diff.newText).split("\n").map((line) => "+" + line).join("\n")}`
|
|
|
3820
3820
|
}
|
|
3821
3821
|
} : {}
|
|
3822
3822
|
};
|
|
3823
|
-
session.approvals.set(String(message.id),
|
|
3823
|
+
session.approvals.set(String(message.id), approval2);
|
|
3824
3824
|
permissions.set(String(message.id), session);
|
|
3825
3825
|
session.status = "attention";
|
|
3826
|
-
publish(session,
|
|
3826
|
+
publish(session, approval2);
|
|
3827
3827
|
}
|
|
3828
3828
|
if (direction === "editor" && !message.method) {
|
|
3829
3829
|
const pending = permissions.get(String(message.id));
|
|
@@ -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.16";
|
|
4052
4052
|
|
|
4053
4053
|
// src/pair.ts
|
|
4054
4054
|
var Invitation = z.object({
|
|
@@ -4215,6 +4215,24 @@ var attached = (id, mime, data, name) => ({
|
|
|
4215
4215
|
});
|
|
4216
4216
|
var MAX_PREVIEW = 64 * 1024;
|
|
4217
4217
|
var NO_PREVIEW = "Review the full request on your computer before approving.";
|
|
4218
|
+
function questionApproval(id, items, multiple) {
|
|
4219
|
+
const asked = items.map((item) => ({
|
|
4220
|
+
...typeof item?.header === "string" ? { header: item.header } : {},
|
|
4221
|
+
question: String(item?.question ?? ""),
|
|
4222
|
+
options: (Array.isArray(item?.options) ? item.options : []).filter((option2) => typeof option2?.label === "string").map((option2) => ({
|
|
4223
|
+
label: String(option2.label),
|
|
4224
|
+
...typeof option2.description === "string" ? { description: option2.description } : {}
|
|
4225
|
+
})),
|
|
4226
|
+
...multiple(item) ? { multiple: true } : {}
|
|
4227
|
+
}));
|
|
4228
|
+
return {
|
|
4229
|
+
id,
|
|
4230
|
+
title: asked.length === 1 ? asked[0].question : `${asked.length} questions`,
|
|
4231
|
+
detail: "",
|
|
4232
|
+
choices: [{ id: "skip", label: "Skip" }],
|
|
4233
|
+
questions: asked
|
|
4234
|
+
};
|
|
4235
|
+
}
|
|
4218
4236
|
|
|
4219
4237
|
// src/harness/history.ts
|
|
4220
4238
|
var SAMPLE_BYTES = 128 * 1024;
|
|
@@ -13246,22 +13264,22 @@ var FrontendAdapter = class {
|
|
|
13246
13264
|
if (options.subscribe === false) requestData.__subscribe = false;
|
|
13247
13265
|
const result = await this.transport.get(path, requestData);
|
|
13248
13266
|
const items = result?.[(0, import_pluralize.default)(modelClass.type)] ?? result?.[modelClass.type + "s"] ?? result?.items ?? [];
|
|
13249
|
-
const
|
|
13267
|
+
const models = items.map(
|
|
13250
13268
|
(row) => modelClass.hydrate(this, row)
|
|
13251
13269
|
);
|
|
13252
13270
|
if (result?.__queryHash) {
|
|
13253
|
-
Object.defineProperty(
|
|
13271
|
+
Object.defineProperty(models, "__queryHash", {
|
|
13254
13272
|
value: result.__queryHash,
|
|
13255
13273
|
enumerable: false
|
|
13256
13274
|
});
|
|
13257
13275
|
}
|
|
13258
13276
|
if (typeof result?.totalCount === "number") {
|
|
13259
|
-
Object.defineProperty(
|
|
13277
|
+
Object.defineProperty(models, "__totalCount", {
|
|
13260
13278
|
value: result.totalCount,
|
|
13261
13279
|
enumerable: false
|
|
13262
13280
|
});
|
|
13263
13281
|
}
|
|
13264
|
-
return
|
|
13282
|
+
return models;
|
|
13265
13283
|
};
|
|
13266
13284
|
chain.first = async () => {
|
|
13267
13285
|
const all = await chain.find();
|
|
@@ -16728,13 +16746,14 @@ var Client = class {
|
|
|
16728
16746
|
});
|
|
16729
16747
|
this.changed();
|
|
16730
16748
|
}) : void 0,
|
|
16731
|
-
approve: approvals.length ? (requestId, approvalId,
|
|
16732
|
-
const
|
|
16749
|
+
approve: approvals.length ? (requestId, approvalId, reply) => this.once(requestId, async () => {
|
|
16750
|
+
const approval2 = this.approvals.get(approvalId);
|
|
16751
|
+
const choice = "choice" in reply ? reply.choice : void 0;
|
|
16733
16752
|
const socket = this.socket;
|
|
16734
|
-
if (!
|
|
16753
|
+
if (!approval2 || approval2.threadId !== id || !approval2.approval.choices.some((value2) => value2.id === choice) || socket?.readyState !== WebSocket2.OPEN)
|
|
16735
16754
|
throw new Error("Codex approval is no longer pending");
|
|
16736
16755
|
await new Promise((resolve3, reject) => socket.send(
|
|
16737
|
-
JSON.stringify({ id:
|
|
16756
|
+
JSON.stringify({ id: approval2.requestId, result: { decision: choice } }),
|
|
16738
16757
|
(error) => error ? reject(new Error("Codex approval could not be sent")) : resolve3()
|
|
16739
16758
|
));
|
|
16740
16759
|
this.approvals.delete(approvalId);
|
|
@@ -16810,18 +16829,18 @@ var Codex = class {
|
|
|
16810
16829
|
};
|
|
16811
16830
|
|
|
16812
16831
|
// src/harness/headless.ts
|
|
16813
|
-
import { randomUUID as
|
|
16814
|
-
import { readFileSync as readFileSync3 } from "node:fs";
|
|
16832
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
16815
16833
|
import { createInterface as createInterface3 } from "node:readline";
|
|
16816
16834
|
import { spawn as spawn3 } from "node:child_process";
|
|
16817
16835
|
|
|
16818
16836
|
// src/harness/claude.ts
|
|
16819
16837
|
import { homedir as homedir5 } from "node:os";
|
|
16820
|
-
import {
|
|
16838
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
16839
|
+
import { dirname as dirname4, join as join5, basename as basename2 } from "node:path";
|
|
16821
16840
|
import { spawn as spawn2 } from "node:child_process";
|
|
16822
16841
|
import { createInterface as createInterface2 } from "node:readline";
|
|
16823
|
-
import { watch as watch2 } from "node:fs";
|
|
16824
|
-
import { mkdir, readdir as readdir2, readFile as readFile2, rm } from "node:fs/promises";
|
|
16842
|
+
import { watch as watch2, readFileSync as readFileSync3 } from "node:fs";
|
|
16843
|
+
import { mkdir, readdir as readdir2, readFile as readFile2, readlink, rm } from "node:fs/promises";
|
|
16825
16844
|
import { createServer as createServer2 } from "node:http";
|
|
16826
16845
|
var MODELS_MS = 3e4;
|
|
16827
16846
|
var COMMANDS = {
|
|
@@ -16829,32 +16848,48 @@ var COMMANDS = {
|
|
|
16829
16848
|
fast: (value2) => `/fast ${value2 ? "on" : "off"}`,
|
|
16830
16849
|
ultracode: (value2) => `/effort ultracode ${value2 ? "on" : "off"}`
|
|
16831
16850
|
};
|
|
16832
|
-
var
|
|
16833
|
-
|
|
16851
|
+
var offered = /* @__PURE__ */ new Map();
|
|
16852
|
+
var asking = /* @__PURE__ */ new Set();
|
|
16853
|
+
var forgetModels = () => offered.clear();
|
|
16854
|
+
function claudeModels(changed, command2 = "claude", version2 = "") {
|
|
16855
|
+
const key = `${command2}\0${version2}`;
|
|
16856
|
+
if (!offered.has(key) && !asking.has(key)) {
|
|
16857
|
+
asking.add(key);
|
|
16858
|
+
void listModels(command2).then((models) => {
|
|
16859
|
+
asking.delete(key);
|
|
16860
|
+
if (!models.length) return;
|
|
16861
|
+
offered.set(key, models);
|
|
16862
|
+
changed();
|
|
16863
|
+
});
|
|
16864
|
+
}
|
|
16865
|
+
return offered.get(key) ?? [];
|
|
16866
|
+
}
|
|
16867
|
+
function listModels(command2) {
|
|
16834
16868
|
return new Promise((resolve3) => {
|
|
16835
16869
|
const child = spawn2(
|
|
16836
|
-
|
|
16870
|
+
command2,
|
|
16837
16871
|
["-p", "--input-format", "stream-json", "--output-format", "stream-json", "--verbose"],
|
|
16838
16872
|
{ cwd: homedir5(), stdio: "pipe", shell: process.platform === "win32" }
|
|
16839
16873
|
);
|
|
16840
16874
|
const timer = setTimeout(() => child.kill(), MODELS_MS);
|
|
16841
16875
|
child.stdin.on("error", () => {
|
|
16842
16876
|
});
|
|
16843
|
-
child.once("error", () => resolve3());
|
|
16877
|
+
child.once("error", () => resolve3([]));
|
|
16844
16878
|
child.once("exit", () => {
|
|
16845
16879
|
clearTimeout(timer);
|
|
16846
|
-
resolve3();
|
|
16880
|
+
resolve3([]);
|
|
16847
16881
|
});
|
|
16848
16882
|
createInterface2({ input: child.stdout }).on("line", (line) => {
|
|
16849
16883
|
try {
|
|
16850
16884
|
const message = JSON.parse(line);
|
|
16851
16885
|
if (message?.type !== "control_response" || message.response?.request_id !== "models")
|
|
16852
16886
|
return;
|
|
16853
|
-
const
|
|
16854
|
-
|
|
16855
|
-
models
|
|
16887
|
+
const models = message.response.response?.models;
|
|
16888
|
+
resolve3(
|
|
16889
|
+
(Array.isArray(models) ? models : []).filter(
|
|
16856
16890
|
(model) => typeof model?.value === "string" && typeof model.displayName === "string"
|
|
16857
|
-
)
|
|
16891
|
+
)
|
|
16892
|
+
);
|
|
16858
16893
|
child.stdin.end();
|
|
16859
16894
|
} catch {
|
|
16860
16895
|
}
|
|
@@ -16865,7 +16900,7 @@ function askModels() {
|
|
|
16865
16900
|
);
|
|
16866
16901
|
});
|
|
16867
16902
|
}
|
|
16868
|
-
function claudeOptions(recorded) {
|
|
16903
|
+
function claudeOptions(models, recorded) {
|
|
16869
16904
|
const ran = typeof recorded.model === "string" ? recorded.model : "";
|
|
16870
16905
|
const current = models.find((model) => model.value !== "default" && model.resolvedModel === ran) ?? models.find((model) => model.value === ran);
|
|
16871
16906
|
const choices = models.map((model) => ({ value: model.value, name: model.displayName }));
|
|
@@ -16877,9 +16912,148 @@ function claudeOptions(recorded) {
|
|
|
16877
16912
|
...current?.supportedEffortLevels?.includes("xhigh") ? [{ id: "ultracode", name: "Ultracode", value: recorded.ultracode === true }] : []
|
|
16878
16913
|
];
|
|
16879
16914
|
}
|
|
16915
|
+
var EDITS = /* @__PURE__ */ new Set(["Edit", "MultiEdit", "NotebookEdit", "Write"]);
|
|
16916
|
+
var SCOPE = 32;
|
|
16917
|
+
var preview2 = (text3) => Buffer.byteLength(text3) > MAX_PREVIEW ? Buffer.from(text3).subarray(0, MAX_PREVIEW).toString("utf8") : text3;
|
|
16918
|
+
function hunk(before, after, offset = 0) {
|
|
16919
|
+
const old = before ? before.replace(/\n$/, "").split("\n") : [];
|
|
16920
|
+
const next = after ? after.replace(/\n$/, "").split("\n") : [];
|
|
16921
|
+
let start2 = 0;
|
|
16922
|
+
while (start2 < old.length && start2 < next.length && old[start2] === next[start2]) start2++;
|
|
16923
|
+
let end = 0;
|
|
16924
|
+
while (end < old.length - start2 && end < next.length - start2 && old[old.length - 1 - end] === next[next.length - 1 - end])
|
|
16925
|
+
end++;
|
|
16926
|
+
const removed = old.slice(start2, old.length - end);
|
|
16927
|
+
const added2 = next.slice(start2, next.length - end);
|
|
16928
|
+
return {
|
|
16929
|
+
lines: [
|
|
16930
|
+
`@@ -${offset + start2 + 1},${removed.length} +${offset + start2 + 1},${added2.length} @@`,
|
|
16931
|
+
...old.slice(Math.max(0, start2 - 3), start2).map((line) => ` ${line}`),
|
|
16932
|
+
...removed.map((line) => `-${line}`),
|
|
16933
|
+
...added2.map((line) => `+${line}`),
|
|
16934
|
+
...old.slice(old.length - end, old.length - end + 3).map((line) => ` ${line}`)
|
|
16935
|
+
],
|
|
16936
|
+
added: added2.length,
|
|
16937
|
+
deleted: removed.length
|
|
16938
|
+
};
|
|
16939
|
+
}
|
|
16940
|
+
function change(tool, input) {
|
|
16941
|
+
const path = input.file_path;
|
|
16942
|
+
if (typeof path !== "string") return void 0;
|
|
16943
|
+
let current = "";
|
|
16944
|
+
try {
|
|
16945
|
+
current = readFileSync3(path, "utf8");
|
|
16946
|
+
} catch {
|
|
16947
|
+
}
|
|
16948
|
+
const edit = (value2) => {
|
|
16949
|
+
const before = String(value2?.old_string ?? "");
|
|
16950
|
+
const at = before ? current.indexOf(before) : -1;
|
|
16951
|
+
return hunk(
|
|
16952
|
+
before,
|
|
16953
|
+
String(value2?.new_string ?? ""),
|
|
16954
|
+
at < 0 ? 0 : current.slice(0, at).split("\n").length - 1
|
|
16955
|
+
);
|
|
16956
|
+
};
|
|
16957
|
+
const hunks = tool === "Write" && typeof input.content === "string" ? [hunk(current, input.content)] : tool === "Edit" ? [edit(input)] : tool === "MultiEdit" && Array.isArray(input.edits) ? input.edits.map(edit) : [];
|
|
16958
|
+
if (!hunks.length) return void 0;
|
|
16959
|
+
const diff = [`--- ${path}`, `+++ ${path}`, ...hunks.flatMap((item) => item.lines)].join("\n");
|
|
16960
|
+
if (Buffer.byteLength(diff) > MAX_PREVIEW) return void 0;
|
|
16961
|
+
return {
|
|
16962
|
+
path,
|
|
16963
|
+
diff,
|
|
16964
|
+
added: hunks.reduce((sum, item) => sum + item.added, 0),
|
|
16965
|
+
deleted: hunks.reduce((sum, item) => sum + item.deleted, 0)
|
|
16966
|
+
};
|
|
16967
|
+
}
|
|
16968
|
+
function remember(suggestions = []) {
|
|
16969
|
+
const updates = suggestions.filter(
|
|
16970
|
+
(update) => update?.type === "addRules" && update.behavior === "allow" || update?.type === "addDirectories"
|
|
16971
|
+
);
|
|
16972
|
+
const rule = updates.flatMap((update) => update.rules ?? [])[0];
|
|
16973
|
+
const folder = updates.flatMap((update) => update.directories ?? [])[0];
|
|
16974
|
+
const scope2 = rule ? String(rule.ruleContent ?? rule.toolName) : folder ? `access to ${basename2(String(folder))}` : "";
|
|
16975
|
+
if (scope2)
|
|
16976
|
+
return {
|
|
16977
|
+
label: `Always allow ${scope2.length > SCOPE ? `${scope2.slice(0, SCOPE - 1)}\u2026` : scope2}`,
|
|
16978
|
+
updates
|
|
16979
|
+
};
|
|
16980
|
+
const edits = suggestions.find(
|
|
16981
|
+
(update) => update?.type === "setMode" && update.mode === "acceptEdits"
|
|
16982
|
+
);
|
|
16983
|
+
return edits ? { label: "Allow all edits", updates: [edits] } : void 0;
|
|
16984
|
+
}
|
|
16985
|
+
function ask(id, request2, host) {
|
|
16986
|
+
const { tool, input } = request2;
|
|
16987
|
+
if (tool === "AskUserQuestion" && Array.isArray(input.questions))
|
|
16988
|
+
return questionApproval(id, input.questions, (item) => item.multiSelect === true);
|
|
16989
|
+
if (tool === "ExitPlanMode")
|
|
16990
|
+
return {
|
|
16991
|
+
id,
|
|
16992
|
+
title: "Approve this plan?",
|
|
16993
|
+
detail: host ? "" : "Approve it on your computer, or tell Claude what to change.",
|
|
16994
|
+
choices: [
|
|
16995
|
+
...host ? [{ id: "accept", label: "Approve" }] : [],
|
|
16996
|
+
{ id: "revise", label: "Keep planning", text: true }
|
|
16997
|
+
],
|
|
16998
|
+
plan: preview2(String(input.plan ?? ""))
|
|
16999
|
+
};
|
|
17000
|
+
const diff = change(tool, input);
|
|
17001
|
+
const always = remember(request2.suggestions);
|
|
17002
|
+
return {
|
|
17003
|
+
id,
|
|
17004
|
+
title: tool === "Bash" ? "Allow command?" : EDITS.has(tool) ? "Allow file changes?" : `Allow ${request2.label ?? tool}?`,
|
|
17005
|
+
detail: preview2(
|
|
17006
|
+
[
|
|
17007
|
+
request2.description ?? (typeof input.description === "string" ? input.description : ""),
|
|
17008
|
+
typeof input.command === "string" ? input.command : diff ? "" : JSON.stringify(input, null, 2)
|
|
17009
|
+
].filter(Boolean).join("\n\n")
|
|
17010
|
+
),
|
|
17011
|
+
...diff ? { change: diff } : {},
|
|
17012
|
+
choices: [
|
|
17013
|
+
{ id: "accept", label: "Allow once" },
|
|
17014
|
+
...always ? [{ id: "always", label: always.label }] : [],
|
|
17015
|
+
{ id: "decline", label: "Reject" }
|
|
17016
|
+
]
|
|
17017
|
+
};
|
|
17018
|
+
}
|
|
17019
|
+
function decide(request2, approval2, reply) {
|
|
17020
|
+
const input = request2.input;
|
|
17021
|
+
if ("answers" in reply)
|
|
17022
|
+
return {
|
|
17023
|
+
behavior: "allow",
|
|
17024
|
+
updatedInput: {
|
|
17025
|
+
...input,
|
|
17026
|
+
answers: Object.fromEntries(
|
|
17027
|
+
(approval2.questions ?? []).map((item, index) => [
|
|
17028
|
+
item.question,
|
|
17029
|
+
reply.answers[index].join(", ")
|
|
17030
|
+
])
|
|
17031
|
+
)
|
|
17032
|
+
}
|
|
17033
|
+
};
|
|
17034
|
+
if (reply.choice === "accept")
|
|
17035
|
+
return {
|
|
17036
|
+
behavior: "allow",
|
|
17037
|
+
updatedInput: input,
|
|
17038
|
+
...approval2.plan === void 0 ? {} : { updatedPermissions: [{ type: "setMode", mode: "default", destination: "session" }] }
|
|
17039
|
+
};
|
|
17040
|
+
if (reply.choice === "always")
|
|
17041
|
+
return {
|
|
17042
|
+
behavior: "allow",
|
|
17043
|
+
updatedInput: input,
|
|
17044
|
+
updatedPermissions: remember(request2.suggestions)?.updates ?? []
|
|
17045
|
+
};
|
|
17046
|
+
if (reply.choice === "revise")
|
|
17047
|
+
return { behavior: "deny", message: reply.text?.trim() || "Keep planning." };
|
|
17048
|
+
return {
|
|
17049
|
+
behavior: "deny",
|
|
17050
|
+
message: reply.choice === "skip" ? "The person skipped these questions." : "The person rejected this from their phone."
|
|
17051
|
+
};
|
|
17052
|
+
}
|
|
16880
17053
|
var MAX_SESSIONS2 = 100;
|
|
16881
17054
|
var MAX_REQUESTS = 1e3;
|
|
16882
|
-
var
|
|
17055
|
+
var MAX_PROMPT = 4 * 1024 * 1024;
|
|
17056
|
+
var SESSION = /^\/sessions\/([\w-]{1,100})(\/prompts)?$/;
|
|
16883
17057
|
var STATUS = {
|
|
16884
17058
|
busy: "working",
|
|
16885
17059
|
idle: "idle",
|
|
@@ -16896,7 +17070,8 @@ async function registrations() {
|
|
|
16896
17070
|
const value2 = JSON.parse(await readFile2(join5(directory2, name), "utf8"));
|
|
16897
17071
|
if (typeof value2.sessionId !== "string" || typeof value2.cwd !== "string") return [];
|
|
16898
17072
|
process.kill(value2.pid, 0);
|
|
16899
|
-
|
|
17073
|
+
const binary2 = await readlink(`/proc/${value2.pid}/exe`).catch(() => "");
|
|
17074
|
+
return [{ ...value2, command: /[\\/]claude(?:\.exe)?$/.test(binary2) ? binary2 : "claude" }];
|
|
16900
17075
|
} catch {
|
|
16901
17076
|
return [];
|
|
16902
17077
|
}
|
|
@@ -16915,23 +17090,32 @@ var Claude = class {
|
|
|
16915
17090
|
notify;
|
|
16916
17091
|
server;
|
|
16917
17092
|
streams = /* @__PURE__ */ new Map();
|
|
17093
|
+
prompts = /* @__PURE__ */ new Map();
|
|
16918
17094
|
requests = /* @__PURE__ */ new Map();
|
|
16919
17095
|
async refresh() {
|
|
16920
17096
|
this.watch();
|
|
16921
17097
|
return (await registrations()).map((session) => {
|
|
16922
17098
|
const plugin = this.streams.has(session.sessionId);
|
|
17099
|
+
const models = claudeModels(this.changed, session.command, session.version);
|
|
17100
|
+
const approvals = [...this.prompts.values()].filter((prompt) => prompt.session === session.sessionId).map((prompt) => prompt.approval);
|
|
16923
17101
|
return {
|
|
16924
17102
|
id: session.sessionId,
|
|
16925
17103
|
harness: "claude",
|
|
16926
17104
|
cwd: session.cwd,
|
|
16927
17105
|
updatedAt: new Date(session.updatedAt ?? 0).toISOString(),
|
|
16928
|
-
status: STATUS[session.status ?? ""] ?? "unknown",
|
|
17106
|
+
status: approvals.length ? "attention" : STATUS[session.status ?? ""] ?? "unknown",
|
|
16929
17107
|
messages: [],
|
|
16930
|
-
approvals
|
|
17108
|
+
approvals,
|
|
17109
|
+
approve: approvals.length ? (requestId, approvalId, reply) => this.once(requestId, async () => {
|
|
17110
|
+
const prompt = this.prompts.get(approvalId);
|
|
17111
|
+
if (prompt?.session !== session.sessionId)
|
|
17112
|
+
throw new Error("Claude Code approval is no longer pending");
|
|
17113
|
+
prompt.answer(decide(prompt.request, prompt.approval, reply));
|
|
17114
|
+
}) : void 0,
|
|
16931
17115
|
send: plugin ? (requestId, text3, now) => this.once(requestId, () => this.deliver(session.sessionId, now ? { text: text3, now } : { text: text3 })) : void 0,
|
|
16932
17116
|
stop: plugin ? (requestId) => this.once(requestId, () => this.deliver(session.sessionId, { now: true })) : void 0,
|
|
16933
17117
|
now: plugin,
|
|
16934
|
-
options: claudeOptions,
|
|
17118
|
+
options: (recorded) => claudeOptions(models, recorded),
|
|
16935
17119
|
configure: plugin ? (requestId, option2, value2) => this.once(requestId, () => this.deliver(session.sessionId, { text: COMMANDS[option2](value2) })) : void 0
|
|
16936
17120
|
};
|
|
16937
17121
|
});
|
|
@@ -16940,8 +17124,9 @@ var Claude = class {
|
|
|
16940
17124
|
async listen() {
|
|
16941
17125
|
if (this.server || process.env.DASHUP_LIVE_DISCOVERY === "0") return;
|
|
16942
17126
|
const server = this.server = createServer2((request2, response) => {
|
|
16943
|
-
const id =
|
|
16944
|
-
if (
|
|
17127
|
+
const [, id, prompt] = SESSION.exec(request2.url ?? "") ?? [];
|
|
17128
|
+
if (id && prompt && request2.method === "POST") return this.prompt(id, request2, response);
|
|
17129
|
+
if (!id || prompt || request2.method !== "GET") return void response.writeHead(404).end();
|
|
16945
17130
|
this.streams.get(id)?.end();
|
|
16946
17131
|
this.streams.set(id, response);
|
|
16947
17132
|
response.once("close", () => {
|
|
@@ -16959,6 +17144,46 @@ var Claude = class {
|
|
|
16959
17144
|
server.listen(this.socket, resolve3);
|
|
16960
17145
|
});
|
|
16961
17146
|
}
|
|
17147
|
+
// Asks the phone what the session's permission request asks the person.
|
|
17148
|
+
prompt(session, request2, response) {
|
|
17149
|
+
const chunks = [];
|
|
17150
|
+
let bytes = 0;
|
|
17151
|
+
request2.on("data", (chunk) => {
|
|
17152
|
+
bytes += chunk.length;
|
|
17153
|
+
if (bytes > MAX_PROMPT) request2.destroy();
|
|
17154
|
+
else chunks.push(chunk);
|
|
17155
|
+
});
|
|
17156
|
+
request2.once("end", () => {
|
|
17157
|
+
let asked;
|
|
17158
|
+
try {
|
|
17159
|
+
asked = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
17160
|
+
} catch {
|
|
17161
|
+
}
|
|
17162
|
+
if (typeof asked?.tool !== "string" || !asked.input || typeof asked.input !== "object")
|
|
17163
|
+
return void response.writeHead(400).end();
|
|
17164
|
+
const id = randomUUID3();
|
|
17165
|
+
const prompted = {
|
|
17166
|
+
tool: asked.tool,
|
|
17167
|
+
input: asked.input,
|
|
17168
|
+
...Array.isArray(asked.suggestions) ? { suggestions: asked.suggestions } : {}
|
|
17169
|
+
};
|
|
17170
|
+
this.prompts.set(id, {
|
|
17171
|
+
session,
|
|
17172
|
+
request: prompted,
|
|
17173
|
+
approval: ask(id, prompted, false),
|
|
17174
|
+
answer: (decision) => {
|
|
17175
|
+
this.prompts.delete(id);
|
|
17176
|
+
response.end(JSON.stringify(decision));
|
|
17177
|
+
this.changed();
|
|
17178
|
+
}
|
|
17179
|
+
});
|
|
17180
|
+
response.once("close", () => {
|
|
17181
|
+
if (this.prompts.delete(id)) this.changed();
|
|
17182
|
+
});
|
|
17183
|
+
response.writeHead(200, { "Content-Type": "application/json" }).flushHeaders();
|
|
17184
|
+
this.changed();
|
|
17185
|
+
});
|
|
17186
|
+
}
|
|
16962
17187
|
deliver(id, delivery) {
|
|
16963
17188
|
const stream = this.streams.get(id);
|
|
16964
17189
|
if (!stream) return Promise.reject(new Error("Claude Code is not taking messages"));
|
|
@@ -16997,6 +17222,7 @@ var Claude = class {
|
|
|
16997
17222
|
clearTimeout(this.notify);
|
|
16998
17223
|
this.watcher?.close();
|
|
16999
17224
|
this.watcher = void 0;
|
|
17225
|
+
this.prompts.clear();
|
|
17000
17226
|
this.server?.closeAllConnections();
|
|
17001
17227
|
this.server?.close();
|
|
17002
17228
|
this.server = void 0;
|
|
@@ -17004,82 +17230,9 @@ var Claude = class {
|
|
|
17004
17230
|
};
|
|
17005
17231
|
|
|
17006
17232
|
// src/harness/headless.ts
|
|
17007
|
-
var CHOICES = [
|
|
17008
|
-
{ id: "accept", label: "Allow once" },
|
|
17009
|
-
{ id: "decline", label: "Reject" }
|
|
17010
|
-
];
|
|
17011
|
-
var EDITS = /* @__PURE__ */ new Set(["Edit", "MultiEdit", "NotebookEdit", "Write"]);
|
|
17012
17233
|
var MAX_REQUESTS2 = 1e3;
|
|
17013
17234
|
var NOTIFY_MS = 100;
|
|
17014
17235
|
var SWITCHES = { fast: "fastMode", ultracode: "ultracode" };
|
|
17015
|
-
function hunk(before, after, offset = 0) {
|
|
17016
|
-
const old = before ? before.replace(/\n$/, "").split("\n") : [];
|
|
17017
|
-
const next = after ? after.replace(/\n$/, "").split("\n") : [];
|
|
17018
|
-
let start2 = 0;
|
|
17019
|
-
while (start2 < old.length && start2 < next.length && old[start2] === next[start2]) start2++;
|
|
17020
|
-
let end = 0;
|
|
17021
|
-
while (end < old.length - start2 && end < next.length - start2 && old[old.length - 1 - end] === next[next.length - 1 - end])
|
|
17022
|
-
end++;
|
|
17023
|
-
const removed = old.slice(start2, old.length - end);
|
|
17024
|
-
const added2 = next.slice(start2, next.length - end);
|
|
17025
|
-
return {
|
|
17026
|
-
lines: [
|
|
17027
|
-
`@@ -${offset + start2 + 1},${removed.length} +${offset + start2 + 1},${added2.length} @@`,
|
|
17028
|
-
...old.slice(Math.max(0, start2 - 3), start2).map((line) => ` ${line}`),
|
|
17029
|
-
...removed.map((line) => `-${line}`),
|
|
17030
|
-
...added2.map((line) => `+${line}`),
|
|
17031
|
-
...old.slice(old.length - end, old.length - end + 3).map((line) => ` ${line}`)
|
|
17032
|
-
],
|
|
17033
|
-
added: added2.length,
|
|
17034
|
-
deleted: removed.length
|
|
17035
|
-
};
|
|
17036
|
-
}
|
|
17037
|
-
function change(tool, input) {
|
|
17038
|
-
const path = input.file_path;
|
|
17039
|
-
if (typeof path !== "string") return void 0;
|
|
17040
|
-
let current = "";
|
|
17041
|
-
try {
|
|
17042
|
-
current = readFileSync3(path, "utf8");
|
|
17043
|
-
} catch {
|
|
17044
|
-
}
|
|
17045
|
-
const edit = (value2) => {
|
|
17046
|
-
const before = String(value2?.old_string ?? "");
|
|
17047
|
-
const at = before ? current.indexOf(before) : -1;
|
|
17048
|
-
return hunk(
|
|
17049
|
-
before,
|
|
17050
|
-
String(value2?.new_string ?? ""),
|
|
17051
|
-
at < 0 ? 0 : current.slice(0, at).split("\n").length - 1
|
|
17052
|
-
);
|
|
17053
|
-
};
|
|
17054
|
-
const hunks = tool === "Write" && typeof input.content === "string" ? [hunk(current, input.content)] : tool === "Edit" ? [edit(input)] : tool === "MultiEdit" && Array.isArray(input.edits) ? input.edits.map(edit) : [];
|
|
17055
|
-
if (!hunks.length) return void 0;
|
|
17056
|
-
const diff = [`--- ${path}`, `+++ ${path}`, ...hunks.flatMap((item) => item.lines)].join("\n");
|
|
17057
|
-
if (Buffer.byteLength(diff) > MAX_PREVIEW) return void 0;
|
|
17058
|
-
return {
|
|
17059
|
-
path,
|
|
17060
|
-
diff,
|
|
17061
|
-
added: hunks.reduce((sum, item) => sum + item.added, 0),
|
|
17062
|
-
deleted: hunks.reduce((sum, item) => sum + item.deleted, 0)
|
|
17063
|
-
};
|
|
17064
|
-
}
|
|
17065
|
-
function approval2(id, request2) {
|
|
17066
|
-
const input = request2.input ?? {};
|
|
17067
|
-
const tool = String(request2.display_name ?? request2.tool_name ?? "a tool");
|
|
17068
|
-
const diff = change(request2.tool_name, input);
|
|
17069
|
-
let detail = [
|
|
17070
|
-
typeof request2.description === "string" ? request2.description : "",
|
|
17071
|
-
typeof input.command === "string" ? input.command : diff ? "" : JSON.stringify(input, null, 2)
|
|
17072
|
-
].filter(Boolean).join("\n\n");
|
|
17073
|
-
if (Buffer.byteLength(detail) > MAX_PREVIEW)
|
|
17074
|
-
detail = Buffer.from(detail).subarray(0, MAX_PREVIEW).toString("utf8");
|
|
17075
|
-
return {
|
|
17076
|
-
id,
|
|
17077
|
-
title: request2.tool_name === "Bash" ? "Allow command?" : EDITS.has(request2.tool_name) ? "Allow file changes?" : `Allow ${tool}?`,
|
|
17078
|
-
detail,
|
|
17079
|
-
...diff ? { change: diff } : {},
|
|
17080
|
-
choices: CHOICES.map((choice) => ({ ...choice }))
|
|
17081
|
-
};
|
|
17082
|
-
}
|
|
17083
17236
|
var Session = class {
|
|
17084
17237
|
// A resumed session is `saved` and keeps the title its history gives.
|
|
17085
17238
|
constructor(id, cwd, title2, changed, saved = false) {
|
|
@@ -17113,14 +17266,14 @@ var Session = class {
|
|
|
17113
17266
|
interrupt() {
|
|
17114
17267
|
this.write({
|
|
17115
17268
|
type: "control_request",
|
|
17116
|
-
request_id:
|
|
17269
|
+
request_id: randomUUID4(),
|
|
17117
17270
|
request: { subtype: "interrupt" }
|
|
17118
17271
|
});
|
|
17119
17272
|
}
|
|
17120
17273
|
apply(option2, value2) {
|
|
17121
17274
|
this.write({
|
|
17122
17275
|
type: "control_request",
|
|
17123
|
-
request_id:
|
|
17276
|
+
request_id: randomUUID4(),
|
|
17124
17277
|
request: option2 === "model" ? { subtype: "set_model", model: value2 } : { subtype: "apply_flag_settings", settings: { [SWITCHES[option2]]: value2 } }
|
|
17125
17278
|
});
|
|
17126
17279
|
}
|
|
@@ -17136,7 +17289,7 @@ var Session = class {
|
|
|
17136
17289
|
// Resolves once Claude Code takes the message, or says why it could not start. `now` stops the
|
|
17137
17290
|
// turn under way, so Claude Code takes the message at once.
|
|
17138
17291
|
say(text3, now = false) {
|
|
17139
|
-
this.messages.push({ id:
|
|
17292
|
+
this.messages.push({ id: randomUUID4(), role: "user", text: text3 });
|
|
17140
17293
|
this.typing = "";
|
|
17141
17294
|
this.status = "working";
|
|
17142
17295
|
this.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -17205,15 +17358,27 @@ var Session = class {
|
|
|
17205
17358
|
else if (message.type === "assistant") {
|
|
17206
17359
|
const text3 = (Array.isArray(message.message?.content) ? message.message.content : []).filter((part) => part?.type === "text" && typeof part.text === "string").map((part) => part.text).join("\n\n");
|
|
17207
17360
|
if (!text3) return false;
|
|
17208
|
-
this.messages.push({ id: String(message.uuid ??
|
|
17361
|
+
this.messages.push({ id: String(message.uuid ?? randomUUID4()), role: "assistant", text: text3 });
|
|
17209
17362
|
} else if (message.type === "control_request" && message.request?.subtype === "can_use_tool") {
|
|
17210
|
-
const id =
|
|
17363
|
+
const id = randomUUID4();
|
|
17364
|
+
const asked = message.request;
|
|
17365
|
+
const request2 = {
|
|
17366
|
+
tool: String(asked.tool_name),
|
|
17367
|
+
input: asked.input ?? {},
|
|
17368
|
+
label: typeof asked.display_name === "string" ? asked.display_name : void 0,
|
|
17369
|
+
description: typeof asked.description === "string" ? asked.description : void 0,
|
|
17370
|
+
suggestions: Array.isArray(asked.permission_suggestions) ? asked.permission_suggestions : void 0
|
|
17371
|
+
};
|
|
17211
17372
|
this.approvals.set(id, {
|
|
17212
|
-
|
|
17213
|
-
|
|
17214
|
-
approval:
|
|
17373
|
+
control: String(message.request_id),
|
|
17374
|
+
request: request2,
|
|
17375
|
+
approval: ask(id, request2, true)
|
|
17215
17376
|
});
|
|
17216
17377
|
this.status = "attention";
|
|
17378
|
+
} else if (message.type === "control_cancel_request") {
|
|
17379
|
+
for (const [id, pending] of this.approvals)
|
|
17380
|
+
if (pending.control === message.request_id) this.approvals.delete(id);
|
|
17381
|
+
if (this.status === "attention" && !this.approvals.size) this.status = "working";
|
|
17217
17382
|
} else if (message.type === "result") {
|
|
17218
17383
|
if (--this.open <= 0) this.turn?.stdin.end();
|
|
17219
17384
|
} else if (message.type === "stream_event" && !message.parent_tool_use_id) {
|
|
@@ -17229,7 +17394,7 @@ var Session = class {
|
|
|
17229
17394
|
this.changed();
|
|
17230
17395
|
return init;
|
|
17231
17396
|
}
|
|
17232
|
-
live() {
|
|
17397
|
+
live(models) {
|
|
17233
17398
|
return {
|
|
17234
17399
|
id: this.id,
|
|
17235
17400
|
cwd: this.cwd,
|
|
@@ -17242,7 +17407,7 @@ var Session = class {
|
|
|
17242
17407
|
approvals: [...this.approvals.values()].map((pending) => pending.approval),
|
|
17243
17408
|
send: (requestId, text3, now) => this.once(requestId, () => this.say(text3, now)),
|
|
17244
17409
|
now: true,
|
|
17245
|
-
options: claudeOptions,
|
|
17410
|
+
options: (recorded) => claudeOptions(models, recorded),
|
|
17246
17411
|
configure: (requestId, option2, value2) => this.once(requestId, async () => {
|
|
17247
17412
|
this.chosen[option2] = value2;
|
|
17248
17413
|
this.apply(option2, value2);
|
|
@@ -17251,16 +17416,16 @@ var Session = class {
|
|
|
17251
17416
|
if (!this.turn) throw new Error("Nothing is running in this Claude Code session");
|
|
17252
17417
|
this.interrupt();
|
|
17253
17418
|
}) : void 0,
|
|
17254
|
-
approve: this.approvals.size ? (requestId, approvalId,
|
|
17419
|
+
approve: this.approvals.size ? (requestId, approvalId, reply) => this.once(requestId, async () => {
|
|
17255
17420
|
const pending = this.approvals.get(approvalId);
|
|
17256
|
-
if (!pending || !this.turn
|
|
17421
|
+
if (!pending || !this.turn)
|
|
17257
17422
|
throw new Error("Claude Code approval is no longer pending");
|
|
17258
17423
|
this.write({
|
|
17259
17424
|
type: "control_response",
|
|
17260
17425
|
response: {
|
|
17261
17426
|
subtype: "success",
|
|
17262
|
-
request_id: pending.
|
|
17263
|
-
response:
|
|
17427
|
+
request_id: pending.control,
|
|
17428
|
+
response: decide(pending.request, pending.approval, reply)
|
|
17264
17429
|
}
|
|
17265
17430
|
});
|
|
17266
17431
|
this.approvals.delete(approvalId);
|
|
@@ -17291,7 +17456,7 @@ var Headless = class {
|
|
|
17291
17456
|
// Starts a session in `cwd` with its first message, and resolves with the session's id.
|
|
17292
17457
|
async start(cwd, text3) {
|
|
17293
17458
|
const title2 = text3.trim().split("\n")[0].slice(0, 80);
|
|
17294
|
-
const session = new Session(
|
|
17459
|
+
const session = new Session(randomUUID4(), cwd, title2, this.soon);
|
|
17295
17460
|
this.sessions.set(session.id, session);
|
|
17296
17461
|
try {
|
|
17297
17462
|
await session.say(text3);
|
|
@@ -17314,7 +17479,8 @@ var Headless = class {
|
|
|
17314
17479
|
}
|
|
17315
17480
|
}
|
|
17316
17481
|
async refresh() {
|
|
17317
|
-
|
|
17482
|
+
const models = claudeModels(this.changed);
|
|
17483
|
+
return [...this.sessions.values()].map((session) => session.live(models));
|
|
17318
17484
|
}
|
|
17319
17485
|
stop() {
|
|
17320
17486
|
clearTimeout(this.notify);
|
|
@@ -17329,9 +17495,9 @@ import { connect as connect2 } from "node:net";
|
|
|
17329
17495
|
import { promisify as promisify3 } from "node:util";
|
|
17330
17496
|
import { fileURLToPath } from "node:url";
|
|
17331
17497
|
import { execFile as execFile3 } from "node:child_process";
|
|
17332
|
-
import { createHash as createHash3, randomUUID as
|
|
17498
|
+
import { createHash as createHash3, randomUUID as randomUUID5 } from "node:crypto";
|
|
17333
17499
|
import { stat as stat2, readdir as readdir3, readFile as readFile3 } from "node:fs/promises";
|
|
17334
|
-
import { join as join6, dirname as dirname5, basename as
|
|
17500
|
+
import { join as join6, dirname as dirname5, basename as basename3, isAbsolute as isAbsolute3 } from "node:path";
|
|
17335
17501
|
import { WebSocketTransport } from "@microsoft/agent-host-protocol/ws";
|
|
17336
17502
|
import { AhpClient } from "@microsoft/agent-host-protocol/client";
|
|
17337
17503
|
import { chatReducer, sessionReducer, SUPPORTED_PROTOCOL_VERSIONS } from "@microsoft/agent-host-protocol";
|
|
@@ -17349,7 +17515,7 @@ var CONFIRMATION_CHOICES = [
|
|
|
17349
17515
|
function roots() {
|
|
17350
17516
|
const override = process.env.DASHUP_AHP_DATA ?? process.env.DASHUP_VSCODE_DATA;
|
|
17351
17517
|
if (override !== void 0)
|
|
17352
|
-
return override ? [
|
|
17518
|
+
return override ? [basename3(override) === "User" ? dirname5(override) : override] : [];
|
|
17353
17519
|
if (process.env.DASHUP_LIVE_DISCOVERY === "0") return [];
|
|
17354
17520
|
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");
|
|
17355
17521
|
return ["Code", "Code - Insiders"].map((name) => join6(base, name));
|
|
@@ -17391,12 +17557,12 @@ async function entries() {
|
|
|
17391
17557
|
try {
|
|
17392
17558
|
if (!await privateFile(path, MAX_METADATA)) continue;
|
|
17393
17559
|
const parsed = JSON.parse(await readFile3(path, "utf8"));
|
|
17394
|
-
const rows =
|
|
17560
|
+
const rows = basename3(path) === "metadata.json" && Array.isArray(parsed) ? parsed : [parsed];
|
|
17395
17561
|
for (const row of rows.slice(0, MAX_HOSTS)) {
|
|
17396
17562
|
if (row?.schemaVersion !== 2 || !["editor", "standalone"].includes(row.type) || !Number.isSafeInteger(row.pid) || row.pid <= 0 || typeof row.instanceId !== "string" || !row.instanceId || typeof row.connectionToken !== "string" || !row.connectionToken || row.connectionToken.length > 1024 || row.endpoint?.type !== "socket" || typeof row.endpoint.path !== "string" || !isAbsolute3(row.endpoint.path))
|
|
17397
17563
|
continue;
|
|
17398
17564
|
const identity2 = createHash3("sha256").update(`${row.type}\0${row.pid}\0${row.instanceId}`).digest("hex");
|
|
17399
|
-
if (
|
|
17565
|
+
if (basename3(path) !== "metadata.json" && basename3(path) !== `${identity2}.json`) continue;
|
|
17400
17566
|
try {
|
|
17401
17567
|
process.kill(row.pid, 0);
|
|
17402
17568
|
} catch {
|
|
@@ -17522,7 +17688,7 @@ var Host = class {
|
|
|
17522
17688
|
client;
|
|
17523
17689
|
connecting;
|
|
17524
17690
|
stopped = false;
|
|
17525
|
-
clientId = `dashup-${
|
|
17691
|
+
clientId = `dashup-${randomUUID5()}`;
|
|
17526
17692
|
resources = /* @__PURE__ */ new Map();
|
|
17527
17693
|
subscriptions = /* @__PURE__ */ new Set();
|
|
17528
17694
|
receipts = /* @__PURE__ */ new Map();
|
|
@@ -17703,12 +17869,12 @@ var Host = class {
|
|
|
17703
17869
|
for (const pending of Array.isArray(session.inputNeeded) ? session.inputNeeded : []) {
|
|
17704
17870
|
if (pending.kind !== "toolConfirmation" || pending.chat !== item.resource || pending.toolCall?.status !== "pending-confirmation")
|
|
17705
17871
|
continue;
|
|
17706
|
-
const
|
|
17872
|
+
const preview3 = confirmation(pending.toolCall);
|
|
17707
17873
|
approvals.push({
|
|
17708
17874
|
id: pending.id,
|
|
17709
17875
|
title: text2(pending.toolCall.confirmationTitle) || pending.toolCall.displayName || "Native agent approval",
|
|
17710
|
-
detail:
|
|
17711
|
-
choices:
|
|
17876
|
+
detail: preview3.detail,
|
|
17877
|
+
choices: preview3.choices.map(({ id: id2, label }) => ({ id: id2, label }))
|
|
17712
17878
|
});
|
|
17713
17879
|
}
|
|
17714
17880
|
const id = `vscode-ahp:${createHash3("sha256").update(this.entry.root).digest("hex").slice(0, 16)}:${item.resource}`;
|
|
@@ -17750,7 +17916,8 @@ var Host = class {
|
|
|
17750
17916
|
message: { text: value2, origin: { kind: "user" } }
|
|
17751
17917
|
});
|
|
17752
17918
|
} : void 0,
|
|
17753
|
-
approve: approvals.some((
|
|
17919
|
+
approve: approvals.some((approval2) => approval2.choices.length) ? (requestId, approvalId, reply) => {
|
|
17920
|
+
const choice = "choice" in reply ? reply.choice : void 0;
|
|
17754
17921
|
const prior = this.requests.get(requestId);
|
|
17755
17922
|
if (prior) return prior;
|
|
17756
17923
|
const current = this.resources.get(summary.resource)?.state;
|
|
@@ -17825,7 +17992,7 @@ import { join as join7 } from "node:path";
|
|
|
17825
17992
|
import { promisify as promisify4 } from "node:util";
|
|
17826
17993
|
import { createHash as createHash4 } from "node:crypto";
|
|
17827
17994
|
import { execFile as execFile4, spawn as spawn4 } from "node:child_process";
|
|
17828
|
-
import { stat as stat3, readlink, readdir as readdir4, readFile as readFile4 } from "node:fs/promises";
|
|
17995
|
+
import { stat as stat3, readlink as readlink2, readdir as readdir4, readFile as readFile4 } from "node:fs/promises";
|
|
17829
17996
|
var run2 = promisify4(execFile4);
|
|
17830
17997
|
var REQUEST_MS3 = 4e3;
|
|
17831
17998
|
var MAX_BYTES3 = 8 * 1024 * 1024;
|
|
@@ -17880,7 +18047,7 @@ async function linuxEndpoints() {
|
|
|
17880
18047
|
})
|
|
17881
18048
|
);
|
|
17882
18049
|
for (const fd of (await readdir4(join7(directory2, "fd"))).slice(0, 1024)) {
|
|
17883
|
-
const target = await
|
|
18050
|
+
const target = await readlink2(join7(directory2, "fd", fd)).catch(() => "");
|
|
17884
18051
|
const inode = /^socket:\[(\d+)\]$/.exec(target)?.[1];
|
|
17885
18052
|
const url2 = inode ? listeners.get(inode) : void 0;
|
|
17886
18053
|
if (url2) result.push({ url: url2, authorization: authorization(environment) });
|
|
@@ -18123,21 +18290,14 @@ var Server = class {
|
|
|
18123
18290
|
(question) => question?.sessionID === id && typeof question.id === "string"
|
|
18124
18291
|
) : [];
|
|
18125
18292
|
const approvals = pending.map((permission) => permissionApproval(permission, history, cwd));
|
|
18126
|
-
for (const question of asks)
|
|
18127
|
-
|
|
18128
|
-
|
|
18129
|
-
|
|
18130
|
-
|
|
18131
|
-
|
|
18132
|
-
|
|
18133
|
-
|
|
18134
|
-
detail: typeof first?.question === "string" ? first.question : "Open this question in the computer\u2019s native interface.",
|
|
18135
|
-
choices: options.map((option2, index) => ({
|
|
18136
|
-
id: `answer:${index}`,
|
|
18137
|
-
label: option2.label
|
|
18138
|
-
}))
|
|
18139
|
-
});
|
|
18140
|
-
}
|
|
18293
|
+
for (const question of asks)
|
|
18294
|
+
approvals.push(
|
|
18295
|
+
questionApproval(
|
|
18296
|
+
`question:${question.id}`,
|
|
18297
|
+
Array.isArray(question.questions) ? question.questions : [],
|
|
18298
|
+
(item) => item.multiple === true
|
|
18299
|
+
)
|
|
18300
|
+
);
|
|
18141
18301
|
result.push({
|
|
18142
18302
|
id,
|
|
18143
18303
|
cwd,
|
|
@@ -18173,23 +18333,21 @@ var Server = class {
|
|
|
18173
18333
|
});
|
|
18174
18334
|
this.changed();
|
|
18175
18335
|
}),
|
|
18176
|
-
approve: approvals.some((
|
|
18336
|
+
approve: approvals.some((approval2) => approval2.choices.length) ? (requestId, approvalId, reply) => this.once(requestId, async () => {
|
|
18177
18337
|
if (this.stopped) throw new Error("OpenCode is disconnected");
|
|
18338
|
+
const choice = "choice" in reply ? reply.choice : void 0;
|
|
18178
18339
|
if (approvalId.startsWith("question:")) {
|
|
18179
18340
|
const requestID = approvalId.slice("question:".length);
|
|
18180
18341
|
const current = await this.request("/question", cwd);
|
|
18181
|
-
|
|
18342
|
+
if (!Array.isArray(current) || !current.some(
|
|
18182
18343
|
(value2) => value2?.id === requestID && value2.sessionID === id
|
|
18183
|
-
)
|
|
18184
|
-
|
|
18185
|
-
|
|
18186
|
-
|
|
18187
|
-
|
|
18188
|
-
|
|
18189
|
-
|
|
18190
|
-
await this.request(`/question/${encodeURIComponent(requestID)}/reply`, cwd, {
|
|
18191
|
-
answers: [[option2.label]]
|
|
18192
|
-
});
|
|
18344
|
+
))
|
|
18345
|
+
throw new Error("Question is no longer pending");
|
|
18346
|
+
await this.request(
|
|
18347
|
+
`/question/${encodeURIComponent(requestID)}/${"answers" in reply ? "reply" : "reject"}`,
|
|
18348
|
+
cwd,
|
|
18349
|
+
"answers" in reply ? { answers: reply.answers } : {}
|
|
18350
|
+
);
|
|
18193
18351
|
} else {
|
|
18194
18352
|
const current = await this.request("/permission", cwd);
|
|
18195
18353
|
const permission = Array.isArray(current) ? current.find(
|
|
@@ -18413,10 +18571,10 @@ var LiveHarnesses = class {
|
|
|
18413
18571
|
});
|
|
18414
18572
|
return this.refreshing;
|
|
18415
18573
|
}
|
|
18416
|
-
// The agents whose own command runs on this computer
|
|
18574
|
+
// The agents whose own command runs on this computer. Claude Code is asked its models again.
|
|
18417
18575
|
async agents() {
|
|
18418
18576
|
const harnesses = ["claude", "codex", "opencode"];
|
|
18419
|
-
|
|
18577
|
+
forgetModels();
|
|
18420
18578
|
const runs = await Promise.all(
|
|
18421
18579
|
harnesses.map((harness) => promisify5(execFile5)(harness, ["--version"], {
|
|
18422
18580
|
timeout: 1e4,
|
|
@@ -18426,7 +18584,6 @@ var LiveHarnesses = class {
|
|
|
18426
18584
|
() => false
|
|
18427
18585
|
))
|
|
18428
18586
|
);
|
|
18429
|
-
await asked;
|
|
18430
18587
|
return harnesses.filter((_, index) => runs[index]);
|
|
18431
18588
|
}
|
|
18432
18589
|
// Starts a session in `cwd` with its first message, and resolves with the session's native id.
|
|
@@ -18475,10 +18632,10 @@ var LiveHarnesses = class {
|
|
|
18475
18632
|
};
|
|
18476
18633
|
|
|
18477
18634
|
// src/harness/queue.ts
|
|
18478
|
-
import { randomUUID as
|
|
18635
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
18479
18636
|
import { spawn as spawn5 } from "node:child_process";
|
|
18480
18637
|
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
18481
|
-
import { join as join8, dirname as dirname6, basename as
|
|
18638
|
+
import { join as join8, dirname as dirname6, basename as basename4 } from "node:path";
|
|
18482
18639
|
import { statSync as statSync2, existsSync as existsSync4, readdirSync as readdirSync3, readFileSync as readFileSync4, readlinkSync, realpathSync as realpathSync2 } from "node:fs";
|
|
18483
18640
|
var CODEX_VERSION = "0.160.1";
|
|
18484
18641
|
var REQUEST_MS4 = 1e4;
|
|
@@ -18510,7 +18667,7 @@ function processOwners(pid) {
|
|
|
18510
18667
|
const root = `/proc/${pid}`;
|
|
18511
18668
|
if (process.getuid && statSync2(root).uid !== process.getuid()) return [];
|
|
18512
18669
|
const executable = realpathSync2.native(join8(root, "exe"));
|
|
18513
|
-
if (
|
|
18670
|
+
if (basename4(executable) !== "codex" || packageVersion(executable) !== CODEX_VERSION) return [];
|
|
18514
18671
|
const status2 = readFileSync4(join8(root, "stat"), "utf8");
|
|
18515
18672
|
const start2 = status2.slice(status2.lastIndexOf(")") + 2).split(" ")[19];
|
|
18516
18673
|
if (!start2 || !/^\d+$/.test(start2)) return [];
|
|
@@ -18523,8 +18680,8 @@ function processOwners(pid) {
|
|
|
18523
18680
|
}
|
|
18524
18681
|
});
|
|
18525
18682
|
return files2.flatMap(({ fd, path }) => {
|
|
18526
|
-
if (
|
|
18527
|
-
const thread =
|
|
18683
|
+
if (basename4(dirname6(path)) !== "thread-writer-locks" || !path.endsWith(".lock")) return [];
|
|
18684
|
+
const thread = basename4(path, ".lock");
|
|
18528
18685
|
if (!THREAD_ID.test(thread)) return [];
|
|
18529
18686
|
try {
|
|
18530
18687
|
const lock = readFileSync4(join8(root, "fdinfo", fd), "utf8");
|
|
@@ -18760,7 +18917,7 @@ var NativeQueue = class {
|
|
|
18760
18917
|
if (existing) return existing;
|
|
18761
18918
|
if (this.stopped) throw new Error("The connector is stopping");
|
|
18762
18919
|
validateOwner(scope2.owner);
|
|
18763
|
-
const clientUserMessageId =
|
|
18920
|
+
const clientUserMessageId = randomUUID6();
|
|
18764
18921
|
const request2 = { ...scope2, receipt: { state: "unknown", clientUserMessageId } };
|
|
18765
18922
|
let writer;
|
|
18766
18923
|
try {
|
|
@@ -18834,6 +18991,7 @@ var EPHEMERAL_TTL_MS = 6e4;
|
|
|
18834
18991
|
var HOME_RECENT = 20;
|
|
18835
18992
|
var FOLLOW_MS = 2 * 6e4;
|
|
18836
18993
|
var RESCAN_MS = 1e3;
|
|
18994
|
+
var answers = (approval2, reply) => "answers" in reply ? reply.answers.length === approval2.questions?.length : approval2.choices.some((choice) => choice.id === reply.choice);
|
|
18837
18995
|
var Command = z2.object({
|
|
18838
18996
|
type: z2.literal("command"),
|
|
18839
18997
|
requestId: z2.string().min(1),
|
|
@@ -18855,7 +19013,15 @@ var Command = z2.object({
|
|
|
18855
19013
|
option: z2.string().min(1).max(100),
|
|
18856
19014
|
value: z2.union([z2.string().min(1).max(200), z2.boolean()])
|
|
18857
19015
|
}).strict(),
|
|
18858
|
-
z2.object({
|
|
19016
|
+
z2.object({
|
|
19017
|
+
kind: z2.literal("approval.respond"),
|
|
19018
|
+
approvalId: z2.string(),
|
|
19019
|
+
choice: z2.string().optional(),
|
|
19020
|
+
text: z2.string().max(1e5).optional(),
|
|
19021
|
+
answers: z2.array(z2.array(z2.string().min(1).max(1e5)).min(1)).min(1).optional()
|
|
19022
|
+
}).strict().refine(
|
|
19023
|
+
(action) => action.choice === void 0 !== (action.answers === void 0) && (action.text === void 0 || action.choice !== void 0)
|
|
19024
|
+
),
|
|
18859
19025
|
z2.object({ kind: z2.literal("attachment.read"), id: z2.string().regex(/^[a-f0-9]{64}$/) }).strict(),
|
|
18860
19026
|
z2.object({
|
|
18861
19027
|
kind: z2.literal("session.start"),
|
|
@@ -18971,15 +19137,15 @@ var Connector = class {
|
|
|
18971
19137
|
for (const project of this.discovery.projects.values())
|
|
18972
19138
|
for (const thread of project.threads) {
|
|
18973
19139
|
const native = this.discovery.locations.get(thread.id)?.live;
|
|
18974
|
-
const
|
|
18975
|
-
if (!
|
|
19140
|
+
const approval2 = native?.approvals[0];
|
|
19141
|
+
if (!approval2) continue;
|
|
18976
19142
|
await this.thread(pair, project, thread);
|
|
18977
19143
|
await this.send(pair, {
|
|
18978
19144
|
type: "attention",
|
|
18979
19145
|
projectId: project.id,
|
|
18980
19146
|
threadId: thread.id,
|
|
18981
|
-
title:
|
|
18982
|
-
body:
|
|
19147
|
+
title: approval2.title,
|
|
19148
|
+
body: approval2.detail,
|
|
18983
19149
|
reason: "approval"
|
|
18984
19150
|
});
|
|
18985
19151
|
}
|
|
@@ -19233,9 +19399,9 @@ var Connector = class {
|
|
|
19233
19399
|
if (JSON.stringify(prior) === JSON.stringify(snapshot)) continue;
|
|
19234
19400
|
this.nativeSnapshots.set(thread.id, structuredClone(snapshot));
|
|
19235
19401
|
for (const pair of this.activePairs()) await this.thread(pair, project, thread);
|
|
19236
|
-
for (const
|
|
19237
|
-
if (!prior?.approvals.some((item) => item.id ===
|
|
19238
|
-
await this.attention(project.id, thread.id,
|
|
19402
|
+
for (const approval2 of session.approvals)
|
|
19403
|
+
if (!prior?.approvals.some((item) => item.id === approval2.id))
|
|
19404
|
+
await this.attention(project.id, thread.id, approval2.title, approval2.detail, "approval");
|
|
19239
19405
|
if (prior?.thread.status === "working" && thread.status === "idle")
|
|
19240
19406
|
await this.attention(
|
|
19241
19407
|
project.id,
|
|
@@ -19423,7 +19589,7 @@ var Connector = class {
|
|
|
19423
19589
|
}
|
|
19424
19590
|
}
|
|
19425
19591
|
attach(socket) {
|
|
19426
|
-
const sessions = bridgeAcp(socket, (session,
|
|
19592
|
+
const sessions = bridgeAcp(socket, (session, approval2) => {
|
|
19427
19593
|
this.agents.set(session.id, session);
|
|
19428
19594
|
const { project, thread } = this.adopt(session);
|
|
19429
19595
|
if (!this.snapshots.has(session.id)) {
|
|
@@ -19438,15 +19604,15 @@ var Connector = class {
|
|
|
19438
19604
|
}
|
|
19439
19605
|
}).catch((error) => this.report(error));
|
|
19440
19606
|
}
|
|
19441
|
-
if (
|
|
19607
|
+
if (approval2)
|
|
19442
19608
|
this.work = this.work.then(async () => {
|
|
19443
|
-
if (this.agents.get(session.id) !== session || ![...session.approvals.values()].includes(
|
|
19609
|
+
if (this.agents.get(session.id) !== session || ![...session.approvals.values()].includes(approval2))
|
|
19444
19610
|
return;
|
|
19445
19611
|
await this.attention(
|
|
19446
19612
|
project.id,
|
|
19447
19613
|
thread.id,
|
|
19448
|
-
|
|
19449
|
-
|
|
19614
|
+
approval2.title,
|
|
19615
|
+
approval2.detail,
|
|
19450
19616
|
"approval"
|
|
19451
19617
|
);
|
|
19452
19618
|
}).catch((error) => this.report(error));
|
|
@@ -19612,24 +19778,24 @@ var Connector = class {
|
|
|
19612
19778
|
}
|
|
19613
19779
|
case "approval.respond": {
|
|
19614
19780
|
if (!thread) throw new Error("Thread not found");
|
|
19615
|
-
const
|
|
19781
|
+
const { kind, approvalId, ...reply } = command2.action;
|
|
19616
19782
|
const native = this.discovery.locations.get(thread.id)?.live;
|
|
19617
19783
|
if (native?.approve) {
|
|
19618
|
-
const
|
|
19619
|
-
if (!
|
|
19784
|
+
const approval3 = native.approvals.find((item) => item.id === approvalId);
|
|
19785
|
+
if (!approval3 || !answers(approval3, reply))
|
|
19620
19786
|
throw new Error("Approval is no longer available");
|
|
19621
|
-
return native.approve(command2.requestId,
|
|
19787
|
+
return native.approve(command2.requestId, approval3.id, reply);
|
|
19622
19788
|
}
|
|
19623
19789
|
const agent = this.attached(thread);
|
|
19624
19790
|
if (!agent)
|
|
19625
19791
|
throw new Error("Approval requires an attached ACP session. Answer it on your computer.");
|
|
19626
|
-
const [request2,
|
|
19627
|
-
if (!
|
|
19792
|
+
const [request2, approval2] = [...agent.approvals].find(([, item]) => item.id === approvalId) ?? [];
|
|
19793
|
+
if (!approval2 || !("choice" in reply) || !answers(approval2, reply))
|
|
19628
19794
|
throw new Error("Approval is no longer available");
|
|
19629
19795
|
return commandAcp(agent, command2.requestId, {
|
|
19630
19796
|
kind: "approval",
|
|
19631
19797
|
id: request2,
|
|
19632
|
-
choice:
|
|
19798
|
+
choice: reply.choice
|
|
19633
19799
|
});
|
|
19634
19800
|
}
|
|
19635
19801
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dashup/connector",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
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",
|
|
33
34
|
"@dashup/protocol": "0.1.0",
|
|
34
35
|
"@dashup/typescript-config": "0.0.0",
|
|
35
|
-
"@
|
|
36
|
-
"@parcae/sdk": "0.8.1"
|
|
37
|
-
"@parcae/lynx": "0.1.0"
|
|
36
|
+
"@parcae/lynx": "0.1.0",
|
|
37
|
+
"@parcae/sdk": "0.8.1"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
40
|
"dashup": "tsx src/index.ts",
|