@dashup/connector 0.1.15 → 0.1.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -4
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/hooks/register.ts +77 -0
- package/dist/index.js +1144 -872
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -670,13 +670,13 @@ var require_common = __commonJS({
|
|
|
670
670
|
}
|
|
671
671
|
}
|
|
672
672
|
}
|
|
673
|
-
function matchesTemplate(
|
|
673
|
+
function matchesTemplate(search2, template) {
|
|
674
674
|
let searchIndex = 0;
|
|
675
675
|
let templateIndex = 0;
|
|
676
676
|
let starIndex = -1;
|
|
677
677
|
let matchIndex = 0;
|
|
678
|
-
while (searchIndex <
|
|
679
|
-
if (templateIndex < template.length && (template[templateIndex] ===
|
|
678
|
+
while (searchIndex < search2.length) {
|
|
679
|
+
if (templateIndex < template.length && (template[templateIndex] === search2[searchIndex] || template[templateIndex] === "*")) {
|
|
680
680
|
if (template[templateIndex] === "*") {
|
|
681
681
|
starIndex = templateIndex;
|
|
682
682
|
matchIndex = searchIndex;
|
|
@@ -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.17";
|
|
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;
|
|
@@ -4222,8 +4240,11 @@ var READ_BYTES = 64 * 1024;
|
|
|
4222
4240
|
var TITLE_LIMIT = 120;
|
|
4223
4241
|
var LATEST_BYTES = 8 * 1024 * 1024;
|
|
4224
4242
|
var JOURNALS = 16;
|
|
4243
|
+
var SEARCH_BYTES = 1024 * 1024;
|
|
4244
|
+
var MARKED = 512;
|
|
4225
4245
|
var journals = /* @__PURE__ */ new Map();
|
|
4226
4246
|
var metadata = /* @__PURE__ */ new Map();
|
|
4247
|
+
var marked = /* @__PURE__ */ new Map();
|
|
4227
4248
|
function object(value2) {
|
|
4228
4249
|
return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : void 0;
|
|
4229
4250
|
}
|
|
@@ -4288,6 +4309,40 @@ function described(path, until, describe2) {
|
|
|
4288
4309
|
metadata.set(path, { stamp, value: value2 });
|
|
4289
4310
|
return value2;
|
|
4290
4311
|
}
|
|
4312
|
+
function newest(path, marker) {
|
|
4313
|
+
const key = `${path}\0${marker}`;
|
|
4314
|
+
const stamp = statSync(path);
|
|
4315
|
+
const saved = marked.get(key);
|
|
4316
|
+
if (saved && unchanged(saved.stamp, stamp)) return saved.row;
|
|
4317
|
+
const grown = saved && saved.stamp.ino === stamp.ino && saved.stamp.size <= stamp.size;
|
|
4318
|
+
const start2 = grown ? Math.max(0, saved.stamp.size - SEARCH_BYTES) : 0;
|
|
4319
|
+
const row = search(path, marker, start2, stamp.size) ?? (grown ? saved.row : void 0);
|
|
4320
|
+
marked.delete(key);
|
|
4321
|
+
marked.set(key, { stamp, row });
|
|
4322
|
+
if (marked.size > MARKED) marked.delete(marked.keys().next().value);
|
|
4323
|
+
return row;
|
|
4324
|
+
}
|
|
4325
|
+
function search(path, marker, from, to) {
|
|
4326
|
+
let carry = Buffer.alloc(0);
|
|
4327
|
+
let position = to;
|
|
4328
|
+
while (position > from) {
|
|
4329
|
+
const begin = Math.max(from, position - SEARCH_BYTES);
|
|
4330
|
+
const buffer = Buffer.concat([readRange(path, begin, position - begin), carry]);
|
|
4331
|
+
const first = begin > from ? buffer.indexOf(10) + 1 : 0;
|
|
4332
|
+
position = begin;
|
|
4333
|
+
if (begin > from && !first) {
|
|
4334
|
+
carry = buffer;
|
|
4335
|
+
continue;
|
|
4336
|
+
}
|
|
4337
|
+
const lines = buffer.subarray(first).toString("utf8").split("\n");
|
|
4338
|
+
for (let index = lines.length - 1; index >= 0; index--) {
|
|
4339
|
+
const row = lines[index].includes(marker) ? parse(lines[index]) : void 0;
|
|
4340
|
+
if (row) return row;
|
|
4341
|
+
}
|
|
4342
|
+
carry = buffer.subarray(0, first);
|
|
4343
|
+
}
|
|
4344
|
+
return void 0;
|
|
4345
|
+
}
|
|
4291
4346
|
var windowed = (path, messages5) => journals.get(path)?.start ? [EARLIER, ...messages5] : messages5;
|
|
4292
4347
|
function journal(path, latest = false) {
|
|
4293
4348
|
const stamp = statSync(path);
|
|
@@ -4544,20 +4599,18 @@ function claudeConversation(conversation, rows) {
|
|
|
4544
4599
|
}
|
|
4545
4600
|
function claudeSettings(path) {
|
|
4546
4601
|
const rows = journal(path, true);
|
|
4547
|
-
const
|
|
4602
|
+
const marker = object(newest(path, '"ultra_effort_')?.attachment)?.type;
|
|
4548
4603
|
for (let index = rows.length - 1; index >= 0; index--) {
|
|
4549
4604
|
const row = rows[index];
|
|
4550
4605
|
const message = object(row.message);
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
settings.ultracode = marker === "ultra_effort_enter";
|
|
4558
|
-
if (settings.model !== void 0 && settings.ultracode !== void 0) break;
|
|
4606
|
+
if (row.type === "assistant" && !row.isSidechain && typeof message?.model === "string" && message.model !== "<synthetic>")
|
|
4607
|
+
return {
|
|
4608
|
+
model: message.model,
|
|
4609
|
+
fast: object(message.usage)?.speed === "fast",
|
|
4610
|
+
ultracode: marker === "ultra_effort_enter"
|
|
4611
|
+
};
|
|
4559
4612
|
}
|
|
4560
|
-
return { ultracode:
|
|
4613
|
+
return { ultracode: marker === "ultra_effort_enter" };
|
|
4561
4614
|
}
|
|
4562
4615
|
function claude(root, scan) {
|
|
4563
4616
|
for (const path of files(join2(root, "projects"), scan, 1).filter((file) => file.endsWith(".jsonl"))) {
|
|
@@ -4614,6 +4667,21 @@ function applyPatch(patch) {
|
|
|
4614
4667
|
+++ ${kind === "Delete" ? "/dev/null" : path}`
|
|
4615
4668
|
).replace(/^\*\*\* .*\n?/gm, "");
|
|
4616
4669
|
}
|
|
4670
|
+
function codexSettings(path) {
|
|
4671
|
+
const rows = journal(path, true);
|
|
4672
|
+
const applied = object(newest(path, '"thread_settings_applied"')?.payload?.thread_settings);
|
|
4673
|
+
const fast = applied ? { fast: applied.service_tier === "priority" } : {};
|
|
4674
|
+
for (let index = rows.length - 1; index >= 0; index--) {
|
|
4675
|
+
const row = rows[index];
|
|
4676
|
+
const turn = row.type === "turn_context" ? object(row.payload) : void 0;
|
|
4677
|
+
const settings = row.payload?.type === "thread_settings_applied" ? object(row.payload.thread_settings) : void 0;
|
|
4678
|
+
const model = settings?.model ?? turn?.model;
|
|
4679
|
+
const effort = settings?.reasoning_effort ?? turn?.effort;
|
|
4680
|
+
if (typeof model === "string")
|
|
4681
|
+
return { model, ...typeof effort === "string" ? { effort } : {}, ...fast };
|
|
4682
|
+
}
|
|
4683
|
+
return fast;
|
|
4684
|
+
}
|
|
4617
4685
|
function codexMessages(path) {
|
|
4618
4686
|
const rows = journal(path, true);
|
|
4619
4687
|
const outputs = /* @__PURE__ */ new Map();
|
|
@@ -4706,7 +4774,8 @@ function codex(root, scan) {
|
|
|
4706
4774
|
"Codex thread"
|
|
4707
4775
|
),
|
|
4708
4776
|
files: [path, ...indexed ? [index] : []],
|
|
4709
|
-
read: () => codexMessages(path)
|
|
4777
|
+
read: () => codexMessages(path),
|
|
4778
|
+
settings: () => codexSettings(path)
|
|
4710
4779
|
});
|
|
4711
4780
|
} catch {
|
|
4712
4781
|
scan.warnings.push(`Cannot read Codex session: ${path}`);
|
|
@@ -4882,7 +4951,7 @@ function openCodeLegacy(root, scan) {
|
|
|
4882
4951
|
continue;
|
|
4883
4952
|
scan.paths.add(root);
|
|
4884
4953
|
const messages5 = join2(root, "message", row.id);
|
|
4885
|
-
const
|
|
4954
|
+
const newest2 = files(messages5, scan, 0).filter((file) => file.endsWith(".json")).sort().at(-1);
|
|
4886
4955
|
scan.sessions.push({
|
|
4887
4956
|
harness: "opencode",
|
|
4888
4957
|
id: row.id,
|
|
@@ -4890,7 +4959,7 @@ function openCodeLegacy(root, scan) {
|
|
|
4890
4959
|
title: title(row.title, "OpenCode thread"),
|
|
4891
4960
|
parent: typeof row.parentID === "string" ? row.parentID : void 0,
|
|
4892
4961
|
updatedAt: date(
|
|
4893
|
-
|
|
4962
|
+
newest2 ? jsonFile(newest2)?.time?.created : row.time?.created,
|
|
4894
4963
|
statSync(path).mtimeMs
|
|
4895
4964
|
),
|
|
4896
4965
|
files: [path, messages5, join2(root, "part")],
|
|
@@ -13246,22 +13315,22 @@ var FrontendAdapter = class {
|
|
|
13246
13315
|
if (options.subscribe === false) requestData.__subscribe = false;
|
|
13247
13316
|
const result = await this.transport.get(path, requestData);
|
|
13248
13317
|
const items = result?.[(0, import_pluralize.default)(modelClass.type)] ?? result?.[modelClass.type + "s"] ?? result?.items ?? [];
|
|
13249
|
-
const
|
|
13318
|
+
const models2 = items.map(
|
|
13250
13319
|
(row) => modelClass.hydrate(this, row)
|
|
13251
13320
|
);
|
|
13252
13321
|
if (result?.__queryHash) {
|
|
13253
|
-
Object.defineProperty(
|
|
13322
|
+
Object.defineProperty(models2, "__queryHash", {
|
|
13254
13323
|
value: result.__queryHash,
|
|
13255
13324
|
enumerable: false
|
|
13256
13325
|
});
|
|
13257
13326
|
}
|
|
13258
13327
|
if (typeof result?.totalCount === "number") {
|
|
13259
|
-
Object.defineProperty(
|
|
13328
|
+
Object.defineProperty(models2, "__totalCount", {
|
|
13260
13329
|
value: result.totalCount,
|
|
13261
13330
|
enumerable: false
|
|
13262
13331
|
});
|
|
13263
13332
|
}
|
|
13264
|
-
return
|
|
13333
|
+
return models2;
|
|
13265
13334
|
};
|
|
13266
13335
|
chain.first = async () => {
|
|
13267
13336
|
const all = await chain.find();
|
|
@@ -16433,720 +16502,460 @@ async function shareAttachment(config2, pair, project, thread, messages5, id) {
|
|
|
16433
16502
|
import { promisify as promisify5 } from "node:util";
|
|
16434
16503
|
import { execFile as execFile5 } from "node:child_process";
|
|
16435
16504
|
|
|
16436
|
-
// src/harness/
|
|
16437
|
-
import
|
|
16505
|
+
// src/harness/headless.ts
|
|
16506
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
16507
|
+
import { createInterface as createInterface3 } from "node:readline";
|
|
16508
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
16509
|
+
|
|
16510
|
+
// src/harness/claude.ts
|
|
16438
16511
|
import { homedir as homedir4 } from "node:os";
|
|
16439
|
-
import { connect } from "node:net";
|
|
16440
|
-
import { promisify as promisify2 } from "node:util";
|
|
16441
16512
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
16442
|
-
import {
|
|
16443
|
-
import {
|
|
16444
|
-
import {
|
|
16445
|
-
|
|
16446
|
-
|
|
16447
|
-
|
|
16448
|
-
var
|
|
16449
|
-
var
|
|
16450
|
-
|
|
16451
|
-
|
|
16452
|
-
{
|
|
16453
|
-
|
|
16454
|
-
|
|
16455
|
-
|
|
16456
|
-
|
|
16457
|
-
|
|
16458
|
-
|
|
16459
|
-
|
|
16460
|
-
|
|
16461
|
-
|
|
16462
|
-
|
|
16463
|
-
|
|
16464
|
-
|
|
16465
|
-
|
|
16466
|
-
|
|
16513
|
+
import { dirname as dirname4, join as join4, basename as basename2 } from "node:path";
|
|
16514
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
16515
|
+
import { createInterface as createInterface2 } from "node:readline";
|
|
16516
|
+
import { watch as watch2, readFileSync as readFileSync3 } from "node:fs";
|
|
16517
|
+
import { mkdir, readdir, readFile, readlink, rm } from "node:fs/promises";
|
|
16518
|
+
import { createServer as createServer2 } from "node:http";
|
|
16519
|
+
var MODELS_MS = 3e4;
|
|
16520
|
+
var COMMANDS = {
|
|
16521
|
+
model: (value2) => `/model ${value2}`,
|
|
16522
|
+
fast: (value2) => `/fast ${value2 ? "on" : "off"}`,
|
|
16523
|
+
ultracode: (value2) => `/effort ultracode ${value2 ? "on" : "off"}`
|
|
16524
|
+
};
|
|
16525
|
+
var offered = /* @__PURE__ */ new Map();
|
|
16526
|
+
var asking = /* @__PURE__ */ new Set();
|
|
16527
|
+
var forgetModels = () => offered.clear();
|
|
16528
|
+
function claudeModels(changed, command2 = "claude", version2 = "") {
|
|
16529
|
+
const key = `${command2}\0${version2}`;
|
|
16530
|
+
if (!offered.has(key) && !asking.has(key)) {
|
|
16531
|
+
asking.add(key);
|
|
16532
|
+
void listModels(command2).then((models2) => {
|
|
16533
|
+
asking.delete(key);
|
|
16534
|
+
if (!models2.length) return;
|
|
16535
|
+
offered.set(key, models2);
|
|
16536
|
+
changed();
|
|
16537
|
+
});
|
|
16538
|
+
}
|
|
16539
|
+
return offered.get(key) ?? [];
|
|
16540
|
+
}
|
|
16541
|
+
function listModels(command2) {
|
|
16542
|
+
return new Promise((resolve3) => {
|
|
16543
|
+
const child = spawn2(
|
|
16544
|
+
command2,
|
|
16545
|
+
["-p", "--input-format", "stream-json", "--output-format", "stream-json", "--verbose"],
|
|
16546
|
+
{ cwd: homedir4(), stdio: "pipe", shell: process.platform === "win32" }
|
|
16547
|
+
);
|
|
16548
|
+
const timer = setTimeout(() => child.kill(), MODELS_MS);
|
|
16549
|
+
child.stdin.on("error", () => {
|
|
16550
|
+
});
|
|
16551
|
+
child.once("error", () => resolve3([]));
|
|
16552
|
+
child.once("exit", () => {
|
|
16553
|
+
clearTimeout(timer);
|
|
16554
|
+
resolve3([]);
|
|
16555
|
+
});
|
|
16556
|
+
createInterface2({ input: child.stdout }).on("line", (line) => {
|
|
16557
|
+
try {
|
|
16558
|
+
const message = JSON.parse(line);
|
|
16559
|
+
if (message?.type !== "control_response" || message.response?.request_id !== "models")
|
|
16560
|
+
return;
|
|
16561
|
+
const models2 = message.response.response?.models;
|
|
16562
|
+
resolve3(
|
|
16563
|
+
(Array.isArray(models2) ? models2 : []).filter(
|
|
16564
|
+
(model) => typeof model?.value === "string" && typeof model.displayName === "string"
|
|
16565
|
+
)
|
|
16566
|
+
);
|
|
16567
|
+
child.stdin.end();
|
|
16568
|
+
} catch {
|
|
16569
|
+
}
|
|
16570
|
+
});
|
|
16571
|
+
child.stdin.write(
|
|
16572
|
+
`${JSON.stringify({ type: "control_request", request_id: "models", request: { subtype: "initialize" } })}
|
|
16573
|
+
`
|
|
16574
|
+
);
|
|
16575
|
+
});
|
|
16576
|
+
}
|
|
16577
|
+
function claudeOptions(models2, recorded) {
|
|
16578
|
+
const ran = typeof recorded.model === "string" ? recorded.model : "";
|
|
16579
|
+
const current = models2.find((model) => model.value !== "default" && model.resolvedModel === ran) ?? models2.find((model) => model.value === ran);
|
|
16580
|
+
const choices = models2.map((model) => ({ value: model.value, name: model.displayName }));
|
|
16581
|
+
if (!ran) return [];
|
|
16582
|
+
if (!current) choices.push({ value: ran, name: ran });
|
|
16583
|
+
return [
|
|
16584
|
+
{ id: "model", name: "Model", value: current?.value ?? ran, choices },
|
|
16585
|
+
...current?.supportsFastMode ? [{ id: "fast", name: "Fast", value: recorded.fast === true }] : [],
|
|
16586
|
+
...current?.supportedEffortLevels?.includes("xhigh") ? [{ id: "ultracode", name: "Ultracode", value: recorded.ultracode === true }] : []
|
|
16587
|
+
];
|
|
16588
|
+
}
|
|
16589
|
+
var EDITS = /* @__PURE__ */ new Set(["Edit", "MultiEdit", "NotebookEdit", "Write"]);
|
|
16590
|
+
var SCOPE = 32;
|
|
16591
|
+
var preview2 = (text3) => Buffer.byteLength(text3) > MAX_PREVIEW ? Buffer.from(text3).subarray(0, MAX_PREVIEW).toString("utf8") : text3;
|
|
16592
|
+
function hunk(before, after, offset = 0) {
|
|
16593
|
+
const old = before ? before.replace(/\n$/, "").split("\n") : [];
|
|
16594
|
+
const next = after ? after.replace(/\n$/, "").split("\n") : [];
|
|
16595
|
+
let start2 = 0;
|
|
16596
|
+
while (start2 < old.length && start2 < next.length && old[start2] === next[start2]) start2++;
|
|
16597
|
+
let end = 0;
|
|
16598
|
+
while (end < old.length - start2 && end < next.length - start2 && old[old.length - 1 - end] === next[next.length - 1 - end])
|
|
16599
|
+
end++;
|
|
16600
|
+
const removed = old.slice(start2, old.length - end);
|
|
16601
|
+
const added2 = next.slice(start2, next.length - end);
|
|
16602
|
+
return {
|
|
16603
|
+
lines: [
|
|
16604
|
+
`@@ -${offset + start2 + 1},${removed.length} +${offset + start2 + 1},${added2.length} @@`,
|
|
16605
|
+
...old.slice(Math.max(0, start2 - 3), start2).map((line) => ` ${line}`),
|
|
16606
|
+
...removed.map((line) => `-${line}`),
|
|
16607
|
+
...added2.map((line) => `+${line}`),
|
|
16608
|
+
...old.slice(old.length - end, old.length - end + 3).map((line) => ` ${line}`)
|
|
16609
|
+
],
|
|
16610
|
+
added: added2.length,
|
|
16611
|
+
deleted: removed.length
|
|
16612
|
+
};
|
|
16613
|
+
}
|
|
16614
|
+
function change(tool, input) {
|
|
16615
|
+
const path = input.file_path;
|
|
16616
|
+
if (typeof path !== "string") return void 0;
|
|
16617
|
+
let current = "";
|
|
16618
|
+
try {
|
|
16619
|
+
current = readFileSync3(path, "utf8");
|
|
16620
|
+
} catch {
|
|
16621
|
+
}
|
|
16622
|
+
const edit = (value2) => {
|
|
16623
|
+
const before = String(value2?.old_string ?? "");
|
|
16624
|
+
const at = before ? current.indexOf(before) : -1;
|
|
16625
|
+
return hunk(
|
|
16626
|
+
before,
|
|
16627
|
+
String(value2?.new_string ?? ""),
|
|
16628
|
+
at < 0 ? 0 : current.slice(0, at).split("\n").length - 1
|
|
16629
|
+
);
|
|
16630
|
+
};
|
|
16631
|
+
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) : [];
|
|
16632
|
+
if (!hunks.length) return void 0;
|
|
16633
|
+
const diff = [`--- ${path}`, `+++ ${path}`, ...hunks.flatMap((item) => item.lines)].join("\n");
|
|
16634
|
+
if (Buffer.byteLength(diff) > MAX_PREVIEW) return void 0;
|
|
16635
|
+
return {
|
|
16636
|
+
path,
|
|
16637
|
+
diff,
|
|
16638
|
+
added: hunks.reduce((sum, item) => sum + item.added, 0),
|
|
16639
|
+
deleted: hunks.reduce((sum, item) => sum + item.deleted, 0)
|
|
16640
|
+
};
|
|
16641
|
+
}
|
|
16642
|
+
function remember(suggestions = []) {
|
|
16643
|
+
const updates = suggestions.filter(
|
|
16644
|
+
(update) => update?.type === "addRules" && update.behavior === "allow" || update?.type === "addDirectories"
|
|
16645
|
+
);
|
|
16646
|
+
const rule = updates.flatMap((update) => update.rules ?? [])[0];
|
|
16647
|
+
const folder = updates.flatMap((update) => update.directories ?? [])[0];
|
|
16648
|
+
const scope2 = rule ? String(rule.ruleContent ?? rule.toolName) : folder ? `access to ${basename2(String(folder))}` : "";
|
|
16649
|
+
if (scope2)
|
|
16650
|
+
return {
|
|
16651
|
+
label: `Always allow ${scope2.length > SCOPE ? `${scope2.slice(0, SCOPE - 1)}\u2026` : scope2}`,
|
|
16652
|
+
updates
|
|
16653
|
+
};
|
|
16654
|
+
const edits = suggestions.find(
|
|
16655
|
+
(update) => update?.type === "setMode" && update.mode === "acceptEdits"
|
|
16656
|
+
);
|
|
16657
|
+
return edits ? { label: "Allow all edits", updates: [edits] } : void 0;
|
|
16658
|
+
}
|
|
16659
|
+
function ask(id, request2, host) {
|
|
16660
|
+
const { tool, input } = request2;
|
|
16661
|
+
if (tool === "AskUserQuestion" && Array.isArray(input.questions))
|
|
16662
|
+
return questionApproval(id, input.questions, (item) => item.multiSelect === true);
|
|
16663
|
+
if (tool === "ExitPlanMode")
|
|
16664
|
+
return {
|
|
16665
|
+
id,
|
|
16666
|
+
title: "Approve this plan?",
|
|
16667
|
+
detail: host ? "" : "Approve it on your computer, or tell Claude what to change.",
|
|
16668
|
+
choices: [
|
|
16669
|
+
...host ? [{ id: "accept", label: "Approve" }] : [],
|
|
16670
|
+
{ id: "revise", label: "Keep planning", text: true }
|
|
16671
|
+
],
|
|
16672
|
+
plan: preview2(String(input.plan ?? ""))
|
|
16673
|
+
};
|
|
16674
|
+
const diff = change(tool, input);
|
|
16675
|
+
const always = remember(request2.suggestions);
|
|
16467
16676
|
return {
|
|
16468
16677
|
id,
|
|
16469
|
-
title:
|
|
16470
|
-
detail
|
|
16471
|
-
|
|
16678
|
+
title: tool === "Bash" ? "Allow command?" : EDITS.has(tool) ? "Allow file changes?" : `Allow ${request2.label ?? tool}?`,
|
|
16679
|
+
detail: preview2(
|
|
16680
|
+
[
|
|
16681
|
+
request2.description ?? (typeof input.description === "string" ? input.description : ""),
|
|
16682
|
+
typeof input.command === "string" ? input.command : diff ? "" : JSON.stringify(input, null, 2)
|
|
16683
|
+
].filter(Boolean).join("\n\n")
|
|
16684
|
+
),
|
|
16685
|
+
...diff ? { change: diff } : {},
|
|
16686
|
+
choices: [
|
|
16687
|
+
{ id: "accept", label: "Allow once" },
|
|
16688
|
+
...always ? [{ id: "always", label: always.label }] : [],
|
|
16689
|
+
{ id: "decline", label: "Reject" }
|
|
16690
|
+
]
|
|
16472
16691
|
};
|
|
16473
16692
|
}
|
|
16474
|
-
|
|
16475
|
-
|
|
16476
|
-
|
|
16477
|
-
|
|
16693
|
+
function decide(request2, approval2, reply) {
|
|
16694
|
+
const input = request2.input;
|
|
16695
|
+
if ("answers" in reply)
|
|
16696
|
+
return {
|
|
16697
|
+
behavior: "allow",
|
|
16698
|
+
updatedInput: {
|
|
16699
|
+
...input,
|
|
16700
|
+
answers: Object.fromEntries(
|
|
16701
|
+
(approval2.questions ?? []).map((item, index) => [
|
|
16702
|
+
item.question,
|
|
16703
|
+
reply.answers[index].join(", ")
|
|
16704
|
+
])
|
|
16705
|
+
)
|
|
16706
|
+
}
|
|
16707
|
+
};
|
|
16708
|
+
if (reply.choice === "accept")
|
|
16709
|
+
return {
|
|
16710
|
+
behavior: "allow",
|
|
16711
|
+
updatedInput: input,
|
|
16712
|
+
...approval2.plan === void 0 ? {} : { updatedPermissions: [{ type: "setMode", mode: "default", destination: "session" }] }
|
|
16713
|
+
};
|
|
16714
|
+
if (reply.choice === "always")
|
|
16715
|
+
return {
|
|
16716
|
+
behavior: "allow",
|
|
16717
|
+
updatedInput: input,
|
|
16718
|
+
updatedPermissions: remember(request2.suggestions)?.updates ?? []
|
|
16719
|
+
};
|
|
16720
|
+
if (reply.choice === "revise")
|
|
16721
|
+
return { behavior: "deny", message: reply.text?.trim() || "Keep planning." };
|
|
16722
|
+
return {
|
|
16723
|
+
behavior: "deny",
|
|
16724
|
+
message: reply.choice === "skip" ? "The person skipped these questions." : "The person rejected this from their phone."
|
|
16725
|
+
};
|
|
16726
|
+
}
|
|
16727
|
+
var MAX_SESSIONS = 100;
|
|
16728
|
+
var MAX_REQUESTS = 1e3;
|
|
16729
|
+
var MAX_PROMPT = 4 * 1024 * 1024;
|
|
16730
|
+
var SESSION = /^\/sessions\/([\w-]{1,100})(\/prompts)?$/;
|
|
16731
|
+
var STATUS = {
|
|
16732
|
+
busy: "working",
|
|
16733
|
+
idle: "idle",
|
|
16734
|
+
shell: "idle",
|
|
16735
|
+
waiting: "attention"
|
|
16736
|
+
};
|
|
16737
|
+
async function registrations() {
|
|
16478
16738
|
if (process.env.DASHUP_LIVE_DISCOVERY === "0") return [];
|
|
16479
|
-
const
|
|
16480
|
-
|
|
16481
|
-
|
|
16482
|
-
|
|
16483
|
-
if (homes.size >= MAX_SOCKETS) break;
|
|
16739
|
+
const directory2 = join4(claudeHome(), "sessions");
|
|
16740
|
+
const names = await readdir(directory2).catch(() => []);
|
|
16741
|
+
const found = await Promise.all(
|
|
16742
|
+
names.filter((name) => /^\d+\.json$/.test(name)).slice(0, MAX_SESSIONS).map(async (name) => {
|
|
16484
16743
|
try {
|
|
16485
|
-
const
|
|
16486
|
-
|
|
16487
|
-
|
|
16488
|
-
const
|
|
16489
|
-
|
|
16490
|
-
const environment = await readFile(join4(directory2, "environ"), "utf8");
|
|
16491
|
-
const home = environment.split("\0").find((value2) => value2.startsWith("CODEX_HOME="))?.slice("CODEX_HOME=".length);
|
|
16492
|
-
if (home && isAbsolute2(home)) homes.add(home);
|
|
16744
|
+
const value2 = JSON.parse(await readFile(join4(directory2, name), "utf8"));
|
|
16745
|
+
if (typeof value2.sessionId !== "string" || typeof value2.cwd !== "string") return [];
|
|
16746
|
+
process.kill(value2.pid, 0);
|
|
16747
|
+
const binary2 = await readlink(`/proc/${value2.pid}/exe`).catch(() => "");
|
|
16748
|
+
return [{ ...value2, command: /[\\/]claude(?:\.exe)?$/.test(binary2) ? binary2 : "claude" }];
|
|
16493
16749
|
} catch {
|
|
16750
|
+
return [];
|
|
16494
16751
|
}
|
|
16495
|
-
}
|
|
16496
|
-
|
|
16497
|
-
return
|
|
16498
|
-
}
|
|
16499
|
-
function messages2(thread) {
|
|
16500
|
-
if (!Array.isArray(thread.turns)) return [];
|
|
16501
|
-
return thread.turns.flatMap((turn) => Array.isArray(turn?.items) ? turn.items.flatMap((item) => {
|
|
16502
|
-
if (typeof item?.id !== "string") return [];
|
|
16503
|
-
if (item.type === "userMessage" && Array.isArray(item.content)) {
|
|
16504
|
-
const text3 = item.content.filter((part) => part?.type === "text" && typeof part.text === "string").map((part) => part.text).join("\n");
|
|
16505
|
-
return text3 ? [{ id: item.id, role: "user", text: text3 }] : [];
|
|
16506
|
-
}
|
|
16507
|
-
if (item.type === "agentMessage" && typeof item.text === "string")
|
|
16508
|
-
return [{ id: item.id, role: "assistant", text: item.text }];
|
|
16509
|
-
if (item.type === "commandExecution" && typeof item.command === "string")
|
|
16510
|
-
return [
|
|
16511
|
-
{
|
|
16512
|
-
id: item.id,
|
|
16513
|
-
role: "tool",
|
|
16514
|
-
text: [item.command, item.aggregatedOutput].filter((value2) => typeof value2 === "string").join("\n\n")
|
|
16515
|
-
}
|
|
16516
|
-
];
|
|
16517
|
-
return [];
|
|
16518
|
-
}) : []);
|
|
16752
|
+
})
|
|
16753
|
+
);
|
|
16754
|
+
return found.flat();
|
|
16519
16755
|
}
|
|
16520
|
-
|
|
16521
|
-
|
|
16522
|
-
|
|
16523
|
-
|
|
16524
|
-
(flag) => flag === "waitingOnApproval" || flag === "waitingOnUserInput"
|
|
16525
|
-
) ? "attention" : "working";
|
|
16756
|
+
var Claude = class {
|
|
16757
|
+
constructor(changed, socket = join4(dirname4(configPath()), "claude.sock")) {
|
|
16758
|
+
this.changed = changed;
|
|
16759
|
+
this.socket = socket;
|
|
16526
16760
|
}
|
|
16527
|
-
return "unknown";
|
|
16528
|
-
}
|
|
16529
|
-
var Client = class {
|
|
16530
|
-
constructor(path, changed) {
|
|
16531
|
-
this.path = path;
|
|
16532
|
-
this.changed = changed;
|
|
16533
|
-
}
|
|
16534
|
-
path;
|
|
16535
16761
|
changed;
|
|
16536
16762
|
socket;
|
|
16537
|
-
|
|
16538
|
-
stopped = false;
|
|
16539
|
-
sequence = 0;
|
|
16540
|
-
controls = false;
|
|
16763
|
+
watcher;
|
|
16541
16764
|
notify;
|
|
16542
|
-
|
|
16543
|
-
|
|
16765
|
+
server;
|
|
16766
|
+
streams = /* @__PURE__ */ new Map();
|
|
16767
|
+
prompts = /* @__PURE__ */ new Map();
|
|
16544
16768
|
requests = /* @__PURE__ */ new Map();
|
|
16545
|
-
|
|
16546
|
-
|
|
16547
|
-
|
|
16548
|
-
|
|
16549
|
-
|
|
16550
|
-
|
|
16551
|
-
|
|
16552
|
-
|
|
16553
|
-
|
|
16554
|
-
|
|
16555
|
-
|
|
16556
|
-
|
|
16557
|
-
|
|
16558
|
-
|
|
16559
|
-
|
|
16560
|
-
|
|
16561
|
-
|
|
16562
|
-
|
|
16563
|
-
|
|
16564
|
-
|
|
16565
|
-
}
|
|
16566
|
-
|
|
16567
|
-
|
|
16568
|
-
|
|
16569
|
-
|
|
16570
|
-
|
|
16571
|
-
|
|
16572
|
-
|
|
16573
|
-
|
|
16574
|
-
|
|
16575
|
-
|
|
16576
|
-
|
|
16577
|
-
|
|
16578
|
-
|
|
16579
|
-
|
|
16580
|
-
|
|
16581
|
-
|
|
16582
|
-
|
|
16583
|
-
|
|
16584
|
-
|
|
16585
|
-
item: params.itemId,
|
|
16586
|
-
text: (known && known.item === params.itemId ? known.text : "") + params.delta
|
|
16587
|
-
});
|
|
16588
|
-
}
|
|
16589
|
-
if (message.method === "serverRequest/resolved") {
|
|
16590
|
-
for (const [id, pending] of this.approvals)
|
|
16591
|
-
if (pending.requestId === message.params?.requestId) this.approvals.delete(id);
|
|
16592
|
-
}
|
|
16593
|
-
if ((message.method === "item/commandExecution/requestApproval" || message.method === "item/fileChange/requestApproval") && (typeof message.id === "string" || typeof message.id === "number") && typeof message.params?.threadId === "string") {
|
|
16594
|
-
const id = randomUUID2();
|
|
16595
|
-
this.approvals.set(id, {
|
|
16596
|
-
requestId: message.id,
|
|
16597
|
-
threadId: params.threadId,
|
|
16598
|
-
approval: approval(id, message.method, params)
|
|
16599
|
-
});
|
|
16600
|
-
}
|
|
16601
|
-
if (/^(thread|turn|item|serverRequest)\//.test(message.method) && !this.notify) {
|
|
16602
|
-
this.notify = setTimeout(() => {
|
|
16603
|
-
this.notify = void 0;
|
|
16604
|
-
this.changed();
|
|
16605
|
-
}, 100);
|
|
16606
|
-
this.notify.unref();
|
|
16607
|
-
}
|
|
16608
|
-
});
|
|
16609
|
-
socket.on("close", () => {
|
|
16610
|
-
if (this.socket !== socket) return;
|
|
16611
|
-
this.socket = void 0;
|
|
16612
|
-
this.controls = false;
|
|
16613
|
-
this.approvals.clear();
|
|
16614
|
-
for (const pending of this.pending.values()) {
|
|
16615
|
-
clearTimeout(pending.timer);
|
|
16616
|
-
pending.reject(new Error("Codex control socket disconnected"));
|
|
16617
|
-
}
|
|
16618
|
-
this.pending.clear();
|
|
16619
|
-
if (!this.stopped) this.changed();
|
|
16620
|
-
});
|
|
16621
|
-
await new Promise((resolve3, reject) => {
|
|
16622
|
-
socket.once("open", resolve3);
|
|
16623
|
-
socket.once("error", reject);
|
|
16624
|
-
socket.once("close", () => reject(new Error("Codex control socket closed before initialization")));
|
|
16625
|
-
});
|
|
16626
|
-
const initialized = await this.call("initialize", {
|
|
16627
|
-
clientInfo: { name: "dashup", title: "Dashup", version },
|
|
16628
|
-
capabilities: { experimentalApi: false, requestAttestation: false }
|
|
16769
|
+
async refresh() {
|
|
16770
|
+
this.watch();
|
|
16771
|
+
return (await registrations()).map((session) => {
|
|
16772
|
+
const plugin = this.streams.has(session.sessionId);
|
|
16773
|
+
const models2 = claudeModels(this.changed, session.command, session.version);
|
|
16774
|
+
const approvals = [...this.prompts.values()].filter((prompt) => prompt.session === session.sessionId).map((prompt) => prompt.approval);
|
|
16775
|
+
return {
|
|
16776
|
+
id: session.sessionId,
|
|
16777
|
+
harness: "claude",
|
|
16778
|
+
cwd: session.cwd,
|
|
16779
|
+
updatedAt: new Date(session.updatedAt ?? 0).toISOString(),
|
|
16780
|
+
status: approvals.length ? "attention" : STATUS[session.status ?? ""] ?? "unknown",
|
|
16781
|
+
messages: [],
|
|
16782
|
+
approvals,
|
|
16783
|
+
approve: approvals.length ? (requestId, approvalId, reply) => this.once(requestId, async () => {
|
|
16784
|
+
const prompt = this.prompts.get(approvalId);
|
|
16785
|
+
if (prompt?.session !== session.sessionId)
|
|
16786
|
+
throw new Error("Claude Code approval is no longer pending");
|
|
16787
|
+
prompt.answer(decide(prompt.request, prompt.approval, reply));
|
|
16788
|
+
}) : void 0,
|
|
16789
|
+
send: plugin ? (requestId, text3, now) => this.once(requestId, () => this.deliver(session.sessionId, now ? { text: text3, now } : { text: text3 })) : void 0,
|
|
16790
|
+
stop: plugin ? (requestId) => this.once(requestId, () => this.deliver(session.sessionId, { now: true })) : void 0,
|
|
16791
|
+
now: plugin,
|
|
16792
|
+
options: (recorded) => claudeOptions(models2, recorded),
|
|
16793
|
+
configure: plugin ? (requestId, option2, value2) => this.once(requestId, () => this.deliver(session.sessionId, { text: COMMANDS[option2](value2) })) : void 0
|
|
16794
|
+
};
|
|
16795
|
+
});
|
|
16796
|
+
}
|
|
16797
|
+
// Only the connector serves the plugin; one-off commands such as `dashup doctor` read alone.
|
|
16798
|
+
async listen() {
|
|
16799
|
+
if (this.server || process.env.DASHUP_LIVE_DISCOVERY === "0") return;
|
|
16800
|
+
const server = this.server = createServer2((request2, response) => {
|
|
16801
|
+
const [, id, prompt] = SESSION.exec(request2.url ?? "") ?? [];
|
|
16802
|
+
if (id && prompt && request2.method === "POST") return this.prompt(id, request2, response);
|
|
16803
|
+
if (!id || prompt || request2.method !== "GET") return void response.writeHead(404).end();
|
|
16804
|
+
this.streams.get(id)?.end();
|
|
16805
|
+
this.streams.set(id, response);
|
|
16806
|
+
response.once("close", () => {
|
|
16807
|
+
if (this.streams.get(id) === response) this.streams.delete(id);
|
|
16808
|
+
this.changed();
|
|
16629
16809
|
});
|
|
16630
|
-
|
|
16631
|
-
|
|
16632
|
-
|
|
16633
|
-
})
|
|
16634
|
-
|
|
16635
|
-
|
|
16636
|
-
|
|
16637
|
-
|
|
16638
|
-
|
|
16639
|
-
}
|
|
16640
|
-
this.connecting = void 0;
|
|
16641
|
-
}
|
|
16810
|
+
response.writeHead(200, { "Content-Type": "application/x-ndjson" }).flushHeaders();
|
|
16811
|
+
this.changed();
|
|
16812
|
+
});
|
|
16813
|
+
await mkdir(dirname4(this.socket), { recursive: true, mode: 448 });
|
|
16814
|
+
await rm(this.socket, { force: true });
|
|
16815
|
+
if (this.server !== server) return;
|
|
16816
|
+
await new Promise((resolve3, reject) => {
|
|
16817
|
+
server.once("error", reject);
|
|
16818
|
+
server.listen(this.socket, resolve3);
|
|
16819
|
+
});
|
|
16642
16820
|
}
|
|
16643
|
-
|
|
16644
|
-
|
|
16645
|
-
|
|
16646
|
-
|
|
16647
|
-
|
|
16648
|
-
|
|
16649
|
-
|
|
16650
|
-
|
|
16651
|
-
|
|
16652
|
-
|
|
16653
|
-
|
|
16654
|
-
|
|
16655
|
-
|
|
16656
|
-
|
|
16657
|
-
|
|
16658
|
-
|
|
16659
|
-
|
|
16660
|
-
|
|
16821
|
+
// Asks the phone what the session's permission request asks the person.
|
|
16822
|
+
prompt(session, request2, response) {
|
|
16823
|
+
const chunks = [];
|
|
16824
|
+
let bytes = 0;
|
|
16825
|
+
request2.on("data", (chunk) => {
|
|
16826
|
+
bytes += chunk.length;
|
|
16827
|
+
if (bytes > MAX_PROMPT) request2.destroy();
|
|
16828
|
+
else chunks.push(chunk);
|
|
16829
|
+
});
|
|
16830
|
+
request2.once("end", () => {
|
|
16831
|
+
let asked;
|
|
16832
|
+
try {
|
|
16833
|
+
asked = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
16834
|
+
} catch {
|
|
16835
|
+
}
|
|
16836
|
+
if (typeof asked?.tool !== "string" || !asked.input || typeof asked.input !== "object")
|
|
16837
|
+
return void response.writeHead(400).end();
|
|
16838
|
+
const id = randomUUID2();
|
|
16839
|
+
const prompted = {
|
|
16840
|
+
tool: asked.tool,
|
|
16841
|
+
input: asked.input,
|
|
16842
|
+
...Array.isArray(asked.suggestions) ? { suggestions: asked.suggestions } : {}
|
|
16843
|
+
};
|
|
16844
|
+
this.prompts.set(id, {
|
|
16845
|
+
session,
|
|
16846
|
+
request: prompted,
|
|
16847
|
+
approval: ask(id, prompted, false),
|
|
16848
|
+
answer: (decision) => {
|
|
16849
|
+
this.prompts.delete(id);
|
|
16850
|
+
response.end(JSON.stringify(decision));
|
|
16851
|
+
this.changed();
|
|
16661
16852
|
}
|
|
16662
16853
|
});
|
|
16854
|
+
response.once("close", () => {
|
|
16855
|
+
if (this.prompts.delete(id)) this.changed();
|
|
16856
|
+
});
|
|
16857
|
+
response.writeHead(200, { "Content-Type": "application/json" }).flushHeaders();
|
|
16858
|
+
this.changed();
|
|
16663
16859
|
});
|
|
16664
16860
|
}
|
|
16861
|
+
deliver(id, delivery) {
|
|
16862
|
+
const stream = this.streams.get(id);
|
|
16863
|
+
if (!stream) return Promise.reject(new Error("Claude Code is not taking messages"));
|
|
16864
|
+
return new Promise((resolve3, reject) => stream.write(`${JSON.stringify(delivery)}
|
|
16865
|
+
`, (error) => error ? reject(error) : resolve3()));
|
|
16866
|
+
}
|
|
16867
|
+
// Each request delivers at most once.
|
|
16665
16868
|
once(id, action) {
|
|
16666
16869
|
const known = this.requests.get(id);
|
|
16667
16870
|
if (known) return known;
|
|
16668
|
-
if (this.requests.size >=
|
|
16669
|
-
|
|
16871
|
+
if (this.requests.size >= MAX_REQUESTS)
|
|
16872
|
+
this.requests.delete(this.requests.keys().next().value);
|
|
16670
16873
|
const result = action();
|
|
16671
16874
|
this.requests.set(id, result);
|
|
16672
16875
|
return result;
|
|
16673
16876
|
}
|
|
16674
|
-
|
|
16675
|
-
|
|
16676
|
-
|
|
16677
|
-
|
|
16678
|
-
|
|
16679
|
-
|
|
16680
|
-
|
|
16681
|
-
|
|
16682
|
-
|
|
16683
|
-
|
|
16684
|
-
|
|
16685
|
-
|
|
16686
|
-
|
|
16687
|
-
|
|
16688
|
-
|
|
16689
|
-
|
|
16690
|
-
|
|
16691
|
-
try {
|
|
16692
|
-
const read = await this.call("thread/read", { threadId: id, includeTurns: true });
|
|
16693
|
-
const thread = read.thread;
|
|
16694
|
-
if (thread?.id !== id || typeof thread.cwd !== "string" || !thread.cwd) continue;
|
|
16695
|
-
const approvals = [...this.approvals.values()].filter((value2) => value2.threadId === id).map((value2) => value2.approval);
|
|
16696
|
-
sessions.push({
|
|
16697
|
-
id,
|
|
16698
|
-
harness: "codex",
|
|
16699
|
-
cwd: thread.cwd,
|
|
16700
|
-
title: typeof thread.name === "string" && thread.name ? thread.name : typeof thread.preview === "string" && thread.preview ? thread.preview : "Codex thread",
|
|
16701
|
-
updatedAt: new Date(Number(thread.updatedAt ?? 0) * 1e3).toISOString(),
|
|
16702
|
-
status: approvals.length ? "attention" : status(thread),
|
|
16703
|
-
messages: messages2(thread),
|
|
16704
|
-
typing: this.typing.get(id)?.text,
|
|
16705
|
-
approvals,
|
|
16706
|
-
// An interrupt for a finished turn is never answered, so the active turn is read first.
|
|
16707
|
-
stop: this.controls ? (requestId) => this.once(requestId, async () => {
|
|
16708
|
-
const native = (await this.call("thread/read", { threadId: id, includeTurns: true })).thread;
|
|
16709
|
-
const turn = native?.status?.type === "active" && Array.isArray(native.turns) ? native.turns.at(-1) : void 0;
|
|
16710
|
-
if (turn?.status !== "inProgress" || typeof turn.id !== "string")
|
|
16711
|
-
throw new Error("Nothing is running in this Codex thread");
|
|
16712
|
-
await this.call("turn/interrupt", { threadId: id, turnId: turn.id });
|
|
16713
|
-
this.changed();
|
|
16714
|
-
}) : void 0,
|
|
16715
|
-
send: this.controls && thread.status?.type === "idle" ? (requestId, text3) => this.once(requestId, async () => {
|
|
16716
|
-
if (!text3.trim() || Buffer.byteLength(text3) > MAX_BYTES / 2)
|
|
16717
|
-
throw new Error("Message is empty or too large");
|
|
16718
|
-
const current = await this.call("thread/loaded/list", { limit: MAX_SESSIONS });
|
|
16719
|
-
if (!Array.isArray(current.data) || !current.data.includes(id))
|
|
16720
|
-
throw new Error("Codex thread is no longer loaded");
|
|
16721
|
-
const native = (await this.call("thread/read", { threadId: id, includeTurns: false })).thread;
|
|
16722
|
-
if (native?.id !== id || native.status?.type !== "idle")
|
|
16723
|
-
throw new Error("Codex thread is busy or unavailable");
|
|
16724
|
-
await this.call("turn/start", {
|
|
16725
|
-
threadId: id,
|
|
16726
|
-
clientUserMessageId: requestId,
|
|
16727
|
-
input: [{ type: "text", text: text3, text_elements: [] }]
|
|
16728
|
-
});
|
|
16729
|
-
this.changed();
|
|
16730
|
-
}) : void 0,
|
|
16731
|
-
approve: approvals.length ? (requestId, approvalId, choice) => this.once(requestId, async () => {
|
|
16732
|
-
const approval3 = this.approvals.get(approvalId);
|
|
16733
|
-
const socket = this.socket;
|
|
16734
|
-
if (!approval3 || approval3.threadId !== id || !approval3.approval.choices.some((value2) => value2.id === choice) || socket?.readyState !== WebSocket2.OPEN)
|
|
16735
|
-
throw new Error("Codex approval is no longer pending");
|
|
16736
|
-
await new Promise((resolve3, reject) => socket.send(
|
|
16737
|
-
JSON.stringify({ id: approval3.requestId, result: { decision: choice } }),
|
|
16738
|
-
(error) => error ? reject(new Error("Codex approval could not be sent")) : resolve3()
|
|
16739
|
-
));
|
|
16740
|
-
this.approvals.delete(approvalId);
|
|
16741
|
-
this.changed();
|
|
16742
|
-
}) : void 0
|
|
16743
|
-
});
|
|
16744
|
-
} catch {
|
|
16745
|
-
}
|
|
16746
|
-
}
|
|
16747
|
-
return sessions;
|
|
16877
|
+
watch() {
|
|
16878
|
+
if (this.watcher || process.env.DASHUP_LIVE_DISCOVERY === "0") return;
|
|
16879
|
+
try {
|
|
16880
|
+
const watcher = this.watcher = watch2(join4(claudeHome(), "sessions"), () => {
|
|
16881
|
+
if (this.notify) return;
|
|
16882
|
+
this.notify = setTimeout(() => {
|
|
16883
|
+
this.notify = void 0;
|
|
16884
|
+
this.changed();
|
|
16885
|
+
}, 100);
|
|
16886
|
+
this.notify.unref();
|
|
16887
|
+
});
|
|
16888
|
+
watcher.on("error", () => {
|
|
16889
|
+
watcher.close();
|
|
16890
|
+
if (this.watcher === watcher) this.watcher = void 0;
|
|
16891
|
+
});
|
|
16892
|
+
} catch {
|
|
16893
|
+
}
|
|
16748
16894
|
}
|
|
16749
16895
|
stop() {
|
|
16750
|
-
this.stopped = true;
|
|
16751
16896
|
clearTimeout(this.notify);
|
|
16752
|
-
this.
|
|
16753
|
-
|
|
16754
|
-
|
|
16755
|
-
|
|
16756
|
-
|
|
16757
|
-
this.
|
|
16758
|
-
this.approvals.clear();
|
|
16759
|
-
}
|
|
16760
|
-
};
|
|
16761
|
-
var Codex = class {
|
|
16762
|
-
constructor(changed) {
|
|
16763
|
-
this.changed = changed;
|
|
16764
|
-
}
|
|
16765
|
-
changed;
|
|
16766
|
-
clients = /* @__PURE__ */ new Map();
|
|
16767
|
-
// Control sockets of daemons this connector started, which discovery may not look for.
|
|
16768
|
-
started = /* @__PURE__ */ new Set();
|
|
16769
|
-
// Codex's own daemon runs the threads Dashup starts or resumes, so they outlive this connector.
|
|
16770
|
-
async start(cwd, text3, saved) {
|
|
16771
|
-
const path = process.env.DASHUP_CODEX_SOCKET || join4(codexHome(), ...SOCKET_PARTS);
|
|
16772
|
-
await promisify2(execFile2)("codex", ["app-server", "daemon", "start"], {
|
|
16773
|
-
timeout: 6e4,
|
|
16774
|
-
shell: process.platform === "win32"
|
|
16775
|
-
});
|
|
16776
|
-
this.started.add(path);
|
|
16777
|
-
let client = this.clients.get(path);
|
|
16778
|
-
if (!client) {
|
|
16779
|
-
client = new Client(path, this.changed);
|
|
16780
|
-
this.clients.set(path, client);
|
|
16781
|
-
}
|
|
16782
|
-
return client.start(cwd, text3, saved);
|
|
16783
|
-
}
|
|
16784
|
-
async resume(id, cwd, text3) {
|
|
16785
|
-
await this.start(cwd, text3, id);
|
|
16786
|
-
}
|
|
16787
|
-
async refresh() {
|
|
16788
|
-
const paths = /* @__PURE__ */ new Set([...await sockets(), ...this.started]);
|
|
16789
|
-
for (const [path, client] of this.clients)
|
|
16790
|
-
if (!paths.has(path)) {
|
|
16791
|
-
client.stop();
|
|
16792
|
-
this.clients.delete(path);
|
|
16793
|
-
}
|
|
16794
|
-
const results = await Promise.allSettled(
|
|
16795
|
-
[...paths].map(async (path) => {
|
|
16796
|
-
let client = this.clients.get(path);
|
|
16797
|
-
if (!client) {
|
|
16798
|
-
client = new Client(path, this.changed);
|
|
16799
|
-
this.clients.set(path, client);
|
|
16800
|
-
}
|
|
16801
|
-
return client.read();
|
|
16802
|
-
})
|
|
16803
|
-
);
|
|
16804
|
-
return results.flatMap((result) => result.status === "fulfilled" ? result.value : []);
|
|
16805
|
-
}
|
|
16806
|
-
stop() {
|
|
16807
|
-
for (const client of this.clients.values()) client.stop();
|
|
16808
|
-
this.clients.clear();
|
|
16897
|
+
this.watcher?.close();
|
|
16898
|
+
this.watcher = void 0;
|
|
16899
|
+
this.prompts.clear();
|
|
16900
|
+
this.server?.closeAllConnections();
|
|
16901
|
+
this.server?.close();
|
|
16902
|
+
this.server = void 0;
|
|
16809
16903
|
}
|
|
16810
16904
|
};
|
|
16811
16905
|
|
|
16812
16906
|
// src/harness/headless.ts
|
|
16813
|
-
|
|
16814
|
-
|
|
16815
|
-
|
|
16816
|
-
|
|
16817
|
-
|
|
16818
|
-
|
|
16819
|
-
|
|
16820
|
-
|
|
16821
|
-
|
|
16822
|
-
import { createInterface as createInterface2 } from "node:readline";
|
|
16823
|
-
import { watch as watch2 } from "node:fs";
|
|
16824
|
-
import { mkdir, readdir as readdir2, readFile as readFile2, readlink, rm } from "node:fs/promises";
|
|
16825
|
-
import { createServer as createServer2 } from "node:http";
|
|
16826
|
-
var MODELS_MS = 3e4;
|
|
16827
|
-
var COMMANDS = {
|
|
16828
|
-
model: (value2) => `/model ${value2}`,
|
|
16829
|
-
fast: (value2) => `/fast ${value2 ? "on" : "off"}`,
|
|
16830
|
-
ultracode: (value2) => `/effort ultracode ${value2 ? "on" : "off"}`
|
|
16831
|
-
};
|
|
16832
|
-
var offered = /* @__PURE__ */ new Map();
|
|
16833
|
-
var asking = /* @__PURE__ */ new Set();
|
|
16834
|
-
var forgetModels = () => offered.clear();
|
|
16835
|
-
function claudeModels(changed, command2 = "claude", version2 = "") {
|
|
16836
|
-
const key = `${command2}\0${version2}`;
|
|
16837
|
-
if (!offered.has(key) && !asking.has(key)) {
|
|
16838
|
-
asking.add(key);
|
|
16839
|
-
void listModels(command2).then((models) => {
|
|
16840
|
-
asking.delete(key);
|
|
16841
|
-
if (!models.length) return;
|
|
16842
|
-
offered.set(key, models);
|
|
16843
|
-
changed();
|
|
16844
|
-
});
|
|
16845
|
-
}
|
|
16846
|
-
return offered.get(key) ?? [];
|
|
16847
|
-
}
|
|
16848
|
-
function listModels(command2) {
|
|
16849
|
-
return new Promise((resolve3) => {
|
|
16850
|
-
const child = spawn2(
|
|
16851
|
-
command2,
|
|
16852
|
-
["-p", "--input-format", "stream-json", "--output-format", "stream-json", "--verbose"],
|
|
16853
|
-
{ cwd: homedir5(), stdio: "pipe", shell: process.platform === "win32" }
|
|
16854
|
-
);
|
|
16855
|
-
const timer = setTimeout(() => child.kill(), MODELS_MS);
|
|
16856
|
-
child.stdin.on("error", () => {
|
|
16857
|
-
});
|
|
16858
|
-
child.once("error", () => resolve3([]));
|
|
16859
|
-
child.once("exit", () => {
|
|
16860
|
-
clearTimeout(timer);
|
|
16861
|
-
resolve3([]);
|
|
16862
|
-
});
|
|
16863
|
-
createInterface2({ input: child.stdout }).on("line", (line) => {
|
|
16864
|
-
try {
|
|
16865
|
-
const message = JSON.parse(line);
|
|
16866
|
-
if (message?.type !== "control_response" || message.response?.request_id !== "models")
|
|
16867
|
-
return;
|
|
16868
|
-
const models = message.response.response?.models;
|
|
16869
|
-
resolve3(
|
|
16870
|
-
(Array.isArray(models) ? models : []).filter(
|
|
16871
|
-
(model) => typeof model?.value === "string" && typeof model.displayName === "string"
|
|
16872
|
-
)
|
|
16873
|
-
);
|
|
16874
|
-
child.stdin.end();
|
|
16875
|
-
} catch {
|
|
16876
|
-
}
|
|
16877
|
-
});
|
|
16878
|
-
child.stdin.write(
|
|
16879
|
-
`${JSON.stringify({ type: "control_request", request_id: "models", request: { subtype: "initialize" } })}
|
|
16880
|
-
`
|
|
16881
|
-
);
|
|
16882
|
-
});
|
|
16883
|
-
}
|
|
16884
|
-
function claudeOptions(models, recorded) {
|
|
16885
|
-
const ran = typeof recorded.model === "string" ? recorded.model : "";
|
|
16886
|
-
const current = models.find((model) => model.value !== "default" && model.resolvedModel === ran) ?? models.find((model) => model.value === ran);
|
|
16887
|
-
const choices = models.map((model) => ({ value: model.value, name: model.displayName }));
|
|
16888
|
-
if (ran && !current) choices.push({ value: ran, name: ran });
|
|
16889
|
-
if (!choices.length) return [];
|
|
16890
|
-
return [
|
|
16891
|
-
{ id: "model", name: "Model", value: current?.value ?? ran, choices },
|
|
16892
|
-
...current?.supportsFastMode ? [{ id: "fast", name: "Fast", value: recorded.fast === true }] : [],
|
|
16893
|
-
...current?.supportedEffortLevels?.includes("xhigh") ? [{ id: "ultracode", name: "Ultracode", value: recorded.ultracode === true }] : []
|
|
16894
|
-
];
|
|
16895
|
-
}
|
|
16896
|
-
var MAX_SESSIONS2 = 100;
|
|
16897
|
-
var MAX_REQUESTS = 1e3;
|
|
16898
|
-
var SESSION = /^\/sessions\/([\w-]{1,100})$/;
|
|
16899
|
-
var STATUS = {
|
|
16900
|
-
busy: "working",
|
|
16901
|
-
idle: "idle",
|
|
16902
|
-
shell: "idle",
|
|
16903
|
-
waiting: "attention"
|
|
16904
|
-
};
|
|
16905
|
-
async function registrations() {
|
|
16906
|
-
if (process.env.DASHUP_LIVE_DISCOVERY === "0") return [];
|
|
16907
|
-
const directory2 = join5(claudeHome(), "sessions");
|
|
16908
|
-
const names = await readdir2(directory2).catch(() => []);
|
|
16909
|
-
const found = await Promise.all(
|
|
16910
|
-
names.filter((name) => /^\d+\.json$/.test(name)).slice(0, MAX_SESSIONS2).map(async (name) => {
|
|
16911
|
-
try {
|
|
16912
|
-
const value2 = JSON.parse(await readFile2(join5(directory2, name), "utf8"));
|
|
16913
|
-
if (typeof value2.sessionId !== "string" || typeof value2.cwd !== "string") return [];
|
|
16914
|
-
process.kill(value2.pid, 0);
|
|
16915
|
-
const binary2 = await readlink(`/proc/${value2.pid}/exe`).catch(() => "");
|
|
16916
|
-
return [{ ...value2, command: /[\\/]claude(?:\.exe)?$/.test(binary2) ? binary2 : "claude" }];
|
|
16917
|
-
} catch {
|
|
16918
|
-
return [];
|
|
16919
|
-
}
|
|
16920
|
-
})
|
|
16921
|
-
);
|
|
16922
|
-
return found.flat();
|
|
16923
|
-
}
|
|
16924
|
-
var Claude = class {
|
|
16925
|
-
constructor(changed, socket = join5(dirname4(configPath()), "claude.sock")) {
|
|
16907
|
+
var MAX_REQUESTS2 = 1e3;
|
|
16908
|
+
var NOTIFY_MS = 100;
|
|
16909
|
+
var SWITCHES = { fast: "fastMode", ultracode: "ultracode" };
|
|
16910
|
+
var Session = class {
|
|
16911
|
+
// A resumed session is `saved` and keeps the title its history gives.
|
|
16912
|
+
constructor(id, cwd, title2, changed, saved = false) {
|
|
16913
|
+
this.id = id;
|
|
16914
|
+
this.cwd = cwd;
|
|
16915
|
+
this.title = title2;
|
|
16926
16916
|
this.changed = changed;
|
|
16927
|
-
this.
|
|
16917
|
+
this.saved = saved;
|
|
16928
16918
|
}
|
|
16919
|
+
id;
|
|
16920
|
+
cwd;
|
|
16921
|
+
title;
|
|
16929
16922
|
changed;
|
|
16930
|
-
|
|
16931
|
-
|
|
16932
|
-
|
|
16933
|
-
|
|
16934
|
-
|
|
16923
|
+
saved;
|
|
16924
|
+
messages = [];
|
|
16925
|
+
status = "idle";
|
|
16926
|
+
updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
16927
|
+
turn;
|
|
16928
|
+
// Messages written to this turn's process that Claude Code has not finished answering.
|
|
16929
|
+
open = 0;
|
|
16930
|
+
// The text Claude Code is writing, as far as it has streamed.
|
|
16931
|
+
typing = "";
|
|
16932
|
+
approvals = /* @__PURE__ */ new Map();
|
|
16935
16933
|
requests = /* @__PURE__ */ new Map();
|
|
16936
|
-
|
|
16937
|
-
|
|
16938
|
-
|
|
16939
|
-
|
|
16940
|
-
|
|
16941
|
-
return {
|
|
16942
|
-
id: session.sessionId,
|
|
16943
|
-
harness: "claude",
|
|
16944
|
-
cwd: session.cwd,
|
|
16945
|
-
updatedAt: new Date(session.updatedAt ?? 0).toISOString(),
|
|
16946
|
-
status: STATUS[session.status ?? ""] ?? "unknown",
|
|
16947
|
-
messages: [],
|
|
16948
|
-
approvals: [],
|
|
16949
|
-
send: plugin ? (requestId, text3, now) => this.once(requestId, () => this.deliver(session.sessionId, now ? { text: text3, now } : { text: text3 })) : void 0,
|
|
16950
|
-
stop: plugin ? (requestId) => this.once(requestId, () => this.deliver(session.sessionId, { now: true })) : void 0,
|
|
16951
|
-
now: plugin,
|
|
16952
|
-
options: (recorded) => claudeOptions(models, recorded),
|
|
16953
|
-
configure: plugin ? (requestId, option2, value2) => this.once(requestId, () => this.deliver(session.sessionId, { text: COMMANDS[option2](value2) })) : void 0
|
|
16954
|
-
};
|
|
16955
|
-
});
|
|
16934
|
+
// What the person chose for this session, applied to each turn as it starts.
|
|
16935
|
+
chosen = {};
|
|
16936
|
+
write(value2) {
|
|
16937
|
+
this.turn?.stdin.write(`${JSON.stringify(value2)}
|
|
16938
|
+
`);
|
|
16956
16939
|
}
|
|
16957
|
-
|
|
16958
|
-
|
|
16959
|
-
|
|
16960
|
-
|
|
16961
|
-
|
|
16962
|
-
if (!id) return void response.writeHead(404).end();
|
|
16963
|
-
this.streams.get(id)?.end();
|
|
16964
|
-
this.streams.set(id, response);
|
|
16965
|
-
response.once("close", () => {
|
|
16966
|
-
if (this.streams.get(id) === response) this.streams.delete(id);
|
|
16967
|
-
this.changed();
|
|
16968
|
-
});
|
|
16969
|
-
response.writeHead(200, { "Content-Type": "application/x-ndjson" }).flushHeaders();
|
|
16970
|
-
this.changed();
|
|
16971
|
-
});
|
|
16972
|
-
await mkdir(dirname4(this.socket), { recursive: true, mode: 448 });
|
|
16973
|
-
await rm(this.socket, { force: true });
|
|
16974
|
-
if (this.server !== server) return;
|
|
16975
|
-
await new Promise((resolve3, reject) => {
|
|
16976
|
-
server.once("error", reject);
|
|
16977
|
-
server.listen(this.socket, resolve3);
|
|
16940
|
+
interrupt() {
|
|
16941
|
+
this.write({
|
|
16942
|
+
type: "control_request",
|
|
16943
|
+
request_id: randomUUID3(),
|
|
16944
|
+
request: { subtype: "interrupt" }
|
|
16978
16945
|
});
|
|
16979
16946
|
}
|
|
16980
|
-
|
|
16981
|
-
|
|
16982
|
-
|
|
16983
|
-
|
|
16984
|
-
|
|
16947
|
+
apply(option2, value2) {
|
|
16948
|
+
this.write({
|
|
16949
|
+
type: "control_request",
|
|
16950
|
+
request_id: randomUUID3(),
|
|
16951
|
+
request: option2 === "model" ? { subtype: "set_model", model: value2 } : { subtype: "apply_flag_settings", settings: { [SWITCHES[option2]]: value2 } }
|
|
16952
|
+
});
|
|
16985
16953
|
}
|
|
16986
|
-
// Each request delivers at most once.
|
|
16987
16954
|
once(id, action) {
|
|
16988
16955
|
const known = this.requests.get(id);
|
|
16989
16956
|
if (known) return known;
|
|
16990
|
-
if (this.requests.size >=
|
|
16991
|
-
|
|
16992
|
-
const result = action();
|
|
16993
|
-
this.requests.set(id, result);
|
|
16994
|
-
return result;
|
|
16995
|
-
}
|
|
16996
|
-
watch() {
|
|
16997
|
-
if (this.watcher || process.env.DASHUP_LIVE_DISCOVERY === "0") return;
|
|
16998
|
-
try {
|
|
16999
|
-
const watcher = this.watcher = watch2(join5(claudeHome(), "sessions"), () => {
|
|
17000
|
-
if (this.notify) return;
|
|
17001
|
-
this.notify = setTimeout(() => {
|
|
17002
|
-
this.notify = void 0;
|
|
17003
|
-
this.changed();
|
|
17004
|
-
}, 100);
|
|
17005
|
-
this.notify.unref();
|
|
17006
|
-
});
|
|
17007
|
-
watcher.on("error", () => {
|
|
17008
|
-
watcher.close();
|
|
17009
|
-
if (this.watcher === watcher) this.watcher = void 0;
|
|
17010
|
-
});
|
|
17011
|
-
} catch {
|
|
17012
|
-
}
|
|
17013
|
-
}
|
|
17014
|
-
stop() {
|
|
17015
|
-
clearTimeout(this.notify);
|
|
17016
|
-
this.watcher?.close();
|
|
17017
|
-
this.watcher = void 0;
|
|
17018
|
-
this.server?.closeAllConnections();
|
|
17019
|
-
this.server?.close();
|
|
17020
|
-
this.server = void 0;
|
|
17021
|
-
}
|
|
17022
|
-
};
|
|
17023
|
-
|
|
17024
|
-
// src/harness/headless.ts
|
|
17025
|
-
var CHOICES = [
|
|
17026
|
-
{ id: "accept", label: "Allow once" },
|
|
17027
|
-
{ id: "decline", label: "Reject" }
|
|
17028
|
-
];
|
|
17029
|
-
var EDITS = /* @__PURE__ */ new Set(["Edit", "MultiEdit", "NotebookEdit", "Write"]);
|
|
17030
|
-
var MAX_REQUESTS2 = 1e3;
|
|
17031
|
-
var NOTIFY_MS = 100;
|
|
17032
|
-
var SWITCHES = { fast: "fastMode", ultracode: "ultracode" };
|
|
17033
|
-
function hunk(before, after, offset = 0) {
|
|
17034
|
-
const old = before ? before.replace(/\n$/, "").split("\n") : [];
|
|
17035
|
-
const next = after ? after.replace(/\n$/, "").split("\n") : [];
|
|
17036
|
-
let start2 = 0;
|
|
17037
|
-
while (start2 < old.length && start2 < next.length && old[start2] === next[start2]) start2++;
|
|
17038
|
-
let end = 0;
|
|
17039
|
-
while (end < old.length - start2 && end < next.length - start2 && old[old.length - 1 - end] === next[next.length - 1 - end])
|
|
17040
|
-
end++;
|
|
17041
|
-
const removed = old.slice(start2, old.length - end);
|
|
17042
|
-
const added2 = next.slice(start2, next.length - end);
|
|
17043
|
-
return {
|
|
17044
|
-
lines: [
|
|
17045
|
-
`@@ -${offset + start2 + 1},${removed.length} +${offset + start2 + 1},${added2.length} @@`,
|
|
17046
|
-
...old.slice(Math.max(0, start2 - 3), start2).map((line) => ` ${line}`),
|
|
17047
|
-
...removed.map((line) => `-${line}`),
|
|
17048
|
-
...added2.map((line) => `+${line}`),
|
|
17049
|
-
...old.slice(old.length - end, old.length - end + 3).map((line) => ` ${line}`)
|
|
17050
|
-
],
|
|
17051
|
-
added: added2.length,
|
|
17052
|
-
deleted: removed.length
|
|
17053
|
-
};
|
|
17054
|
-
}
|
|
17055
|
-
function change(tool, input) {
|
|
17056
|
-
const path = input.file_path;
|
|
17057
|
-
if (typeof path !== "string") return void 0;
|
|
17058
|
-
let current = "";
|
|
17059
|
-
try {
|
|
17060
|
-
current = readFileSync3(path, "utf8");
|
|
17061
|
-
} catch {
|
|
17062
|
-
}
|
|
17063
|
-
const edit = (value2) => {
|
|
17064
|
-
const before = String(value2?.old_string ?? "");
|
|
17065
|
-
const at = before ? current.indexOf(before) : -1;
|
|
17066
|
-
return hunk(
|
|
17067
|
-
before,
|
|
17068
|
-
String(value2?.new_string ?? ""),
|
|
17069
|
-
at < 0 ? 0 : current.slice(0, at).split("\n").length - 1
|
|
17070
|
-
);
|
|
17071
|
-
};
|
|
17072
|
-
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) : [];
|
|
17073
|
-
if (!hunks.length) return void 0;
|
|
17074
|
-
const diff = [`--- ${path}`, `+++ ${path}`, ...hunks.flatMap((item) => item.lines)].join("\n");
|
|
17075
|
-
if (Buffer.byteLength(diff) > MAX_PREVIEW) return void 0;
|
|
17076
|
-
return {
|
|
17077
|
-
path,
|
|
17078
|
-
diff,
|
|
17079
|
-
added: hunks.reduce((sum, item) => sum + item.added, 0),
|
|
17080
|
-
deleted: hunks.reduce((sum, item) => sum + item.deleted, 0)
|
|
17081
|
-
};
|
|
17082
|
-
}
|
|
17083
|
-
function approval2(id, request2) {
|
|
17084
|
-
const input = request2.input ?? {};
|
|
17085
|
-
const tool = String(request2.display_name ?? request2.tool_name ?? "a tool");
|
|
17086
|
-
const diff = change(request2.tool_name, input);
|
|
17087
|
-
let detail = [
|
|
17088
|
-
typeof request2.description === "string" ? request2.description : "",
|
|
17089
|
-
typeof input.command === "string" ? input.command : diff ? "" : JSON.stringify(input, null, 2)
|
|
17090
|
-
].filter(Boolean).join("\n\n");
|
|
17091
|
-
if (Buffer.byteLength(detail) > MAX_PREVIEW)
|
|
17092
|
-
detail = Buffer.from(detail).subarray(0, MAX_PREVIEW).toString("utf8");
|
|
17093
|
-
return {
|
|
17094
|
-
id,
|
|
17095
|
-
title: request2.tool_name === "Bash" ? "Allow command?" : EDITS.has(request2.tool_name) ? "Allow file changes?" : `Allow ${tool}?`,
|
|
17096
|
-
detail,
|
|
17097
|
-
...diff ? { change: diff } : {},
|
|
17098
|
-
choices: CHOICES.map((choice) => ({ ...choice }))
|
|
17099
|
-
};
|
|
17100
|
-
}
|
|
17101
|
-
var Session = class {
|
|
17102
|
-
// A resumed session is `saved` and keeps the title its history gives.
|
|
17103
|
-
constructor(id, cwd, title2, changed, saved = false) {
|
|
17104
|
-
this.id = id;
|
|
17105
|
-
this.cwd = cwd;
|
|
17106
|
-
this.title = title2;
|
|
17107
|
-
this.changed = changed;
|
|
17108
|
-
this.saved = saved;
|
|
17109
|
-
}
|
|
17110
|
-
id;
|
|
17111
|
-
cwd;
|
|
17112
|
-
title;
|
|
17113
|
-
changed;
|
|
17114
|
-
saved;
|
|
17115
|
-
messages = [];
|
|
17116
|
-
status = "idle";
|
|
17117
|
-
updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
17118
|
-
turn;
|
|
17119
|
-
// Messages written to this turn's process that Claude Code has not finished answering.
|
|
17120
|
-
open = 0;
|
|
17121
|
-
// The text Claude Code is writing, as far as it has streamed.
|
|
17122
|
-
typing = "";
|
|
17123
|
-
approvals = /* @__PURE__ */ new Map();
|
|
17124
|
-
requests = /* @__PURE__ */ new Map();
|
|
17125
|
-
// What the person chose for this session, applied to each turn as it starts.
|
|
17126
|
-
chosen = {};
|
|
17127
|
-
write(value2) {
|
|
17128
|
-
this.turn?.stdin.write(`${JSON.stringify(value2)}
|
|
17129
|
-
`);
|
|
17130
|
-
}
|
|
17131
|
-
interrupt() {
|
|
17132
|
-
this.write({
|
|
17133
|
-
type: "control_request",
|
|
17134
|
-
request_id: randomUUID3(),
|
|
17135
|
-
request: { subtype: "interrupt" }
|
|
17136
|
-
});
|
|
17137
|
-
}
|
|
17138
|
-
apply(option2, value2) {
|
|
17139
|
-
this.write({
|
|
17140
|
-
type: "control_request",
|
|
17141
|
-
request_id: randomUUID3(),
|
|
17142
|
-
request: option2 === "model" ? { subtype: "set_model", model: value2 } : { subtype: "apply_flag_settings", settings: { [SWITCHES[option2]]: value2 } }
|
|
17143
|
-
});
|
|
17144
|
-
}
|
|
17145
|
-
once(id, action) {
|
|
17146
|
-
const known = this.requests.get(id);
|
|
17147
|
-
if (known) return known;
|
|
17148
|
-
if (this.requests.size >= MAX_REQUESTS2)
|
|
17149
|
-
throw new Error("Claude Code command history is full; restart Dashup before sending");
|
|
16957
|
+
if (this.requests.size >= MAX_REQUESTS2)
|
|
16958
|
+
throw new Error("Claude Code command history is full; restart Dashup before sending");
|
|
17150
16959
|
const result = action();
|
|
17151
16960
|
this.requests.set(id, result);
|
|
17152
16961
|
return result;
|
|
@@ -17214,130 +17023,575 @@ var Session = class {
|
|
|
17214
17023
|
receive(line) {
|
|
17215
17024
|
let message;
|
|
17216
17025
|
try {
|
|
17217
|
-
message = JSON.parse(line);
|
|
17218
|
-
} catch {
|
|
17219
|
-
return false;
|
|
17026
|
+
message = JSON.parse(line);
|
|
17027
|
+
} catch {
|
|
17028
|
+
return false;
|
|
17029
|
+
}
|
|
17030
|
+
const init = message.type === "system" && message.subtype === "init";
|
|
17031
|
+
if (init) this.saved = true;
|
|
17032
|
+
else if (message.type === "assistant") {
|
|
17033
|
+
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");
|
|
17034
|
+
if (!text3) return false;
|
|
17035
|
+
this.messages.push({ id: String(message.uuid ?? randomUUID3()), role: "assistant", text: text3 });
|
|
17036
|
+
} else if (message.type === "control_request" && message.request?.subtype === "can_use_tool") {
|
|
17037
|
+
const id = randomUUID3();
|
|
17038
|
+
const asked = message.request;
|
|
17039
|
+
const request2 = {
|
|
17040
|
+
tool: String(asked.tool_name),
|
|
17041
|
+
input: asked.input ?? {},
|
|
17042
|
+
label: typeof asked.display_name === "string" ? asked.display_name : void 0,
|
|
17043
|
+
description: typeof asked.description === "string" ? asked.description : void 0,
|
|
17044
|
+
suggestions: Array.isArray(asked.permission_suggestions) ? asked.permission_suggestions : void 0
|
|
17045
|
+
};
|
|
17046
|
+
this.approvals.set(id, {
|
|
17047
|
+
control: String(message.request_id),
|
|
17048
|
+
request: request2,
|
|
17049
|
+
approval: ask(id, request2, true)
|
|
17050
|
+
});
|
|
17051
|
+
this.status = "attention";
|
|
17052
|
+
} else if (message.type === "control_cancel_request") {
|
|
17053
|
+
for (const [id, pending] of this.approvals)
|
|
17054
|
+
if (pending.control === message.request_id) this.approvals.delete(id);
|
|
17055
|
+
if (this.status === "attention" && !this.approvals.size) this.status = "working";
|
|
17056
|
+
} else if (message.type === "result") {
|
|
17057
|
+
if (--this.open <= 0) this.turn?.stdin.end();
|
|
17058
|
+
} else if (message.type === "stream_event" && !message.parent_tool_use_id) {
|
|
17059
|
+
const event = message.event;
|
|
17060
|
+
if (event?.type === "content_block_start") this.typing = "";
|
|
17061
|
+
else if (event?.type === "content_block_delta" && event.delta?.type === "text_delta")
|
|
17062
|
+
this.typing += String(event.delta.text ?? "");
|
|
17063
|
+
else return false;
|
|
17064
|
+
this.changed();
|
|
17065
|
+
return false;
|
|
17066
|
+
} else return false;
|
|
17067
|
+
this.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
17068
|
+
this.changed();
|
|
17069
|
+
return init;
|
|
17070
|
+
}
|
|
17071
|
+
live(models2) {
|
|
17072
|
+
return {
|
|
17073
|
+
id: this.id,
|
|
17074
|
+
cwd: this.cwd,
|
|
17075
|
+
title: this.title,
|
|
17076
|
+
harness: "claude",
|
|
17077
|
+
updatedAt: this.updatedAt,
|
|
17078
|
+
status: this.status,
|
|
17079
|
+
messages: [...this.messages],
|
|
17080
|
+
typing: this.typing,
|
|
17081
|
+
approvals: [...this.approvals.values()].map((pending) => pending.approval),
|
|
17082
|
+
send: (requestId, text3, now) => this.once(requestId, () => this.say(text3, now)),
|
|
17083
|
+
now: true,
|
|
17084
|
+
options: (recorded) => claudeOptions(models2, recorded),
|
|
17085
|
+
configure: (requestId, option2, value2) => this.once(requestId, async () => {
|
|
17086
|
+
this.chosen[option2] = value2;
|
|
17087
|
+
this.apply(option2, value2);
|
|
17088
|
+
}),
|
|
17089
|
+
stop: this.turn ? (requestId) => this.once(requestId, async () => {
|
|
17090
|
+
if (!this.turn) throw new Error("Nothing is running in this Claude Code session");
|
|
17091
|
+
this.interrupt();
|
|
17092
|
+
}) : void 0,
|
|
17093
|
+
approve: this.approvals.size ? (requestId, approvalId, reply) => this.once(requestId, async () => {
|
|
17094
|
+
const pending = this.approvals.get(approvalId);
|
|
17095
|
+
if (!pending || !this.turn)
|
|
17096
|
+
throw new Error("Claude Code approval is no longer pending");
|
|
17097
|
+
this.write({
|
|
17098
|
+
type: "control_response",
|
|
17099
|
+
response: {
|
|
17100
|
+
subtype: "success",
|
|
17101
|
+
request_id: pending.control,
|
|
17102
|
+
response: decide(pending.request, pending.approval, reply)
|
|
17103
|
+
}
|
|
17104
|
+
});
|
|
17105
|
+
this.approvals.delete(approvalId);
|
|
17106
|
+
if (!this.approvals.size) this.status = "working";
|
|
17107
|
+
this.changed();
|
|
17108
|
+
}) : void 0
|
|
17109
|
+
};
|
|
17110
|
+
}
|
|
17111
|
+
end() {
|
|
17112
|
+
this.turn?.kill();
|
|
17113
|
+
}
|
|
17114
|
+
};
|
|
17115
|
+
var Headless = class {
|
|
17116
|
+
constructor(changed) {
|
|
17117
|
+
this.changed = changed;
|
|
17118
|
+
}
|
|
17119
|
+
changed;
|
|
17120
|
+
sessions = /* @__PURE__ */ new Map();
|
|
17121
|
+
notify;
|
|
17122
|
+
soon = () => {
|
|
17123
|
+
if (this.notify) return;
|
|
17124
|
+
this.notify = setTimeout(() => {
|
|
17125
|
+
this.notify = void 0;
|
|
17126
|
+
this.changed();
|
|
17127
|
+
}, NOTIFY_MS);
|
|
17128
|
+
this.notify.unref();
|
|
17129
|
+
};
|
|
17130
|
+
// Starts a session in `cwd` with its first message, and resolves with the session's id.
|
|
17131
|
+
async start(cwd, text3) {
|
|
17132
|
+
const title2 = text3.trim().split("\n")[0].slice(0, 80);
|
|
17133
|
+
const session = new Session(randomUUID3(), cwd, title2, this.soon);
|
|
17134
|
+
this.sessions.set(session.id, session);
|
|
17135
|
+
try {
|
|
17136
|
+
await session.say(text3);
|
|
17137
|
+
} catch (error) {
|
|
17138
|
+
this.sessions.delete(session.id);
|
|
17139
|
+
throw error;
|
|
17140
|
+
}
|
|
17141
|
+
return session.id;
|
|
17142
|
+
}
|
|
17143
|
+
// Continues a saved session with a message; Claude Code resumes it from its own transcript.
|
|
17144
|
+
async resume(id, cwd, text3) {
|
|
17145
|
+
const known = this.sessions.get(id);
|
|
17146
|
+
const session = known ?? new Session(id, cwd, void 0, this.soon, true);
|
|
17147
|
+
this.sessions.set(id, session);
|
|
17148
|
+
try {
|
|
17149
|
+
await session.say(text3);
|
|
17150
|
+
} catch (error) {
|
|
17151
|
+
if (!known) this.sessions.delete(id);
|
|
17152
|
+
throw error;
|
|
17153
|
+
}
|
|
17154
|
+
}
|
|
17155
|
+
async refresh() {
|
|
17156
|
+
const models2 = claudeModels(this.changed);
|
|
17157
|
+
return [...this.sessions.values()].map((session) => session.live(models2));
|
|
17158
|
+
}
|
|
17159
|
+
stop() {
|
|
17160
|
+
clearTimeout(this.notify);
|
|
17161
|
+
for (const session of this.sessions.values()) session.end();
|
|
17162
|
+
}
|
|
17163
|
+
};
|
|
17164
|
+
|
|
17165
|
+
// src/harness/codex.ts
|
|
17166
|
+
import WebSocket2 from "ws";
|
|
17167
|
+
import { homedir as homedir5 } from "node:os";
|
|
17168
|
+
import { connect } from "node:net";
|
|
17169
|
+
import { promisify as promisify2 } from "node:util";
|
|
17170
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
17171
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
17172
|
+
import { join as join5, isAbsolute as isAbsolute2 } from "node:path";
|
|
17173
|
+
import { statSync as statSync2, readFileSync as readFileSync4 } from "node:fs";
|
|
17174
|
+
import { stat, readdir as readdir2, readFile as readFile2 } from "node:fs/promises";
|
|
17175
|
+
var REQUEST_MS = 4e3;
|
|
17176
|
+
var MAX_BYTES = 8 * 1024 * 1024;
|
|
17177
|
+
var MAX_SESSIONS2 = 100;
|
|
17178
|
+
var MAX_SOCKETS = 32;
|
|
17179
|
+
var MAX_PROCESSES = 4096;
|
|
17180
|
+
var SOCKET_PARTS = ["app-server-control", "app-server-control.sock"];
|
|
17181
|
+
var DECISIONS = [
|
|
17182
|
+
{ id: "accept", label: "Allow once" },
|
|
17183
|
+
{ id: "decline", label: "Reject" }
|
|
17184
|
+
];
|
|
17185
|
+
function approval(id, method, params) {
|
|
17186
|
+
const command2 = method === "item/commandExecution/requestApproval";
|
|
17187
|
+
let detail = [
|
|
17188
|
+
command2 && typeof params.command === "string" ? params.command : "",
|
|
17189
|
+
typeof params.cwd === "string" ? `Working directory: ${params.cwd}` : "",
|
|
17190
|
+
typeof params.grantRoot === "string" ? `Write access root: ${params.grantRoot}` : "",
|
|
17191
|
+
typeof params.reason === "string" ? params.reason : ""
|
|
17192
|
+
].filter(Boolean).join("\n\n");
|
|
17193
|
+
const oversized = Buffer.byteLength(detail) > MAX_PREVIEW;
|
|
17194
|
+
const unavailable = !command2 || params.kind !== void 0 && params.kind !== "command" || typeof params.command !== "string" || !params.command.trim() || oversized;
|
|
17195
|
+
if (oversized) detail = Buffer.from(detail).subarray(0, MAX_PREVIEW).toString("utf8");
|
|
17196
|
+
if (unavailable) detail = [detail, NO_PREVIEW].filter(Boolean).join("\n\n");
|
|
17197
|
+
return {
|
|
17198
|
+
id,
|
|
17199
|
+
title: command2 ? "Allow command?" : "Allow file changes?",
|
|
17200
|
+
detail,
|
|
17201
|
+
choices: DECISIONS.filter((choice) => !unavailable || choice.id === "decline").map((choice) => ({ ...choice }))
|
|
17202
|
+
};
|
|
17203
|
+
}
|
|
17204
|
+
var codexHome = () => process.env.DASHUP_CODEX_HOME ?? process.env.CODEX_HOME ?? join5(homedir5(), ".codex");
|
|
17205
|
+
var catalog;
|
|
17206
|
+
function models() {
|
|
17207
|
+
const path = join5(codexHome(), "models_cache.json");
|
|
17208
|
+
try {
|
|
17209
|
+
const { mtimeMs } = statSync2(path);
|
|
17210
|
+
if (catalog?.path !== path || catalog.mtimeMs !== mtimeMs) {
|
|
17211
|
+
const listed = JSON.parse(readFileSync4(path, "utf8"))?.models;
|
|
17212
|
+
catalog = {
|
|
17213
|
+
path,
|
|
17214
|
+
mtimeMs,
|
|
17215
|
+
models: (Array.isArray(listed) ? listed : []).filter(
|
|
17216
|
+
(model) => typeof model?.slug === "string"
|
|
17217
|
+
)
|
|
17218
|
+
};
|
|
17219
|
+
}
|
|
17220
|
+
return catalog.models;
|
|
17221
|
+
} catch {
|
|
17222
|
+
return [];
|
|
17223
|
+
}
|
|
17224
|
+
}
|
|
17225
|
+
function codexOptions(recorded) {
|
|
17226
|
+
const listed = models();
|
|
17227
|
+
const ran = typeof recorded.model === "string" ? recorded.model : "";
|
|
17228
|
+
const current = listed.find((model) => model.slug === ran);
|
|
17229
|
+
const choices = listed.filter((model) => model.visibility !== "hide").map((model) => ({ value: model.slug, name: model.display_name ?? model.slug }));
|
|
17230
|
+
if (!ran) return [];
|
|
17231
|
+
if (!choices.some((choice) => choice.value === ran))
|
|
17232
|
+
choices.push({ value: ran, name: current?.display_name ?? ran });
|
|
17233
|
+
const effort = typeof recorded.effort === "string" ? recorded.effort : current?.default_reasoning_level;
|
|
17234
|
+
const efforts = [...(current?.supported_reasoning_levels ?? []).map((level) => level.effort)];
|
|
17235
|
+
if (effort && !efforts.includes(effort)) efforts.push(effort);
|
|
17236
|
+
const fast = current?.service_tiers?.find((tier) => tier.id === "priority");
|
|
17237
|
+
return [
|
|
17238
|
+
{ id: "model", name: "Model", value: ran, choices },
|
|
17239
|
+
...effort ? [
|
|
17240
|
+
{
|
|
17241
|
+
id: "effort",
|
|
17242
|
+
name: "Effort",
|
|
17243
|
+
value: effort,
|
|
17244
|
+
choices: efforts.map((value2) => ({ value: value2, name: `${value2} effort` }))
|
|
17245
|
+
}
|
|
17246
|
+
] : [],
|
|
17247
|
+
...fast ? [{ id: "fast", name: fast.name ?? "Fast", value: recorded.fast === true }] : []
|
|
17248
|
+
];
|
|
17249
|
+
}
|
|
17250
|
+
async function sockets() {
|
|
17251
|
+
const explicit = process.env.DASHUP_CODEX_SOCKET;
|
|
17252
|
+
if (explicit !== void 0) return explicit && isAbsolute2(explicit) ? [explicit] : [];
|
|
17253
|
+
if (process.env.DASHUP_LIVE_DISCOVERY === "0") return [];
|
|
17254
|
+
const homes = /* @__PURE__ */ new Set([codexHome()]);
|
|
17255
|
+
if (process.platform === "linux") {
|
|
17256
|
+
const entries2 = await readdir2("/proc").catch(() => []);
|
|
17257
|
+
for (const pid of entries2.filter((value2) => /^\d+$/.test(value2)).slice(0, MAX_PROCESSES)) {
|
|
17258
|
+
if (homes.size >= MAX_SOCKETS) break;
|
|
17259
|
+
try {
|
|
17260
|
+
const directory2 = `/proc/${pid}`;
|
|
17261
|
+
const owner = await stat(directory2);
|
|
17262
|
+
if (process.getuid && owner.uid !== process.getuid()) continue;
|
|
17263
|
+
const command2 = (await readFile2(join5(directory2, "cmdline"), "utf8")).slice(0, 4096);
|
|
17264
|
+
if (!/(?:^|[\/\0 ])codex(?:[\0 .]|$)/.test(command2)) continue;
|
|
17265
|
+
const environment = await readFile2(join5(directory2, "environ"), "utf8");
|
|
17266
|
+
const home = environment.split("\0").find((value2) => value2.startsWith("CODEX_HOME="))?.slice("CODEX_HOME=".length);
|
|
17267
|
+
if (home && isAbsolute2(home)) homes.add(home);
|
|
17268
|
+
} catch {
|
|
17269
|
+
}
|
|
17270
|
+
}
|
|
17271
|
+
}
|
|
17272
|
+
return [...homes].map((home) => join5(home, ...SOCKET_PARTS));
|
|
17273
|
+
}
|
|
17274
|
+
function messages2(thread) {
|
|
17275
|
+
if (!Array.isArray(thread.turns)) return [];
|
|
17276
|
+
return thread.turns.flatMap((turn) => Array.isArray(turn?.items) ? turn.items.flatMap((item) => {
|
|
17277
|
+
if (typeof item?.id !== "string") return [];
|
|
17278
|
+
if (item.type === "userMessage" && Array.isArray(item.content)) {
|
|
17279
|
+
const text3 = item.content.filter((part) => part?.type === "text" && typeof part.text === "string").map((part) => part.text).join("\n");
|
|
17280
|
+
return text3 ? [{ id: item.id, role: "user", text: text3 }] : [];
|
|
17281
|
+
}
|
|
17282
|
+
if (item.type === "agentMessage" && typeof item.text === "string")
|
|
17283
|
+
return [{ id: item.id, role: "assistant", text: item.text }];
|
|
17284
|
+
if (item.type === "commandExecution" && typeof item.command === "string")
|
|
17285
|
+
return [
|
|
17286
|
+
{
|
|
17287
|
+
id: item.id,
|
|
17288
|
+
role: "tool",
|
|
17289
|
+
text: [item.command, item.aggregatedOutput].filter((value2) => typeof value2 === "string").join("\n\n")
|
|
17290
|
+
}
|
|
17291
|
+
];
|
|
17292
|
+
return [];
|
|
17293
|
+
}) : []);
|
|
17294
|
+
}
|
|
17295
|
+
function status(thread) {
|
|
17296
|
+
if (thread.status?.type === "idle") return "idle";
|
|
17297
|
+
if (thread.status?.type === "active") {
|
|
17298
|
+
return thread.status.activeFlags?.some(
|
|
17299
|
+
(flag) => flag === "waitingOnApproval" || flag === "waitingOnUserInput"
|
|
17300
|
+
) ? "attention" : "working";
|
|
17301
|
+
}
|
|
17302
|
+
return "unknown";
|
|
17303
|
+
}
|
|
17304
|
+
var Client = class {
|
|
17305
|
+
constructor(path, changed) {
|
|
17306
|
+
this.path = path;
|
|
17307
|
+
this.changed = changed;
|
|
17308
|
+
}
|
|
17309
|
+
path;
|
|
17310
|
+
changed;
|
|
17311
|
+
socket;
|
|
17312
|
+
connecting;
|
|
17313
|
+
stopped = false;
|
|
17314
|
+
sequence = 0;
|
|
17315
|
+
controls = false;
|
|
17316
|
+
notify;
|
|
17317
|
+
pending = /* @__PURE__ */ new Map();
|
|
17318
|
+
approvals = /* @__PURE__ */ new Map();
|
|
17319
|
+
requests = /* @__PURE__ */ new Map();
|
|
17320
|
+
// The reply each thread is writing, as far as it has streamed, and the item it belongs to.
|
|
17321
|
+
typing = /* @__PURE__ */ new Map();
|
|
17322
|
+
// What the person chose for each thread, which its next turn takes and keeps.
|
|
17323
|
+
chosen = /* @__PURE__ */ new Map();
|
|
17324
|
+
async connect() {
|
|
17325
|
+
if (this.socket?.readyState === WebSocket2.OPEN) return;
|
|
17326
|
+
if (this.connecting) return this.connecting;
|
|
17327
|
+
this.connecting = (async () => {
|
|
17328
|
+
const file = await stat(this.path);
|
|
17329
|
+
if (!file.isSocket() || process.getuid && file.uid !== process.getuid())
|
|
17330
|
+
throw new Error("Codex control socket is not owned by this user");
|
|
17331
|
+
const socket = this.socket = new WebSocket2("ws://localhost/rpc", {
|
|
17332
|
+
createConnection: () => connect(this.path),
|
|
17333
|
+
maxPayload: MAX_BYTES,
|
|
17334
|
+
handshakeTimeout: REQUEST_MS
|
|
17335
|
+
});
|
|
17336
|
+
socket.on("error", () => {
|
|
17337
|
+
});
|
|
17338
|
+
socket.on("message", (bytes) => {
|
|
17339
|
+
let message;
|
|
17340
|
+
try {
|
|
17341
|
+
message = JSON.parse(bytes.toString());
|
|
17342
|
+
} catch {
|
|
17343
|
+
return;
|
|
17344
|
+
}
|
|
17345
|
+
if (typeof message.id === "number" && !message.method) {
|
|
17346
|
+
const pending = this.pending.get(message.id);
|
|
17347
|
+
if (!pending) return;
|
|
17348
|
+
this.pending.delete(message.id);
|
|
17349
|
+
clearTimeout(pending.timer);
|
|
17350
|
+
if (message.error)
|
|
17351
|
+
pending.reject(
|
|
17352
|
+
new Error(`Codex native request failed (${String(message.error.code ?? "unknown")})`)
|
|
17353
|
+
);
|
|
17354
|
+
else pending.resolve(message.result ?? {});
|
|
17355
|
+
return;
|
|
17356
|
+
}
|
|
17357
|
+
if (typeof message.method !== "string") return;
|
|
17358
|
+
const params = message.params;
|
|
17359
|
+
if (message.method === "item/agentMessage/delta" && typeof params?.threadId === "string" && typeof params.delta === "string") {
|
|
17360
|
+
const known = this.typing.get(params.threadId);
|
|
17361
|
+
this.typing.set(params.threadId, {
|
|
17362
|
+
item: params.itemId,
|
|
17363
|
+
text: (known && known.item === params.itemId ? known.text : "") + params.delta
|
|
17364
|
+
});
|
|
17365
|
+
}
|
|
17366
|
+
if (message.method === "serverRequest/resolved") {
|
|
17367
|
+
for (const [id, pending] of this.approvals)
|
|
17368
|
+
if (pending.requestId === message.params?.requestId) this.approvals.delete(id);
|
|
17369
|
+
}
|
|
17370
|
+
if ((message.method === "item/commandExecution/requestApproval" || message.method === "item/fileChange/requestApproval") && (typeof message.id === "string" || typeof message.id === "number") && typeof message.params?.threadId === "string") {
|
|
17371
|
+
const id = randomUUID4();
|
|
17372
|
+
this.approvals.set(id, {
|
|
17373
|
+
requestId: message.id,
|
|
17374
|
+
threadId: params.threadId,
|
|
17375
|
+
approval: approval(id, message.method, params)
|
|
17376
|
+
});
|
|
17377
|
+
}
|
|
17378
|
+
if (/^(thread|turn|item|serverRequest)\//.test(message.method) && !this.notify) {
|
|
17379
|
+
this.notify = setTimeout(() => {
|
|
17380
|
+
this.notify = void 0;
|
|
17381
|
+
this.changed();
|
|
17382
|
+
}, 100);
|
|
17383
|
+
this.notify.unref();
|
|
17384
|
+
}
|
|
17385
|
+
});
|
|
17386
|
+
socket.on("close", () => {
|
|
17387
|
+
if (this.socket !== socket) return;
|
|
17388
|
+
this.socket = void 0;
|
|
17389
|
+
this.controls = false;
|
|
17390
|
+
this.approvals.clear();
|
|
17391
|
+
for (const pending of this.pending.values()) {
|
|
17392
|
+
clearTimeout(pending.timer);
|
|
17393
|
+
pending.reject(new Error("Codex control socket disconnected"));
|
|
17394
|
+
}
|
|
17395
|
+
this.pending.clear();
|
|
17396
|
+
if (!this.stopped) this.changed();
|
|
17397
|
+
});
|
|
17398
|
+
await new Promise((resolve3, reject) => {
|
|
17399
|
+
socket.once("open", resolve3);
|
|
17400
|
+
socket.once("error", reject);
|
|
17401
|
+
socket.once("close", () => reject(new Error("Codex control socket closed before initialization")));
|
|
17402
|
+
});
|
|
17403
|
+
const initialized = await this.call("initialize", {
|
|
17404
|
+
clientInfo: { name: "dashup", title: "Dashup", version },
|
|
17405
|
+
capabilities: { experimentalApi: false, requestAttestation: false }
|
|
17406
|
+
});
|
|
17407
|
+
socket.send(JSON.stringify({ method: "initialized", params: {} }));
|
|
17408
|
+
const version2 = typeof initialized.userAgent === "string" ? /\b0\.(\d+)\.\d+\b/.exec(initialized.userAgent) : null;
|
|
17409
|
+
this.controls = !!version2 && Number(version2[1]) >= 140;
|
|
17410
|
+
})();
|
|
17411
|
+
try {
|
|
17412
|
+
await this.connecting;
|
|
17413
|
+
} catch (error) {
|
|
17414
|
+
this.socket?.terminate();
|
|
17415
|
+
throw error;
|
|
17416
|
+
} finally {
|
|
17417
|
+
this.connecting = void 0;
|
|
17220
17418
|
}
|
|
17221
|
-
|
|
17222
|
-
|
|
17223
|
-
|
|
17224
|
-
|
|
17225
|
-
|
|
17226
|
-
|
|
17227
|
-
|
|
17228
|
-
|
|
17229
|
-
|
|
17230
|
-
|
|
17231
|
-
|
|
17232
|
-
|
|
17419
|
+
}
|
|
17420
|
+
call(method, params) {
|
|
17421
|
+
const socket = this.socket;
|
|
17422
|
+
if (this.stopped || socket?.readyState !== WebSocket2.OPEN)
|
|
17423
|
+
return Promise.reject(new Error("Codex control socket is disconnected"));
|
|
17424
|
+
if (this.pending.size >= 32) return Promise.reject(new Error("Codex request limit reached"));
|
|
17425
|
+
const id = ++this.sequence;
|
|
17426
|
+
return new Promise((resolve3, reject) => {
|
|
17427
|
+
const timer = setTimeout(() => {
|
|
17428
|
+
this.pending.delete(id);
|
|
17429
|
+
reject(new Error("Codex native request timed out"));
|
|
17430
|
+
}, REQUEST_MS);
|
|
17431
|
+
timer.unref();
|
|
17432
|
+
this.pending.set(id, { resolve: resolve3, reject, timer });
|
|
17433
|
+
socket.send(JSON.stringify({ id, method, params }), (error) => {
|
|
17434
|
+
if (error) {
|
|
17435
|
+
clearTimeout(timer);
|
|
17436
|
+
this.pending.delete(id);
|
|
17437
|
+
reject(new Error("Codex native request could not be sent"));
|
|
17438
|
+
}
|
|
17233
17439
|
});
|
|
17234
|
-
|
|
17235
|
-
|
|
17236
|
-
|
|
17237
|
-
|
|
17238
|
-
|
|
17239
|
-
|
|
17240
|
-
|
|
17241
|
-
|
|
17242
|
-
|
|
17243
|
-
|
|
17244
|
-
|
|
17245
|
-
|
|
17246
|
-
|
|
17440
|
+
});
|
|
17441
|
+
}
|
|
17442
|
+
once(id, action) {
|
|
17443
|
+
const known = this.requests.get(id);
|
|
17444
|
+
if (known) return known;
|
|
17445
|
+
if (this.requests.size >= 1e3)
|
|
17446
|
+
throw new Error("Codex command history is full; reconnect before sending");
|
|
17447
|
+
const result = action();
|
|
17448
|
+
this.requests.set(id, result);
|
|
17449
|
+
return result;
|
|
17450
|
+
}
|
|
17451
|
+
// Starts a thread in `cwd` with Codex's own settings, or loads a saved one, then a turn.
|
|
17452
|
+
async start(cwd, text3, saved) {
|
|
17453
|
+
await this.connect();
|
|
17454
|
+
if (!this.controls) throw new Error("Update Codex to start threads from Dashup");
|
|
17455
|
+
const id = saved ? (await this.call("thread/resume", { threadId: saved, excludeTurns: true })).thread?.id : (await this.call("thread/start", { cwd })).thread?.id;
|
|
17456
|
+
if (typeof id !== "string") throw new Error("Codex did not open the thread");
|
|
17457
|
+
await this.call("turn/start", { threadId: id, input: [{ type: "text", text: text3, text_elements: [] }] });
|
|
17247
17458
|
this.changed();
|
|
17248
|
-
return
|
|
17459
|
+
return id;
|
|
17249
17460
|
}
|
|
17250
|
-
|
|
17251
|
-
|
|
17252
|
-
|
|
17253
|
-
|
|
17254
|
-
|
|
17255
|
-
|
|
17256
|
-
|
|
17257
|
-
|
|
17258
|
-
|
|
17259
|
-
|
|
17260
|
-
|
|
17261
|
-
|
|
17262
|
-
|
|
17263
|
-
|
|
17264
|
-
|
|
17265
|
-
|
|
17266
|
-
|
|
17267
|
-
|
|
17268
|
-
|
|
17269
|
-
|
|
17270
|
-
|
|
17271
|
-
|
|
17272
|
-
|
|
17273
|
-
|
|
17274
|
-
|
|
17275
|
-
|
|
17276
|
-
|
|
17277
|
-
|
|
17278
|
-
|
|
17279
|
-
|
|
17280
|
-
|
|
17281
|
-
|
|
17282
|
-
|
|
17461
|
+
async read() {
|
|
17462
|
+
await this.connect();
|
|
17463
|
+
const loaded = await this.call("thread/loaded/list", { limit: MAX_SESSIONS2 });
|
|
17464
|
+
if (!Array.isArray(loaded.data)) return [];
|
|
17465
|
+
const sessions = [];
|
|
17466
|
+
for (const id of loaded.data.slice(0, MAX_SESSIONS2)) {
|
|
17467
|
+
if (typeof id !== "string") continue;
|
|
17468
|
+
try {
|
|
17469
|
+
const read = await this.call("thread/read", { threadId: id, includeTurns: true });
|
|
17470
|
+
const thread = read.thread;
|
|
17471
|
+
if (thread?.id !== id || typeof thread.cwd !== "string" || !thread.cwd) continue;
|
|
17472
|
+
const approvals = [...this.approvals.values()].filter((value2) => value2.threadId === id).map((value2) => value2.approval);
|
|
17473
|
+
sessions.push({
|
|
17474
|
+
id,
|
|
17475
|
+
harness: "codex",
|
|
17476
|
+
cwd: thread.cwd,
|
|
17477
|
+
title: typeof thread.name === "string" && thread.name ? thread.name : typeof thread.preview === "string" && thread.preview ? thread.preview : "Codex thread",
|
|
17478
|
+
updatedAt: new Date(Number(thread.updatedAt ?? 0) * 1e3).toISOString(),
|
|
17479
|
+
status: approvals.length ? "attention" : status(thread),
|
|
17480
|
+
messages: messages2(thread),
|
|
17481
|
+
typing: this.typing.get(id)?.text,
|
|
17482
|
+
approvals,
|
|
17483
|
+
// An interrupt for a finished turn is never answered, so the active turn is read first.
|
|
17484
|
+
stop: this.controls ? (requestId) => this.once(requestId, async () => {
|
|
17485
|
+
const native = (await this.call("thread/read", { threadId: id, includeTurns: true })).thread;
|
|
17486
|
+
const turn = native?.status?.type === "active" && Array.isArray(native.turns) ? native.turns.at(-1) : void 0;
|
|
17487
|
+
if (turn?.status !== "inProgress" || typeof turn.id !== "string")
|
|
17488
|
+
throw new Error("Nothing is running in this Codex thread");
|
|
17489
|
+
await this.call("turn/interrupt", { threadId: id, turnId: turn.id });
|
|
17490
|
+
this.changed();
|
|
17491
|
+
}) : void 0,
|
|
17492
|
+
send: this.controls && thread.status?.type === "idle" ? (requestId, text3) => this.once(requestId, async () => {
|
|
17493
|
+
if (!text3.trim() || Buffer.byteLength(text3) > MAX_BYTES / 2)
|
|
17494
|
+
throw new Error("Message is empty or too large");
|
|
17495
|
+
const current = await this.call("thread/loaded/list", { limit: MAX_SESSIONS2 });
|
|
17496
|
+
if (!Array.isArray(current.data) || !current.data.includes(id))
|
|
17497
|
+
throw new Error("Codex thread is no longer loaded");
|
|
17498
|
+
const native = (await this.call("thread/read", { threadId: id, includeTurns: false })).thread;
|
|
17499
|
+
if (native?.id !== id || native.status?.type !== "idle")
|
|
17500
|
+
throw new Error("Codex thread is busy or unavailable");
|
|
17501
|
+
const chosen = this.chosen.get(id) ?? {};
|
|
17502
|
+
await this.call("turn/start", {
|
|
17503
|
+
threadId: id,
|
|
17504
|
+
clientUserMessageId: requestId,
|
|
17505
|
+
input: [{ type: "text", text: text3, text_elements: [] }],
|
|
17506
|
+
..."model" in chosen ? { model: chosen.model } : {},
|
|
17507
|
+
..."effort" in chosen ? { effort: chosen.effort } : {},
|
|
17508
|
+
..."fast" in chosen ? { serviceTier: chosen.fast ? "priority" : null } : {}
|
|
17509
|
+
});
|
|
17510
|
+
this.changed();
|
|
17511
|
+
}) : void 0,
|
|
17512
|
+
options: codexOptions,
|
|
17513
|
+
configure: this.controls ? (requestId, option2, value2) => this.once(requestId, async () => {
|
|
17514
|
+
this.chosen.set(id, { ...this.chosen.get(id), [option2]: value2 });
|
|
17515
|
+
}) : void 0,
|
|
17516
|
+
approve: approvals.length ? (requestId, approvalId, reply) => this.once(requestId, async () => {
|
|
17517
|
+
const approval2 = this.approvals.get(approvalId);
|
|
17518
|
+
const choice = "choice" in reply ? reply.choice : void 0;
|
|
17519
|
+
const socket = this.socket;
|
|
17520
|
+
if (!approval2 || approval2.threadId !== id || !approval2.approval.choices.some((value2) => value2.id === choice) || socket?.readyState !== WebSocket2.OPEN)
|
|
17521
|
+
throw new Error("Codex approval is no longer pending");
|
|
17522
|
+
await new Promise((resolve3, reject) => socket.send(
|
|
17523
|
+
JSON.stringify({ id: approval2.requestId, result: { decision: choice } }),
|
|
17524
|
+
(error) => error ? reject(new Error("Codex approval could not be sent")) : resolve3()
|
|
17525
|
+
));
|
|
17526
|
+
this.approvals.delete(approvalId);
|
|
17527
|
+
this.changed();
|
|
17528
|
+
}) : void 0
|
|
17283
17529
|
});
|
|
17284
|
-
|
|
17285
|
-
|
|
17286
|
-
|
|
17287
|
-
|
|
17288
|
-
};
|
|
17530
|
+
} catch {
|
|
17531
|
+
}
|
|
17532
|
+
}
|
|
17533
|
+
return sessions;
|
|
17289
17534
|
}
|
|
17290
|
-
|
|
17291
|
-
this.
|
|
17535
|
+
stop() {
|
|
17536
|
+
this.stopped = true;
|
|
17537
|
+
clearTimeout(this.notify);
|
|
17538
|
+
this.socket?.terminate();
|
|
17539
|
+
for (const pending of this.pending.values()) {
|
|
17540
|
+
clearTimeout(pending.timer);
|
|
17541
|
+
pending.reject(new Error("Codex observation stopped"));
|
|
17542
|
+
}
|
|
17543
|
+
this.pending.clear();
|
|
17544
|
+
this.approvals.clear();
|
|
17292
17545
|
}
|
|
17293
17546
|
};
|
|
17294
|
-
var
|
|
17547
|
+
var Codex = class {
|
|
17295
17548
|
constructor(changed) {
|
|
17296
17549
|
this.changed = changed;
|
|
17297
17550
|
}
|
|
17298
17551
|
changed;
|
|
17299
|
-
|
|
17300
|
-
|
|
17301
|
-
|
|
17302
|
-
|
|
17303
|
-
|
|
17304
|
-
|
|
17305
|
-
|
|
17306
|
-
|
|
17307
|
-
|
|
17308
|
-
|
|
17309
|
-
|
|
17310
|
-
|
|
17311
|
-
|
|
17312
|
-
|
|
17313
|
-
|
|
17314
|
-
try {
|
|
17315
|
-
await session.say(text3);
|
|
17316
|
-
} catch (error) {
|
|
17317
|
-
this.sessions.delete(session.id);
|
|
17318
|
-
throw error;
|
|
17552
|
+
clients = /* @__PURE__ */ new Map();
|
|
17553
|
+
// Control sockets of daemons this connector started, which discovery may not look for.
|
|
17554
|
+
started = /* @__PURE__ */ new Set();
|
|
17555
|
+
// Codex's own daemon runs the threads Dashup starts or resumes, so they outlive this connector.
|
|
17556
|
+
async start(cwd, text3, saved) {
|
|
17557
|
+
const path = process.env.DASHUP_CODEX_SOCKET || join5(codexHome(), ...SOCKET_PARTS);
|
|
17558
|
+
await promisify2(execFile2)("codex", ["app-server", "daemon", "start"], {
|
|
17559
|
+
timeout: 6e4,
|
|
17560
|
+
shell: process.platform === "win32"
|
|
17561
|
+
});
|
|
17562
|
+
this.started.add(path);
|
|
17563
|
+
let client = this.clients.get(path);
|
|
17564
|
+
if (!client) {
|
|
17565
|
+
client = new Client(path, this.changed);
|
|
17566
|
+
this.clients.set(path, client);
|
|
17319
17567
|
}
|
|
17320
|
-
return
|
|
17568
|
+
return client.start(cwd, text3, saved);
|
|
17321
17569
|
}
|
|
17322
|
-
// Continues a saved session with a message; Claude Code resumes it from its own transcript.
|
|
17323
17570
|
async resume(id, cwd, text3) {
|
|
17324
|
-
|
|
17325
|
-
const session = known ?? new Session(id, cwd, void 0, this.soon, true);
|
|
17326
|
-
this.sessions.set(id, session);
|
|
17327
|
-
try {
|
|
17328
|
-
await session.say(text3);
|
|
17329
|
-
} catch (error) {
|
|
17330
|
-
if (!known) this.sessions.delete(id);
|
|
17331
|
-
throw error;
|
|
17332
|
-
}
|
|
17571
|
+
await this.start(cwd, text3, id);
|
|
17333
17572
|
}
|
|
17334
17573
|
async refresh() {
|
|
17335
|
-
const
|
|
17336
|
-
|
|
17574
|
+
const paths = /* @__PURE__ */ new Set([...await sockets(), ...this.started]);
|
|
17575
|
+
for (const [path, client] of this.clients)
|
|
17576
|
+
if (!paths.has(path)) {
|
|
17577
|
+
client.stop();
|
|
17578
|
+
this.clients.delete(path);
|
|
17579
|
+
}
|
|
17580
|
+
const results = await Promise.allSettled(
|
|
17581
|
+
[...paths].map(async (path) => {
|
|
17582
|
+
let client = this.clients.get(path);
|
|
17583
|
+
if (!client) {
|
|
17584
|
+
client = new Client(path, this.changed);
|
|
17585
|
+
this.clients.set(path, client);
|
|
17586
|
+
}
|
|
17587
|
+
return client.read();
|
|
17588
|
+
})
|
|
17589
|
+
);
|
|
17590
|
+
return results.flatMap((result) => result.status === "fulfilled" ? result.value : []);
|
|
17337
17591
|
}
|
|
17338
17592
|
stop() {
|
|
17339
|
-
|
|
17340
|
-
|
|
17593
|
+
for (const client of this.clients.values()) client.stop();
|
|
17594
|
+
this.clients.clear();
|
|
17341
17595
|
}
|
|
17342
17596
|
};
|
|
17343
17597
|
|
|
@@ -17348,9 +17602,9 @@ import { connect as connect2 } from "node:net";
|
|
|
17348
17602
|
import { promisify as promisify3 } from "node:util";
|
|
17349
17603
|
import { fileURLToPath } from "node:url";
|
|
17350
17604
|
import { execFile as execFile3 } from "node:child_process";
|
|
17351
|
-
import { createHash as createHash3, randomUUID as
|
|
17605
|
+
import { createHash as createHash3, randomUUID as randomUUID5 } from "node:crypto";
|
|
17352
17606
|
import { stat as stat2, readdir as readdir3, readFile as readFile3 } from "node:fs/promises";
|
|
17353
|
-
import { join as join6, dirname as dirname5, basename as
|
|
17607
|
+
import { join as join6, dirname as dirname5, basename as basename3, isAbsolute as isAbsolute3 } from "node:path";
|
|
17354
17608
|
import { WebSocketTransport } from "@microsoft/agent-host-protocol/ws";
|
|
17355
17609
|
import { AhpClient } from "@microsoft/agent-host-protocol/client";
|
|
17356
17610
|
import { chatReducer, sessionReducer, SUPPORTED_PROTOCOL_VERSIONS } from "@microsoft/agent-host-protocol";
|
|
@@ -17368,7 +17622,7 @@ var CONFIRMATION_CHOICES = [
|
|
|
17368
17622
|
function roots() {
|
|
17369
17623
|
const override = process.env.DASHUP_AHP_DATA ?? process.env.DASHUP_VSCODE_DATA;
|
|
17370
17624
|
if (override !== void 0)
|
|
17371
|
-
return override ? [
|
|
17625
|
+
return override ? [basename3(override) === "User" ? dirname5(override) : override] : [];
|
|
17372
17626
|
if (process.env.DASHUP_LIVE_DISCOVERY === "0") return [];
|
|
17373
17627
|
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");
|
|
17374
17628
|
return ["Code", "Code - Insiders"].map((name) => join6(base, name));
|
|
@@ -17410,12 +17664,12 @@ async function entries() {
|
|
|
17410
17664
|
try {
|
|
17411
17665
|
if (!await privateFile(path, MAX_METADATA)) continue;
|
|
17412
17666
|
const parsed = JSON.parse(await readFile3(path, "utf8"));
|
|
17413
|
-
const rows =
|
|
17667
|
+
const rows = basename3(path) === "metadata.json" && Array.isArray(parsed) ? parsed : [parsed];
|
|
17414
17668
|
for (const row of rows.slice(0, MAX_HOSTS)) {
|
|
17415
17669
|
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))
|
|
17416
17670
|
continue;
|
|
17417
17671
|
const identity2 = createHash3("sha256").update(`${row.type}\0${row.pid}\0${row.instanceId}`).digest("hex");
|
|
17418
|
-
if (
|
|
17672
|
+
if (basename3(path) !== "metadata.json" && basename3(path) !== `${identity2}.json`) continue;
|
|
17419
17673
|
try {
|
|
17420
17674
|
process.kill(row.pid, 0);
|
|
17421
17675
|
} catch {
|
|
@@ -17541,7 +17795,7 @@ var Host = class {
|
|
|
17541
17795
|
client;
|
|
17542
17796
|
connecting;
|
|
17543
17797
|
stopped = false;
|
|
17544
|
-
clientId = `dashup-${
|
|
17798
|
+
clientId = `dashup-${randomUUID5()}`;
|
|
17545
17799
|
resources = /* @__PURE__ */ new Map();
|
|
17546
17800
|
subscriptions = /* @__PURE__ */ new Set();
|
|
17547
17801
|
receipts = /* @__PURE__ */ new Map();
|
|
@@ -17700,14 +17954,14 @@ var Host = class {
|
|
|
17700
17954
|
}
|
|
17701
17955
|
async read() {
|
|
17702
17956
|
await this.connect();
|
|
17703
|
-
const
|
|
17957
|
+
const catalog2 = await this.client.request("listSessions", {
|
|
17704
17958
|
channel: ROOT_CHANNEL,
|
|
17705
17959
|
limit: MAX_SESSIONS3
|
|
17706
17960
|
});
|
|
17707
|
-
if (!Array.isArray(
|
|
17961
|
+
if (!Array.isArray(catalog2.items)) return [];
|
|
17708
17962
|
const result = [];
|
|
17709
17963
|
const active = /* @__PURE__ */ new Set();
|
|
17710
|
-
for (const summary of
|
|
17964
|
+
for (const summary of catalog2.items.slice(0, MAX_SESSIONS3)) {
|
|
17711
17965
|
try {
|
|
17712
17966
|
const session = await this.subscribe(summary.resource);
|
|
17713
17967
|
if (!session || session.lifecycle !== "ready" || !Array.isArray(session.chats)) continue;
|
|
@@ -17722,12 +17976,12 @@ var Host = class {
|
|
|
17722
17976
|
for (const pending of Array.isArray(session.inputNeeded) ? session.inputNeeded : []) {
|
|
17723
17977
|
if (pending.kind !== "toolConfirmation" || pending.chat !== item.resource || pending.toolCall?.status !== "pending-confirmation")
|
|
17724
17978
|
continue;
|
|
17725
|
-
const
|
|
17979
|
+
const preview3 = confirmation(pending.toolCall);
|
|
17726
17980
|
approvals.push({
|
|
17727
17981
|
id: pending.id,
|
|
17728
17982
|
title: text2(pending.toolCall.confirmationTitle) || pending.toolCall.displayName || "Native agent approval",
|
|
17729
|
-
detail:
|
|
17730
|
-
choices:
|
|
17983
|
+
detail: preview3.detail,
|
|
17984
|
+
choices: preview3.choices.map(({ id: id2, label }) => ({ id: id2, label }))
|
|
17731
17985
|
});
|
|
17732
17986
|
}
|
|
17733
17987
|
const id = `vscode-ahp:${createHash3("sha256").update(this.entry.root).digest("hex").slice(0, 16)}:${item.resource}`;
|
|
@@ -17769,7 +18023,8 @@ var Host = class {
|
|
|
17769
18023
|
message: { text: value2, origin: { kind: "user" } }
|
|
17770
18024
|
});
|
|
17771
18025
|
} : void 0,
|
|
17772
|
-
approve: approvals.some((
|
|
18026
|
+
approve: approvals.some((approval2) => approval2.choices.length) ? (requestId, approvalId, reply) => {
|
|
18027
|
+
const choice = "choice" in reply ? reply.choice : void 0;
|
|
17773
18028
|
const prior = this.requests.get(requestId);
|
|
17774
18029
|
if (prior) return prior;
|
|
17775
18030
|
const current = this.resources.get(summary.resource)?.state;
|
|
@@ -18142,21 +18397,14 @@ var Server = class {
|
|
|
18142
18397
|
(question) => question?.sessionID === id && typeof question.id === "string"
|
|
18143
18398
|
) : [];
|
|
18144
18399
|
const approvals = pending.map((permission) => permissionApproval(permission, history, cwd));
|
|
18145
|
-
for (const question of asks)
|
|
18146
|
-
|
|
18147
|
-
|
|
18148
|
-
|
|
18149
|
-
|
|
18150
|
-
|
|
18151
|
-
|
|
18152
|
-
|
|
18153
|
-
detail: typeof first?.question === "string" ? first.question : "Open this question in the computer\u2019s native interface.",
|
|
18154
|
-
choices: options.map((option2, index) => ({
|
|
18155
|
-
id: `answer:${index}`,
|
|
18156
|
-
label: option2.label
|
|
18157
|
-
}))
|
|
18158
|
-
});
|
|
18159
|
-
}
|
|
18400
|
+
for (const question of asks)
|
|
18401
|
+
approvals.push(
|
|
18402
|
+
questionApproval(
|
|
18403
|
+
`question:${question.id}`,
|
|
18404
|
+
Array.isArray(question.questions) ? question.questions : [],
|
|
18405
|
+
(item) => item.multiple === true
|
|
18406
|
+
)
|
|
18407
|
+
);
|
|
18160
18408
|
result.push({
|
|
18161
18409
|
id,
|
|
18162
18410
|
cwd,
|
|
@@ -18192,23 +18440,21 @@ var Server = class {
|
|
|
18192
18440
|
});
|
|
18193
18441
|
this.changed();
|
|
18194
18442
|
}),
|
|
18195
|
-
approve: approvals.some((
|
|
18443
|
+
approve: approvals.some((approval2) => approval2.choices.length) ? (requestId, approvalId, reply) => this.once(requestId, async () => {
|
|
18196
18444
|
if (this.stopped) throw new Error("OpenCode is disconnected");
|
|
18445
|
+
const choice = "choice" in reply ? reply.choice : void 0;
|
|
18197
18446
|
if (approvalId.startsWith("question:")) {
|
|
18198
18447
|
const requestID = approvalId.slice("question:".length);
|
|
18199
18448
|
const current = await this.request("/question", cwd);
|
|
18200
|
-
|
|
18449
|
+
if (!Array.isArray(current) || !current.some(
|
|
18201
18450
|
(value2) => value2?.id === requestID && value2.sessionID === id
|
|
18202
|
-
)
|
|
18203
|
-
|
|
18204
|
-
|
|
18205
|
-
|
|
18206
|
-
|
|
18207
|
-
|
|
18208
|
-
|
|
18209
|
-
await this.request(`/question/${encodeURIComponent(requestID)}/reply`, cwd, {
|
|
18210
|
-
answers: [[option2.label]]
|
|
18211
|
-
});
|
|
18451
|
+
))
|
|
18452
|
+
throw new Error("Question is no longer pending");
|
|
18453
|
+
await this.request(
|
|
18454
|
+
`/question/${encodeURIComponent(requestID)}/${"answers" in reply ? "reply" : "reject"}`,
|
|
18455
|
+
cwd,
|
|
18456
|
+
"answers" in reply ? { answers: reply.answers } : {}
|
|
18457
|
+
);
|
|
18212
18458
|
} else {
|
|
18213
18459
|
const current = await this.request("/permission", cwd);
|
|
18214
18460
|
const permission = Array.isArray(current) ? current.find(
|
|
@@ -18432,6 +18678,12 @@ var LiveHarnesses = class {
|
|
|
18432
18678
|
});
|
|
18433
18679
|
return this.refreshing;
|
|
18434
18680
|
}
|
|
18681
|
+
// What a saved session last ran with, as its agent names its options.
|
|
18682
|
+
recorded(harness, settings) {
|
|
18683
|
+
const changed = () => this.changed();
|
|
18684
|
+
if (harness === "claude") return claudeOptions(claudeModels(changed), settings);
|
|
18685
|
+
return harness === "codex" ? codexOptions(settings) : [];
|
|
18686
|
+
}
|
|
18435
18687
|
// The agents whose own command runs on this computer. Claude Code is asked its models again.
|
|
18436
18688
|
async agents() {
|
|
18437
18689
|
const harnesses = ["claude", "codex", "opencode"];
|
|
@@ -18493,11 +18745,11 @@ var LiveHarnesses = class {
|
|
|
18493
18745
|
};
|
|
18494
18746
|
|
|
18495
18747
|
// src/harness/queue.ts
|
|
18496
|
-
import { randomUUID as
|
|
18748
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
18497
18749
|
import { spawn as spawn5 } from "node:child_process";
|
|
18498
18750
|
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
18499
|
-
import { join as join8, dirname as dirname6, basename as
|
|
18500
|
-
import { statSync as
|
|
18751
|
+
import { join as join8, dirname as dirname6, basename as basename4 } from "node:path";
|
|
18752
|
+
import { statSync as statSync3, existsSync as existsSync4, readdirSync as readdirSync3, readFileSync as readFileSync5, readlinkSync, realpathSync as realpathSync2 } from "node:fs";
|
|
18501
18753
|
var CODEX_VERSION = "0.160.1";
|
|
18502
18754
|
var REQUEST_MS4 = 1e4;
|
|
18503
18755
|
var MAX_BYTES4 = 1024 * 1024;
|
|
@@ -18506,7 +18758,7 @@ var MAX_FILES = 4096;
|
|
|
18506
18758
|
var THREAD_ID = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i;
|
|
18507
18759
|
function identity(path) {
|
|
18508
18760
|
const canonical2 = realpathSync2.native(path);
|
|
18509
|
-
const file =
|
|
18761
|
+
const file = statSync3(canonical2);
|
|
18510
18762
|
if (!file.isFile() || process.getuid && file.uid !== process.getuid())
|
|
18511
18763
|
throw new Error("Native queue files are not owned by this user");
|
|
18512
18764
|
return { path: canonical2, device: file.dev, inode: file.ino };
|
|
@@ -18519,17 +18771,17 @@ function packageVersion(executable) {
|
|
|
18519
18771
|
for (let depth = 0; depth < 8; depth++, directory2 = dirname6(directory2)) {
|
|
18520
18772
|
const path = join8(directory2, "package.json");
|
|
18521
18773
|
if (!existsSync4(path)) continue;
|
|
18522
|
-
const value2 = JSON.parse(
|
|
18774
|
+
const value2 = JSON.parse(readFileSync5(path, "utf8"));
|
|
18523
18775
|
if (value2.name?.startsWith("@openai/codex-")) return value2.version;
|
|
18524
18776
|
}
|
|
18525
18777
|
return void 0;
|
|
18526
18778
|
}
|
|
18527
18779
|
function processOwners(pid) {
|
|
18528
18780
|
const root = `/proc/${pid}`;
|
|
18529
|
-
if (process.getuid &&
|
|
18781
|
+
if (process.getuid && statSync3(root).uid !== process.getuid()) return [];
|
|
18530
18782
|
const executable = realpathSync2.native(join8(root, "exe"));
|
|
18531
|
-
if (
|
|
18532
|
-
const status2 =
|
|
18783
|
+
if (basename4(executable) !== "codex" || packageVersion(executable) !== CODEX_VERSION) return [];
|
|
18784
|
+
const status2 = readFileSync5(join8(root, "stat"), "utf8");
|
|
18533
18785
|
const start2 = status2.slice(status2.lastIndexOf(")") + 2).split(" ")[19];
|
|
18534
18786
|
if (!start2 || !/^\d+$/.test(start2)) return [];
|
|
18535
18787
|
const fds = readdirSync3(join8(root, "fd")).slice(0, MAX_FILES);
|
|
@@ -18541,14 +18793,14 @@ function processOwners(pid) {
|
|
|
18541
18793
|
}
|
|
18542
18794
|
});
|
|
18543
18795
|
return files2.flatMap(({ fd, path }) => {
|
|
18544
|
-
if (
|
|
18545
|
-
const thread =
|
|
18796
|
+
if (basename4(dirname6(path)) !== "thread-writer-locks" || !path.endsWith(".lock")) return [];
|
|
18797
|
+
const thread = basename4(path, ".lock");
|
|
18546
18798
|
if (!THREAD_ID.test(thread)) return [];
|
|
18547
18799
|
try {
|
|
18548
|
-
const lock =
|
|
18800
|
+
const lock = readFileSync5(join8(root, "fdinfo", fd), "utf8");
|
|
18549
18801
|
if (!/FLOCK\s+ADVISORY\s+WRITE/.test(lock)) return [];
|
|
18550
18802
|
const home = realpathSync2.native(dirname6(dirname6(path)));
|
|
18551
|
-
const directory2 =
|
|
18803
|
+
const directory2 = statSync3(home);
|
|
18552
18804
|
if (process.getuid && directory2.uid !== process.getuid()) return [];
|
|
18553
18805
|
const queue = identity(join8(home, "queue_1.sqlite"));
|
|
18554
18806
|
if (!files2.some((file) => {
|
|
@@ -18616,7 +18868,7 @@ function validateOwner(owner) {
|
|
|
18616
18868
|
throw new Error("The original native conversation owner is no longer available");
|
|
18617
18869
|
}
|
|
18618
18870
|
function validateEvidence(owner) {
|
|
18619
|
-
if (realpathSync2.native(owner.home) !== owner.home || process.getuid &&
|
|
18871
|
+
if (realpathSync2.native(owner.home) !== owner.home || process.getuid && statSync3(owner.home).uid !== process.getuid() || !sameFile(identity(owner.queue.path), owner.queue) || !sameFile(identity(owner.rollout.path), owner.rollout))
|
|
18620
18872
|
throw new Error("The original native history storage changed");
|
|
18621
18873
|
const path = join8(owner.home, "thread_history_1.sqlite");
|
|
18622
18874
|
if (existsSync4(path) && identity(path).path !== path)
|
|
@@ -18778,7 +19030,7 @@ var NativeQueue = class {
|
|
|
18778
19030
|
if (existing) return existing;
|
|
18779
19031
|
if (this.stopped) throw new Error("The connector is stopping");
|
|
18780
19032
|
validateOwner(scope2.owner);
|
|
18781
|
-
const clientUserMessageId =
|
|
19033
|
+
const clientUserMessageId = randomUUID6();
|
|
18782
19034
|
const request2 = { ...scope2, receipt: { state: "unknown", clientUserMessageId } };
|
|
18783
19035
|
let writer;
|
|
18784
19036
|
try {
|
|
@@ -18852,6 +19104,7 @@ var EPHEMERAL_TTL_MS = 6e4;
|
|
|
18852
19104
|
var HOME_RECENT = 20;
|
|
18853
19105
|
var FOLLOW_MS = 2 * 6e4;
|
|
18854
19106
|
var RESCAN_MS = 1e3;
|
|
19107
|
+
var answers = (approval2, reply) => "answers" in reply ? reply.answers.length === approval2.questions?.length : approval2.choices.some((choice) => choice.id === reply.choice);
|
|
18855
19108
|
var Command = z2.object({
|
|
18856
19109
|
type: z2.literal("command"),
|
|
18857
19110
|
requestId: z2.string().min(1),
|
|
@@ -18873,7 +19126,15 @@ var Command = z2.object({
|
|
|
18873
19126
|
option: z2.string().min(1).max(100),
|
|
18874
19127
|
value: z2.union([z2.string().min(1).max(200), z2.boolean()])
|
|
18875
19128
|
}).strict(),
|
|
18876
|
-
z2.object({
|
|
19129
|
+
z2.object({
|
|
19130
|
+
kind: z2.literal("approval.respond"),
|
|
19131
|
+
approvalId: z2.string(),
|
|
19132
|
+
choice: z2.string().optional(),
|
|
19133
|
+
text: z2.string().max(1e5).optional(),
|
|
19134
|
+
answers: z2.array(z2.array(z2.string().min(1).max(1e5)).min(1)).min(1).optional()
|
|
19135
|
+
}).strict().refine(
|
|
19136
|
+
(action) => action.choice === void 0 !== (action.answers === void 0) && (action.text === void 0 || action.choice !== void 0)
|
|
19137
|
+
),
|
|
18877
19138
|
z2.object({ kind: z2.literal("attachment.read"), id: z2.string().regex(/^[a-f0-9]{64}$/) }).strict(),
|
|
18878
19139
|
z2.object({
|
|
18879
19140
|
kind: z2.literal("session.start"),
|
|
@@ -18989,15 +19250,15 @@ var Connector = class {
|
|
|
18989
19250
|
for (const project of this.discovery.projects.values())
|
|
18990
19251
|
for (const thread of project.threads) {
|
|
18991
19252
|
const native = this.discovery.locations.get(thread.id)?.live;
|
|
18992
|
-
const
|
|
18993
|
-
if (!
|
|
19253
|
+
const approval2 = native?.approvals[0];
|
|
19254
|
+
if (!approval2) continue;
|
|
18994
19255
|
await this.thread(pair, project, thread);
|
|
18995
19256
|
await this.send(pair, {
|
|
18996
19257
|
type: "attention",
|
|
18997
19258
|
projectId: project.id,
|
|
18998
19259
|
threadId: thread.id,
|
|
18999
|
-
title:
|
|
19000
|
-
body:
|
|
19260
|
+
title: approval2.title,
|
|
19261
|
+
body: approval2.detail,
|
|
19001
19262
|
reason: "approval"
|
|
19002
19263
|
});
|
|
19003
19264
|
}
|
|
@@ -19145,7 +19406,7 @@ var Connector = class {
|
|
|
19145
19406
|
const started = threads.filter((thread) => !thread.parent);
|
|
19146
19407
|
this.listedProjects.set(project.id, {
|
|
19147
19408
|
...project,
|
|
19148
|
-
active: started.reduce((
|
|
19409
|
+
active: started.reduce((newest2, thread) => thread.updatedAt > newest2 ? thread.updatedAt : newest2, ""),
|
|
19149
19410
|
started: started.length
|
|
19150
19411
|
});
|
|
19151
19412
|
}
|
|
@@ -19251,9 +19512,9 @@ var Connector = class {
|
|
|
19251
19512
|
if (JSON.stringify(prior) === JSON.stringify(snapshot)) continue;
|
|
19252
19513
|
this.nativeSnapshots.set(thread.id, structuredClone(snapshot));
|
|
19253
19514
|
for (const pair of this.activePairs()) await this.thread(pair, project, thread);
|
|
19254
|
-
for (const
|
|
19255
|
-
if (!prior?.approvals.some((item) => item.id ===
|
|
19256
|
-
await this.attention(project.id, thread.id,
|
|
19515
|
+
for (const approval2 of session.approvals)
|
|
19516
|
+
if (!prior?.approvals.some((item) => item.id === approval2.id))
|
|
19517
|
+
await this.attention(project.id, thread.id, approval2.title, approval2.detail, "approval");
|
|
19257
19518
|
if (prior?.thread.status === "working" && thread.status === "idle")
|
|
19258
19519
|
await this.attention(
|
|
19259
19520
|
project.id,
|
|
@@ -19441,7 +19702,7 @@ var Connector = class {
|
|
|
19441
19702
|
}
|
|
19442
19703
|
}
|
|
19443
19704
|
attach(socket) {
|
|
19444
|
-
const sessions = bridgeAcp(socket, (session,
|
|
19705
|
+
const sessions = bridgeAcp(socket, (session, approval2) => {
|
|
19445
19706
|
this.agents.set(session.id, session);
|
|
19446
19707
|
const { project, thread } = this.adopt(session);
|
|
19447
19708
|
if (!this.snapshots.has(session.id)) {
|
|
@@ -19456,15 +19717,15 @@ var Connector = class {
|
|
|
19456
19717
|
}
|
|
19457
19718
|
}).catch((error) => this.report(error));
|
|
19458
19719
|
}
|
|
19459
|
-
if (
|
|
19720
|
+
if (approval2)
|
|
19460
19721
|
this.work = this.work.then(async () => {
|
|
19461
|
-
if (this.agents.get(session.id) !== session || ![...session.approvals.values()].includes(
|
|
19722
|
+
if (this.agents.get(session.id) !== session || ![...session.approvals.values()].includes(approval2))
|
|
19462
19723
|
return;
|
|
19463
19724
|
await this.attention(
|
|
19464
19725
|
project.id,
|
|
19465
19726
|
thread.id,
|
|
19466
|
-
|
|
19467
|
-
|
|
19727
|
+
approval2.title,
|
|
19728
|
+
approval2.detail,
|
|
19468
19729
|
"approval"
|
|
19469
19730
|
);
|
|
19470
19731
|
}).catch((error) => this.report(error));
|
|
@@ -19526,11 +19787,22 @@ var Connector = class {
|
|
|
19526
19787
|
};
|
|
19527
19788
|
}
|
|
19528
19789
|
thread(pair, project, thread, history) {
|
|
19790
|
+
const state = this.threadState(project, thread);
|
|
19529
19791
|
return this.send(pair, {
|
|
19530
|
-
...
|
|
19792
|
+
...state,
|
|
19793
|
+
thread: this.recorded(state.thread),
|
|
19531
19794
|
...history ? { history } : {}
|
|
19532
19795
|
});
|
|
19533
19796
|
}
|
|
19797
|
+
// A thread no live session holds shows what its record says it last ran with, without the
|
|
19798
|
+
// choices only a live session takes. Only its own state carries them: a project's snapshot
|
|
19799
|
+
// holds every thread.
|
|
19800
|
+
recorded(thread) {
|
|
19801
|
+
const settings = thread.options || !thread.agent ? void 0 : this.discovery.locations.get(thread.id)?.history?.settings?.();
|
|
19802
|
+
if (!settings) return thread;
|
|
19803
|
+
const options = this.harnesses.recorded(thread.agent.harness, settings).map((option2) => option2.choices ? { ...option2, choices: option2.choices.filter((choice) => choice.value === option2.value) } : option2);
|
|
19804
|
+
return options.length ? { ...thread, options } : thread;
|
|
19805
|
+
}
|
|
19534
19806
|
async command(pair, command2) {
|
|
19535
19807
|
if (command2.action.kind === "projects.refresh") {
|
|
19536
19808
|
await this.findAgents();
|
|
@@ -19630,24 +19902,24 @@ var Connector = class {
|
|
|
19630
19902
|
}
|
|
19631
19903
|
case "approval.respond": {
|
|
19632
19904
|
if (!thread) throw new Error("Thread not found");
|
|
19633
|
-
const
|
|
19905
|
+
const { kind, approvalId, ...reply } = command2.action;
|
|
19634
19906
|
const native = this.discovery.locations.get(thread.id)?.live;
|
|
19635
19907
|
if (native?.approve) {
|
|
19636
|
-
const
|
|
19637
|
-
if (!
|
|
19908
|
+
const approval3 = native.approvals.find((item) => item.id === approvalId);
|
|
19909
|
+
if (!approval3 || !answers(approval3, reply))
|
|
19638
19910
|
throw new Error("Approval is no longer available");
|
|
19639
|
-
return native.approve(command2.requestId,
|
|
19911
|
+
return native.approve(command2.requestId, approval3.id, reply);
|
|
19640
19912
|
}
|
|
19641
19913
|
const agent = this.attached(thread);
|
|
19642
19914
|
if (!agent)
|
|
19643
19915
|
throw new Error("Approval requires an attached ACP session. Answer it on your computer.");
|
|
19644
|
-
const [request2,
|
|
19645
|
-
if (!
|
|
19916
|
+
const [request2, approval2] = [...agent.approvals].find(([, item]) => item.id === approvalId) ?? [];
|
|
19917
|
+
if (!approval2 || !("choice" in reply) || !answers(approval2, reply))
|
|
19646
19918
|
throw new Error("Approval is no longer available");
|
|
19647
19919
|
return commandAcp(agent, command2.requestId, {
|
|
19648
19920
|
kind: "approval",
|
|
19649
19921
|
id: request2,
|
|
19650
|
-
choice:
|
|
19922
|
+
choice: reply.choice
|
|
19651
19923
|
});
|
|
19652
19924
|
}
|
|
19653
19925
|
}
|