@dashup/connector 0.1.16 → 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 +3 -1
- package/dist/index.js +1116 -992
- package/package.json +4 -4
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;
|
|
@@ -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({
|
|
@@ -4240,8 +4240,11 @@ var READ_BYTES = 64 * 1024;
|
|
|
4240
4240
|
var TITLE_LIMIT = 120;
|
|
4241
4241
|
var LATEST_BYTES = 8 * 1024 * 1024;
|
|
4242
4242
|
var JOURNALS = 16;
|
|
4243
|
+
var SEARCH_BYTES = 1024 * 1024;
|
|
4244
|
+
var MARKED = 512;
|
|
4243
4245
|
var journals = /* @__PURE__ */ new Map();
|
|
4244
4246
|
var metadata = /* @__PURE__ */ new Map();
|
|
4247
|
+
var marked = /* @__PURE__ */ new Map();
|
|
4245
4248
|
function object(value2) {
|
|
4246
4249
|
return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : void 0;
|
|
4247
4250
|
}
|
|
@@ -4306,6 +4309,40 @@ function described(path, until, describe2) {
|
|
|
4306
4309
|
metadata.set(path, { stamp, value: value2 });
|
|
4307
4310
|
return value2;
|
|
4308
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
|
+
}
|
|
4309
4346
|
var windowed = (path, messages5) => journals.get(path)?.start ? [EARLIER, ...messages5] : messages5;
|
|
4310
4347
|
function journal(path, latest = false) {
|
|
4311
4348
|
const stamp = statSync(path);
|
|
@@ -4562,20 +4599,18 @@ function claudeConversation(conversation, rows) {
|
|
|
4562
4599
|
}
|
|
4563
4600
|
function claudeSettings(path) {
|
|
4564
4601
|
const rows = journal(path, true);
|
|
4565
|
-
const
|
|
4602
|
+
const marker = object(newest(path, '"ultra_effort_')?.attachment)?.type;
|
|
4566
4603
|
for (let index = rows.length - 1; index >= 0; index--) {
|
|
4567
4604
|
const row = rows[index];
|
|
4568
4605
|
const message = object(row.message);
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
settings.ultracode = marker === "ultra_effort_enter";
|
|
4576
|
-
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
|
+
};
|
|
4577
4612
|
}
|
|
4578
|
-
return { ultracode:
|
|
4613
|
+
return { ultracode: marker === "ultra_effort_enter" };
|
|
4579
4614
|
}
|
|
4580
4615
|
function claude(root, scan) {
|
|
4581
4616
|
for (const path of files(join2(root, "projects"), scan, 1).filter((file) => file.endsWith(".jsonl"))) {
|
|
@@ -4632,6 +4667,21 @@ function applyPatch(patch) {
|
|
|
4632
4667
|
+++ ${kind === "Delete" ? "/dev/null" : path}`
|
|
4633
4668
|
).replace(/^\*\*\* .*\n?/gm, "");
|
|
4634
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
|
+
}
|
|
4635
4685
|
function codexMessages(path) {
|
|
4636
4686
|
const rows = journal(path, true);
|
|
4637
4687
|
const outputs = /* @__PURE__ */ new Map();
|
|
@@ -4724,7 +4774,8 @@ function codex(root, scan) {
|
|
|
4724
4774
|
"Codex thread"
|
|
4725
4775
|
),
|
|
4726
4776
|
files: [path, ...indexed ? [index] : []],
|
|
4727
|
-
read: () => codexMessages(path)
|
|
4777
|
+
read: () => codexMessages(path),
|
|
4778
|
+
settings: () => codexSettings(path)
|
|
4728
4779
|
});
|
|
4729
4780
|
} catch {
|
|
4730
4781
|
scan.warnings.push(`Cannot read Codex session: ${path}`);
|
|
@@ -4900,7 +4951,7 @@ function openCodeLegacy(root, scan) {
|
|
|
4900
4951
|
continue;
|
|
4901
4952
|
scan.paths.add(root);
|
|
4902
4953
|
const messages5 = join2(root, "message", row.id);
|
|
4903
|
-
const
|
|
4954
|
+
const newest2 = files(messages5, scan, 0).filter((file) => file.endsWith(".json")).sort().at(-1);
|
|
4904
4955
|
scan.sessions.push({
|
|
4905
4956
|
harness: "opencode",
|
|
4906
4957
|
id: row.id,
|
|
@@ -4908,7 +4959,7 @@ function openCodeLegacy(root, scan) {
|
|
|
4908
4959
|
title: title(row.title, "OpenCode thread"),
|
|
4909
4960
|
parent: typeof row.parentID === "string" ? row.parentID : void 0,
|
|
4910
4961
|
updatedAt: date(
|
|
4911
|
-
|
|
4962
|
+
newest2 ? jsonFile(newest2)?.time?.created : row.time?.created,
|
|
4912
4963
|
statSync(path).mtimeMs
|
|
4913
4964
|
),
|
|
4914
4965
|
files: [path, messages5, join2(root, "part")],
|
|
@@ -13264,22 +13315,22 @@ var FrontendAdapter = class {
|
|
|
13264
13315
|
if (options.subscribe === false) requestData.__subscribe = false;
|
|
13265
13316
|
const result = await this.transport.get(path, requestData);
|
|
13266
13317
|
const items = result?.[(0, import_pluralize.default)(modelClass.type)] ?? result?.[modelClass.type + "s"] ?? result?.items ?? [];
|
|
13267
|
-
const
|
|
13318
|
+
const models2 = items.map(
|
|
13268
13319
|
(row) => modelClass.hydrate(this, row)
|
|
13269
13320
|
);
|
|
13270
13321
|
if (result?.__queryHash) {
|
|
13271
|
-
Object.defineProperty(
|
|
13322
|
+
Object.defineProperty(models2, "__queryHash", {
|
|
13272
13323
|
value: result.__queryHash,
|
|
13273
13324
|
enumerable: false
|
|
13274
13325
|
});
|
|
13275
13326
|
}
|
|
13276
13327
|
if (typeof result?.totalCount === "number") {
|
|
13277
|
-
Object.defineProperty(
|
|
13328
|
+
Object.defineProperty(models2, "__totalCount", {
|
|
13278
13329
|
value: result.totalCount,
|
|
13279
13330
|
enumerable: false
|
|
13280
13331
|
});
|
|
13281
13332
|
}
|
|
13282
|
-
return
|
|
13333
|
+
return models2;
|
|
13283
13334
|
};
|
|
13284
13335
|
chain.first = async () => {
|
|
13285
13336
|
const all = await chain.find();
|
|
@@ -16451,1040 +16502,1096 @@ async function shareAttachment(config2, pair, project, thread, messages5, id) {
|
|
|
16451
16502
|
import { promisify as promisify5 } from "node:util";
|
|
16452
16503
|
import { execFile as execFile5 } from "node:child_process";
|
|
16453
16504
|
|
|
16454
|
-
// src/harness/
|
|
16455
|
-
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
|
|
16456
16511
|
import { homedir as homedir4 } from "node:os";
|
|
16457
|
-
import { connect } from "node:net";
|
|
16458
|
-
import { promisify as promisify2 } from "node:util";
|
|
16459
16512
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
16460
|
-
import {
|
|
16461
|
-
import {
|
|
16462
|
-
import {
|
|
16463
|
-
|
|
16464
|
-
|
|
16465
|
-
|
|
16466
|
-
var
|
|
16467
|
-
var
|
|
16468
|
-
|
|
16469
|
-
|
|
16470
|
-
{
|
|
16471
|
-
|
|
16472
|
-
|
|
16473
|
-
|
|
16474
|
-
|
|
16475
|
-
|
|
16476
|
-
|
|
16477
|
-
|
|
16478
|
-
|
|
16479
|
-
|
|
16480
|
-
|
|
16481
|
-
|
|
16482
|
-
|
|
16483
|
-
|
|
16484
|
-
|
|
16485
|
-
|
|
16486
|
-
|
|
16487
|
-
title: command2 ? "Allow command?" : "Allow file changes?",
|
|
16488
|
-
detail,
|
|
16489
|
-
choices: DECISIONS.filter((choice) => !unavailable || choice.id === "decline").map((choice) => ({ ...choice }))
|
|
16490
|
-
};
|
|
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) ?? [];
|
|
16491
16540
|
}
|
|
16492
|
-
|
|
16493
|
-
|
|
16494
|
-
|
|
16495
|
-
|
|
16496
|
-
|
|
16497
|
-
|
|
16498
|
-
|
|
16499
|
-
const
|
|
16500
|
-
|
|
16501
|
-
|
|
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) => {
|
|
16502
16557
|
try {
|
|
16503
|
-
const
|
|
16504
|
-
|
|
16505
|
-
|
|
16506
|
-
const
|
|
16507
|
-
|
|
16508
|
-
|
|
16509
|
-
|
|
16510
|
-
|
|
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();
|
|
16511
16568
|
} catch {
|
|
16512
16569
|
}
|
|
16513
|
-
}
|
|
16514
|
-
|
|
16515
|
-
|
|
16570
|
+
});
|
|
16571
|
+
child.stdin.write(
|
|
16572
|
+
`${JSON.stringify({ type: "control_request", request_id: "models", request: { subtype: "initialize" } })}
|
|
16573
|
+
`
|
|
16574
|
+
);
|
|
16575
|
+
});
|
|
16516
16576
|
}
|
|
16517
|
-
function
|
|
16518
|
-
|
|
16519
|
-
|
|
16520
|
-
|
|
16521
|
-
|
|
16522
|
-
|
|
16523
|
-
|
|
16524
|
-
}
|
|
16525
|
-
|
|
16526
|
-
|
|
16527
|
-
|
|
16528
|
-
return [
|
|
16529
|
-
{
|
|
16530
|
-
id: item.id,
|
|
16531
|
-
role: "tool",
|
|
16532
|
-
text: [item.command, item.aggregatedOutput].filter((value2) => typeof value2 === "string").join("\n\n")
|
|
16533
|
-
}
|
|
16534
|
-
];
|
|
16535
|
-
return [];
|
|
16536
|
-
}) : []);
|
|
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
|
+
];
|
|
16537
16588
|
}
|
|
16538
|
-
|
|
16539
|
-
|
|
16540
|
-
|
|
16541
|
-
|
|
16542
|
-
|
|
16543
|
-
|
|
16544
|
-
|
|
16545
|
-
|
|
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
|
+
};
|
|
16546
16613
|
}
|
|
16547
|
-
|
|
16548
|
-
|
|
16549
|
-
|
|
16550
|
-
|
|
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 {
|
|
16551
16621
|
}
|
|
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
|
-
|
|
16586
|
-
|
|
16587
|
-
|
|
16588
|
-
|
|
16589
|
-
|
|
16590
|
-
|
|
16591
|
-
|
|
16592
|
-
|
|
16593
|
-
|
|
16594
|
-
|
|
16595
|
-
|
|
16596
|
-
|
|
16597
|
-
|
|
16598
|
-
|
|
16599
|
-
|
|
16600
|
-
|
|
16601
|
-
|
|
16602
|
-
|
|
16603
|
-
|
|
16604
|
-
|
|
16605
|
-
|
|
16606
|
-
|
|
16607
|
-
|
|
16608
|
-
|
|
16609
|
-
|
|
16610
|
-
|
|
16611
|
-
|
|
16612
|
-
|
|
16613
|
-
|
|
16614
|
-
|
|
16615
|
-
|
|
16616
|
-
|
|
16617
|
-
|
|
16618
|
-
|
|
16619
|
-
|
|
16620
|
-
|
|
16621
|
-
|
|
16622
|
-
|
|
16623
|
-
|
|
16624
|
-
|
|
16625
|
-
|
|
16626
|
-
|
|
16627
|
-
|
|
16628
|
-
|
|
16629
|
-
|
|
16630
|
-
|
|
16631
|
-
|
|
16632
|
-
|
|
16633
|
-
|
|
16634
|
-
|
|
16635
|
-
|
|
16636
|
-
|
|
16637
|
-
|
|
16638
|
-
|
|
16639
|
-
|
|
16640
|
-
|
|
16641
|
-
|
|
16642
|
-
|
|
16643
|
-
|
|
16644
|
-
|
|
16645
|
-
|
|
16646
|
-
|
|
16647
|
-
|
|
16648
|
-
|
|
16649
|
-
|
|
16650
|
-
|
|
16651
|
-
|
|
16652
|
-
|
|
16653
|
-
|
|
16654
|
-
|
|
16655
|
-
|
|
16656
|
-
|
|
16657
|
-
|
|
16658
|
-
|
|
16659
|
-
|
|
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);
|
|
16676
|
+
return {
|
|
16677
|
+
id,
|
|
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
|
+
]
|
|
16691
|
+
};
|
|
16692
|
+
}
|
|
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() {
|
|
16738
|
+
if (process.env.DASHUP_LIVE_DISCOVERY === "0") return [];
|
|
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) => {
|
|
16743
|
+
try {
|
|
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" }];
|
|
16749
|
+
} catch {
|
|
16750
|
+
return [];
|
|
16751
|
+
}
|
|
16752
|
+
})
|
|
16753
|
+
);
|
|
16754
|
+
return found.flat();
|
|
16755
|
+
}
|
|
16756
|
+
var Claude = class {
|
|
16757
|
+
constructor(changed, socket = join4(dirname4(configPath()), "claude.sock")) {
|
|
16758
|
+
this.changed = changed;
|
|
16759
|
+
this.socket = socket;
|
|
16660
16760
|
}
|
|
16661
|
-
|
|
16662
|
-
|
|
16663
|
-
|
|
16664
|
-
|
|
16665
|
-
|
|
16666
|
-
|
|
16667
|
-
|
|
16668
|
-
|
|
16669
|
-
|
|
16670
|
-
|
|
16671
|
-
|
|
16672
|
-
|
|
16673
|
-
|
|
16674
|
-
|
|
16675
|
-
|
|
16676
|
-
|
|
16677
|
-
|
|
16678
|
-
|
|
16679
|
-
|
|
16761
|
+
changed;
|
|
16762
|
+
socket;
|
|
16763
|
+
watcher;
|
|
16764
|
+
notify;
|
|
16765
|
+
server;
|
|
16766
|
+
streams = /* @__PURE__ */ new Map();
|
|
16767
|
+
prompts = /* @__PURE__ */ new Map();
|
|
16768
|
+
requests = /* @__PURE__ */ new Map();
|
|
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();
|
|
16680
16809
|
});
|
|
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);
|
|
16681
16819
|
});
|
|
16682
16820
|
}
|
|
16683
|
-
|
|
16684
|
-
|
|
16685
|
-
|
|
16686
|
-
|
|
16687
|
-
|
|
16688
|
-
|
|
16689
|
-
|
|
16690
|
-
|
|
16691
|
-
|
|
16692
|
-
|
|
16693
|
-
|
|
16694
|
-
await this.connect();
|
|
16695
|
-
if (!this.controls) throw new Error("Update Codex to start threads from Dashup");
|
|
16696
|
-
const id = saved ? (await this.call("thread/resume", { threadId: saved, excludeTurns: true })).thread?.id : (await this.call("thread/start", { cwd })).thread?.id;
|
|
16697
|
-
if (typeof id !== "string") throw new Error("Codex did not open the thread");
|
|
16698
|
-
await this.call("turn/start", { threadId: id, input: [{ type: "text", text: text3, text_elements: [] }] });
|
|
16699
|
-
this.changed();
|
|
16700
|
-
return id;
|
|
16701
|
-
}
|
|
16702
|
-
async read() {
|
|
16703
|
-
await this.connect();
|
|
16704
|
-
const loaded = await this.call("thread/loaded/list", { limit: MAX_SESSIONS });
|
|
16705
|
-
if (!Array.isArray(loaded.data)) return [];
|
|
16706
|
-
const sessions = [];
|
|
16707
|
-
for (const id of loaded.data.slice(0, MAX_SESSIONS)) {
|
|
16708
|
-
if (typeof id !== "string") continue;
|
|
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;
|
|
16709
16832
|
try {
|
|
16710
|
-
|
|
16711
|
-
const thread = read.thread;
|
|
16712
|
-
if (thread?.id !== id || typeof thread.cwd !== "string" || !thread.cwd) continue;
|
|
16713
|
-
const approvals = [...this.approvals.values()].filter((value2) => value2.threadId === id).map((value2) => value2.approval);
|
|
16714
|
-
sessions.push({
|
|
16715
|
-
id,
|
|
16716
|
-
harness: "codex",
|
|
16717
|
-
cwd: thread.cwd,
|
|
16718
|
-
title: typeof thread.name === "string" && thread.name ? thread.name : typeof thread.preview === "string" && thread.preview ? thread.preview : "Codex thread",
|
|
16719
|
-
updatedAt: new Date(Number(thread.updatedAt ?? 0) * 1e3).toISOString(),
|
|
16720
|
-
status: approvals.length ? "attention" : status(thread),
|
|
16721
|
-
messages: messages2(thread),
|
|
16722
|
-
typing: this.typing.get(id)?.text,
|
|
16723
|
-
approvals,
|
|
16724
|
-
// An interrupt for a finished turn is never answered, so the active turn is read first.
|
|
16725
|
-
stop: this.controls ? (requestId) => this.once(requestId, async () => {
|
|
16726
|
-
const native = (await this.call("thread/read", { threadId: id, includeTurns: true })).thread;
|
|
16727
|
-
const turn = native?.status?.type === "active" && Array.isArray(native.turns) ? native.turns.at(-1) : void 0;
|
|
16728
|
-
if (turn?.status !== "inProgress" || typeof turn.id !== "string")
|
|
16729
|
-
throw new Error("Nothing is running in this Codex thread");
|
|
16730
|
-
await this.call("turn/interrupt", { threadId: id, turnId: turn.id });
|
|
16731
|
-
this.changed();
|
|
16732
|
-
}) : void 0,
|
|
16733
|
-
send: this.controls && thread.status?.type === "idle" ? (requestId, text3) => this.once(requestId, async () => {
|
|
16734
|
-
if (!text3.trim() || Buffer.byteLength(text3) > MAX_BYTES / 2)
|
|
16735
|
-
throw new Error("Message is empty or too large");
|
|
16736
|
-
const current = await this.call("thread/loaded/list", { limit: MAX_SESSIONS });
|
|
16737
|
-
if (!Array.isArray(current.data) || !current.data.includes(id))
|
|
16738
|
-
throw new Error("Codex thread is no longer loaded");
|
|
16739
|
-
const native = (await this.call("thread/read", { threadId: id, includeTurns: false })).thread;
|
|
16740
|
-
if (native?.id !== id || native.status?.type !== "idle")
|
|
16741
|
-
throw new Error("Codex thread is busy or unavailable");
|
|
16742
|
-
await this.call("turn/start", {
|
|
16743
|
-
threadId: id,
|
|
16744
|
-
clientUserMessageId: requestId,
|
|
16745
|
-
input: [{ type: "text", text: text3, text_elements: [] }]
|
|
16746
|
-
});
|
|
16747
|
-
this.changed();
|
|
16748
|
-
}) : void 0,
|
|
16749
|
-
approve: approvals.length ? (requestId, approvalId, reply) => this.once(requestId, async () => {
|
|
16750
|
-
const approval2 = this.approvals.get(approvalId);
|
|
16751
|
-
const choice = "choice" in reply ? reply.choice : void 0;
|
|
16752
|
-
const socket = this.socket;
|
|
16753
|
-
if (!approval2 || approval2.threadId !== id || !approval2.approval.choices.some((value2) => value2.id === choice) || socket?.readyState !== WebSocket2.OPEN)
|
|
16754
|
-
throw new Error("Codex approval is no longer pending");
|
|
16755
|
-
await new Promise((resolve3, reject) => socket.send(
|
|
16756
|
-
JSON.stringify({ id: approval2.requestId, result: { decision: choice } }),
|
|
16757
|
-
(error) => error ? reject(new Error("Codex approval could not be sent")) : resolve3()
|
|
16758
|
-
));
|
|
16759
|
-
this.approvals.delete(approvalId);
|
|
16760
|
-
this.changed();
|
|
16761
|
-
}) : void 0
|
|
16762
|
-
});
|
|
16833
|
+
asked = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
16763
16834
|
} catch {
|
|
16764
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();
|
|
16852
|
+
}
|
|
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();
|
|
16859
|
+
});
|
|
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.
|
|
16868
|
+
once(id, action) {
|
|
16869
|
+
const known = this.requests.get(id);
|
|
16870
|
+
if (known) return known;
|
|
16871
|
+
if (this.requests.size >= MAX_REQUESTS)
|
|
16872
|
+
this.requests.delete(this.requests.keys().next().value);
|
|
16873
|
+
const result = action();
|
|
16874
|
+
this.requests.set(id, result);
|
|
16875
|
+
return result;
|
|
16876
|
+
}
|
|
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 {
|
|
16765
16893
|
}
|
|
16766
|
-
return sessions;
|
|
16767
16894
|
}
|
|
16768
16895
|
stop() {
|
|
16769
|
-
this.stopped = true;
|
|
16770
16896
|
clearTimeout(this.notify);
|
|
16771
|
-
this.
|
|
16772
|
-
|
|
16773
|
-
|
|
16774
|
-
|
|
16775
|
-
|
|
16776
|
-
this.
|
|
16777
|
-
this.approvals.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;
|
|
16778
16903
|
}
|
|
16779
16904
|
};
|
|
16780
|
-
|
|
16781
|
-
|
|
16905
|
+
|
|
16906
|
+
// src/harness/headless.ts
|
|
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;
|
|
16782
16916
|
this.changed = changed;
|
|
16917
|
+
this.saved = saved;
|
|
16783
16918
|
}
|
|
16919
|
+
id;
|
|
16920
|
+
cwd;
|
|
16921
|
+
title;
|
|
16784
16922
|
changed;
|
|
16785
|
-
|
|
16786
|
-
|
|
16787
|
-
|
|
16788
|
-
|
|
16789
|
-
|
|
16790
|
-
|
|
16791
|
-
|
|
16792
|
-
|
|
16793
|
-
|
|
16794
|
-
|
|
16795
|
-
|
|
16796
|
-
|
|
16797
|
-
|
|
16798
|
-
|
|
16799
|
-
|
|
16800
|
-
|
|
16801
|
-
return client.start(cwd, text3, saved);
|
|
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();
|
|
16933
|
+
requests = /* @__PURE__ */ new Map();
|
|
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
|
+
`);
|
|
16802
16939
|
}
|
|
16803
|
-
|
|
16804
|
-
|
|
16940
|
+
interrupt() {
|
|
16941
|
+
this.write({
|
|
16942
|
+
type: "control_request",
|
|
16943
|
+
request_id: randomUUID3(),
|
|
16944
|
+
request: { subtype: "interrupt" }
|
|
16945
|
+
});
|
|
16805
16946
|
}
|
|
16806
|
-
|
|
16807
|
-
|
|
16808
|
-
|
|
16809
|
-
|
|
16810
|
-
|
|
16811
|
-
|
|
16812
|
-
}
|
|
16813
|
-
const results = await Promise.allSettled(
|
|
16814
|
-
[...paths].map(async (path) => {
|
|
16815
|
-
let client = this.clients.get(path);
|
|
16816
|
-
if (!client) {
|
|
16817
|
-
client = new Client(path, this.changed);
|
|
16818
|
-
this.clients.set(path, client);
|
|
16819
|
-
}
|
|
16820
|
-
return client.read();
|
|
16821
|
-
})
|
|
16822
|
-
);
|
|
16823
|
-
return results.flatMap((result) => result.status === "fulfilled" ? result.value : []);
|
|
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
|
+
});
|
|
16824
16953
|
}
|
|
16825
|
-
|
|
16826
|
-
|
|
16827
|
-
|
|
16954
|
+
once(id, action) {
|
|
16955
|
+
const known = this.requests.get(id);
|
|
16956
|
+
if (known) return known;
|
|
16957
|
+
if (this.requests.size >= MAX_REQUESTS2)
|
|
16958
|
+
throw new Error("Claude Code command history is full; restart Dashup before sending");
|
|
16959
|
+
const result = action();
|
|
16960
|
+
this.requests.set(id, result);
|
|
16961
|
+
return result;
|
|
16828
16962
|
}
|
|
16829
|
-
|
|
16830
|
-
|
|
16831
|
-
|
|
16832
|
-
|
|
16833
|
-
|
|
16834
|
-
|
|
16835
|
-
|
|
16836
|
-
|
|
16837
|
-
|
|
16838
|
-
|
|
16839
|
-
|
|
16840
|
-
|
|
16841
|
-
|
|
16842
|
-
|
|
16843
|
-
|
|
16844
|
-
|
|
16845
|
-
|
|
16846
|
-
|
|
16847
|
-
|
|
16848
|
-
|
|
16849
|
-
|
|
16850
|
-
|
|
16851
|
-
|
|
16852
|
-
|
|
16853
|
-
|
|
16854
|
-
|
|
16855
|
-
|
|
16856
|
-
|
|
16857
|
-
|
|
16858
|
-
|
|
16859
|
-
asking.delete(key);
|
|
16860
|
-
if (!models.length) return;
|
|
16861
|
-
offered.set(key, models);
|
|
16862
|
-
changed();
|
|
16863
|
-
});
|
|
16864
|
-
}
|
|
16865
|
-
return offered.get(key) ?? [];
|
|
16866
|
-
}
|
|
16867
|
-
function listModels(command2) {
|
|
16868
|
-
return new Promise((resolve3) => {
|
|
16869
|
-
const child = spawn2(
|
|
16870
|
-
command2,
|
|
16871
|
-
["-p", "--input-format", "stream-json", "--output-format", "stream-json", "--verbose"],
|
|
16872
|
-
{ cwd: homedir5(), stdio: "pipe", shell: process.platform === "win32" }
|
|
16963
|
+
// Resolves once Claude Code takes the message, or says why it could not start. `now` stops the
|
|
16964
|
+
// turn under way, so Claude Code takes the message at once.
|
|
16965
|
+
say(text3, now = false) {
|
|
16966
|
+
this.messages.push({ id: randomUUID3(), role: "user", text: text3 });
|
|
16967
|
+
this.typing = "";
|
|
16968
|
+
this.status = "working";
|
|
16969
|
+
this.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
16970
|
+
const message = { type: "user", message: { role: "user", content: text3 } };
|
|
16971
|
+
if (this.turn) {
|
|
16972
|
+
if (now) this.interrupt();
|
|
16973
|
+
this.open++;
|
|
16974
|
+
this.write(message);
|
|
16975
|
+
this.changed();
|
|
16976
|
+
return Promise.resolve();
|
|
16977
|
+
}
|
|
16978
|
+
const turn = this.turn = spawn3(
|
|
16979
|
+
"claude",
|
|
16980
|
+
[
|
|
16981
|
+
"-p",
|
|
16982
|
+
"--input-format",
|
|
16983
|
+
"stream-json",
|
|
16984
|
+
"--output-format",
|
|
16985
|
+
"stream-json",
|
|
16986
|
+
"--verbose",
|
|
16987
|
+
"--include-partial-messages",
|
|
16988
|
+
"--permission-prompt-tool",
|
|
16989
|
+
"stdio",
|
|
16990
|
+
...this.saved ? ["--resume", this.id] : ["--session-id", this.id]
|
|
16991
|
+
],
|
|
16992
|
+
{ cwd: this.cwd, stdio: "pipe", shell: process.platform === "win32" }
|
|
16873
16993
|
);
|
|
16874
|
-
|
|
16875
|
-
|
|
16994
|
+
this.open = 1;
|
|
16995
|
+
let errors = "";
|
|
16996
|
+
turn.stdin.on("error", () => {
|
|
16876
16997
|
});
|
|
16877
|
-
|
|
16878
|
-
|
|
16879
|
-
clearTimeout(timer);
|
|
16880
|
-
resolve3([]);
|
|
16998
|
+
turn.stderr.on("data", (chunk) => {
|
|
16999
|
+
errors = (errors + String(chunk)).slice(-2e3);
|
|
16881
17000
|
});
|
|
16882
|
-
|
|
16883
|
-
|
|
16884
|
-
|
|
16885
|
-
|
|
16886
|
-
|
|
16887
|
-
|
|
16888
|
-
|
|
16889
|
-
|
|
16890
|
-
|
|
16891
|
-
|
|
16892
|
-
|
|
16893
|
-
|
|
16894
|
-
}
|
|
16895
|
-
|
|
17001
|
+
const started = new Promise((resolve3, reject) => {
|
|
17002
|
+
createInterface3({ input: turn.stdout }).on("line", (line) => {
|
|
17003
|
+
if (this.receive(line)) resolve3();
|
|
17004
|
+
});
|
|
17005
|
+
const end = (reason) => {
|
|
17006
|
+
reject(new Error(reason));
|
|
17007
|
+
if (this.turn !== turn) return;
|
|
17008
|
+
this.turn = void 0;
|
|
17009
|
+
this.open = 0;
|
|
17010
|
+
this.approvals.clear();
|
|
17011
|
+
this.status = "idle";
|
|
17012
|
+
this.changed();
|
|
17013
|
+
};
|
|
17014
|
+
turn.once("error", () => end("Claude Code is not installed on this computer"));
|
|
17015
|
+
turn.once("exit", () => end(errors.trim() || "Claude Code stopped before starting"));
|
|
16896
17016
|
});
|
|
16897
|
-
|
|
16898
|
-
|
|
16899
|
-
|
|
16900
|
-
|
|
16901
|
-
});
|
|
16902
|
-
}
|
|
16903
|
-
function claudeOptions(models, recorded) {
|
|
16904
|
-
const ran = typeof recorded.model === "string" ? recorded.model : "";
|
|
16905
|
-
const current = models.find((model) => model.value !== "default" && model.resolvedModel === ran) ?? models.find((model) => model.value === ran);
|
|
16906
|
-
const choices = models.map((model) => ({ value: model.value, name: model.displayName }));
|
|
16907
|
-
if (ran && !current) choices.push({ value: ran, name: ran });
|
|
16908
|
-
if (!choices.length) return [];
|
|
16909
|
-
return [
|
|
16910
|
-
{ id: "model", name: "Model", value: current?.value ?? ran, choices },
|
|
16911
|
-
...current?.supportsFastMode ? [{ id: "fast", name: "Fast", value: recorded.fast === true }] : [],
|
|
16912
|
-
...current?.supportedEffortLevels?.includes("xhigh") ? [{ id: "ultracode", name: "Ultracode", value: recorded.ultracode === true }] : []
|
|
16913
|
-
];
|
|
16914
|
-
}
|
|
16915
|
-
var EDITS = /* @__PURE__ */ new Set(["Edit", "MultiEdit", "NotebookEdit", "Write"]);
|
|
16916
|
-
var SCOPE = 32;
|
|
16917
|
-
var preview2 = (text3) => Buffer.byteLength(text3) > MAX_PREVIEW ? Buffer.from(text3).subarray(0, MAX_PREVIEW).toString("utf8") : text3;
|
|
16918
|
-
function hunk(before, after, offset = 0) {
|
|
16919
|
-
const old = before ? before.replace(/\n$/, "").split("\n") : [];
|
|
16920
|
-
const next = after ? after.replace(/\n$/, "").split("\n") : [];
|
|
16921
|
-
let start2 = 0;
|
|
16922
|
-
while (start2 < old.length && start2 < next.length && old[start2] === next[start2]) start2++;
|
|
16923
|
-
let end = 0;
|
|
16924
|
-
while (end < old.length - start2 && end < next.length - start2 && old[old.length - 1 - end] === next[next.length - 1 - end])
|
|
16925
|
-
end++;
|
|
16926
|
-
const removed = old.slice(start2, old.length - end);
|
|
16927
|
-
const added2 = next.slice(start2, next.length - end);
|
|
16928
|
-
return {
|
|
16929
|
-
lines: [
|
|
16930
|
-
`@@ -${offset + start2 + 1},${removed.length} +${offset + start2 + 1},${added2.length} @@`,
|
|
16931
|
-
...old.slice(Math.max(0, start2 - 3), start2).map((line) => ` ${line}`),
|
|
16932
|
-
...removed.map((line) => `-${line}`),
|
|
16933
|
-
...added2.map((line) => `+${line}`),
|
|
16934
|
-
...old.slice(old.length - end, old.length - end + 3).map((line) => ` ${line}`)
|
|
16935
|
-
],
|
|
16936
|
-
added: added2.length,
|
|
16937
|
-
deleted: removed.length
|
|
16938
|
-
};
|
|
16939
|
-
}
|
|
16940
|
-
function change(tool, input) {
|
|
16941
|
-
const path = input.file_path;
|
|
16942
|
-
if (typeof path !== "string") return void 0;
|
|
16943
|
-
let current = "";
|
|
16944
|
-
try {
|
|
16945
|
-
current = readFileSync3(path, "utf8");
|
|
16946
|
-
} catch {
|
|
17017
|
+
for (const [option2, value2] of Object.entries(this.chosen)) this.apply(option2, value2);
|
|
17018
|
+
this.write(message);
|
|
17019
|
+
this.changed();
|
|
17020
|
+
return started;
|
|
16947
17021
|
}
|
|
16948
|
-
|
|
16949
|
-
|
|
16950
|
-
|
|
16951
|
-
|
|
16952
|
-
|
|
16953
|
-
|
|
16954
|
-
|
|
16955
|
-
|
|
16956
|
-
|
|
16957
|
-
|
|
16958
|
-
|
|
16959
|
-
|
|
16960
|
-
|
|
16961
|
-
|
|
16962
|
-
|
|
16963
|
-
|
|
16964
|
-
|
|
16965
|
-
|
|
16966
|
-
|
|
16967
|
-
}
|
|
16968
|
-
|
|
16969
|
-
|
|
16970
|
-
|
|
16971
|
-
|
|
16972
|
-
|
|
16973
|
-
|
|
16974
|
-
|
|
16975
|
-
|
|
16976
|
-
|
|
16977
|
-
|
|
16978
|
-
|
|
16979
|
-
|
|
16980
|
-
|
|
16981
|
-
|
|
16982
|
-
|
|
16983
|
-
|
|
16984
|
-
}
|
|
16985
|
-
|
|
16986
|
-
|
|
16987
|
-
|
|
16988
|
-
|
|
16989
|
-
|
|
16990
|
-
|
|
16991
|
-
|
|
16992
|
-
|
|
16993
|
-
|
|
16994
|
-
|
|
16995
|
-
|
|
16996
|
-
|
|
16997
|
-
|
|
16998
|
-
plan: preview2(String(input.plan ?? ""))
|
|
16999
|
-
};
|
|
17000
|
-
const diff = change(tool, input);
|
|
17001
|
-
const always = remember(request2.suggestions);
|
|
17002
|
-
return {
|
|
17003
|
-
id,
|
|
17004
|
-
title: tool === "Bash" ? "Allow command?" : EDITS.has(tool) ? "Allow file changes?" : `Allow ${request2.label ?? tool}?`,
|
|
17005
|
-
detail: preview2(
|
|
17006
|
-
[
|
|
17007
|
-
request2.description ?? (typeof input.description === "string" ? input.description : ""),
|
|
17008
|
-
typeof input.command === "string" ? input.command : diff ? "" : JSON.stringify(input, null, 2)
|
|
17009
|
-
].filter(Boolean).join("\n\n")
|
|
17010
|
-
),
|
|
17011
|
-
...diff ? { change: diff } : {},
|
|
17012
|
-
choices: [
|
|
17013
|
-
{ id: "accept", label: "Allow once" },
|
|
17014
|
-
...always ? [{ id: "always", label: always.label }] : [],
|
|
17015
|
-
{ id: "decline", label: "Reject" }
|
|
17016
|
-
]
|
|
17017
|
-
};
|
|
17018
|
-
}
|
|
17019
|
-
function decide(request2, approval2, reply) {
|
|
17020
|
-
const input = request2.input;
|
|
17021
|
-
if ("answers" in reply)
|
|
17022
|
-
return {
|
|
17023
|
-
behavior: "allow",
|
|
17024
|
-
updatedInput: {
|
|
17025
|
-
...input,
|
|
17026
|
-
answers: Object.fromEntries(
|
|
17027
|
-
(approval2.questions ?? []).map((item, index) => [
|
|
17028
|
-
item.question,
|
|
17029
|
-
reply.answers[index].join(", ")
|
|
17030
|
-
])
|
|
17031
|
-
)
|
|
17032
|
-
}
|
|
17033
|
-
};
|
|
17034
|
-
if (reply.choice === "accept")
|
|
17035
|
-
return {
|
|
17036
|
-
behavior: "allow",
|
|
17037
|
-
updatedInput: input,
|
|
17038
|
-
...approval2.plan === void 0 ? {} : { updatedPermissions: [{ type: "setMode", mode: "default", destination: "session" }] }
|
|
17039
|
-
};
|
|
17040
|
-
if (reply.choice === "always")
|
|
17022
|
+
// Handles one line of Claude Code's output, and says whether it started the session.
|
|
17023
|
+
receive(line) {
|
|
17024
|
+
let message;
|
|
17025
|
+
try {
|
|
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) {
|
|
17041
17072
|
return {
|
|
17042
|
-
|
|
17043
|
-
|
|
17044
|
-
|
|
17045
|
-
|
|
17046
|
-
|
|
17047
|
-
|
|
17048
|
-
|
|
17049
|
-
|
|
17050
|
-
|
|
17051
|
-
|
|
17052
|
-
|
|
17053
|
-
|
|
17054
|
-
|
|
17055
|
-
|
|
17056
|
-
|
|
17057
|
-
|
|
17058
|
-
|
|
17059
|
-
|
|
17060
|
-
|
|
17061
|
-
|
|
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
|
+
}
|
|
17062
17114
|
};
|
|
17063
|
-
|
|
17064
|
-
|
|
17065
|
-
const directory2 = join5(claudeHome(), "sessions");
|
|
17066
|
-
const names = await readdir2(directory2).catch(() => []);
|
|
17067
|
-
const found = await Promise.all(
|
|
17068
|
-
names.filter((name) => /^\d+\.json$/.test(name)).slice(0, MAX_SESSIONS2).map(async (name) => {
|
|
17069
|
-
try {
|
|
17070
|
-
const value2 = JSON.parse(await readFile2(join5(directory2, name), "utf8"));
|
|
17071
|
-
if (typeof value2.sessionId !== "string" || typeof value2.cwd !== "string") return [];
|
|
17072
|
-
process.kill(value2.pid, 0);
|
|
17073
|
-
const binary2 = await readlink(`/proc/${value2.pid}/exe`).catch(() => "");
|
|
17074
|
-
return [{ ...value2, command: /[\\/]claude(?:\.exe)?$/.test(binary2) ? binary2 : "claude" }];
|
|
17075
|
-
} catch {
|
|
17076
|
-
return [];
|
|
17077
|
-
}
|
|
17078
|
-
})
|
|
17079
|
-
);
|
|
17080
|
-
return found.flat();
|
|
17081
|
-
}
|
|
17082
|
-
var Claude = class {
|
|
17083
|
-
constructor(changed, socket = join5(dirname4(configPath()), "claude.sock")) {
|
|
17115
|
+
var Headless = class {
|
|
17116
|
+
constructor(changed) {
|
|
17084
17117
|
this.changed = changed;
|
|
17085
|
-
this.socket = socket;
|
|
17086
17118
|
}
|
|
17087
17119
|
changed;
|
|
17088
|
-
|
|
17089
|
-
watcher;
|
|
17120
|
+
sessions = /* @__PURE__ */ new Map();
|
|
17090
17121
|
notify;
|
|
17091
|
-
|
|
17092
|
-
|
|
17093
|
-
|
|
17094
|
-
|
|
17095
|
-
async refresh() {
|
|
17096
|
-
this.watch();
|
|
17097
|
-
return (await registrations()).map((session) => {
|
|
17098
|
-
const plugin = this.streams.has(session.sessionId);
|
|
17099
|
-
const models = claudeModels(this.changed, session.command, session.version);
|
|
17100
|
-
const approvals = [...this.prompts.values()].filter((prompt) => prompt.session === session.sessionId).map((prompt) => prompt.approval);
|
|
17101
|
-
return {
|
|
17102
|
-
id: session.sessionId,
|
|
17103
|
-
harness: "claude",
|
|
17104
|
-
cwd: session.cwd,
|
|
17105
|
-
updatedAt: new Date(session.updatedAt ?? 0).toISOString(),
|
|
17106
|
-
status: approvals.length ? "attention" : STATUS[session.status ?? ""] ?? "unknown",
|
|
17107
|
-
messages: [],
|
|
17108
|
-
approvals,
|
|
17109
|
-
approve: approvals.length ? (requestId, approvalId, reply) => this.once(requestId, async () => {
|
|
17110
|
-
const prompt = this.prompts.get(approvalId);
|
|
17111
|
-
if (prompt?.session !== session.sessionId)
|
|
17112
|
-
throw new Error("Claude Code approval is no longer pending");
|
|
17113
|
-
prompt.answer(decide(prompt.request, prompt.approval, reply));
|
|
17114
|
-
}) : void 0,
|
|
17115
|
-
send: plugin ? (requestId, text3, now) => this.once(requestId, () => this.deliver(session.sessionId, now ? { text: text3, now } : { text: text3 })) : void 0,
|
|
17116
|
-
stop: plugin ? (requestId) => this.once(requestId, () => this.deliver(session.sessionId, { now: true })) : void 0,
|
|
17117
|
-
now: plugin,
|
|
17118
|
-
options: (recorded) => claudeOptions(models, recorded),
|
|
17119
|
-
configure: plugin ? (requestId, option2, value2) => this.once(requestId, () => this.deliver(session.sessionId, { text: COMMANDS[option2](value2) })) : void 0
|
|
17120
|
-
};
|
|
17121
|
-
});
|
|
17122
|
-
}
|
|
17123
|
-
// Only the connector serves the plugin; one-off commands such as `dashup doctor` read alone.
|
|
17124
|
-
async listen() {
|
|
17125
|
-
if (this.server || process.env.DASHUP_LIVE_DISCOVERY === "0") return;
|
|
17126
|
-
const server = this.server = createServer2((request2, response) => {
|
|
17127
|
-
const [, id, prompt] = SESSION.exec(request2.url ?? "") ?? [];
|
|
17128
|
-
if (id && prompt && request2.method === "POST") return this.prompt(id, request2, response);
|
|
17129
|
-
if (!id || prompt || request2.method !== "GET") return void response.writeHead(404).end();
|
|
17130
|
-
this.streams.get(id)?.end();
|
|
17131
|
-
this.streams.set(id, response);
|
|
17132
|
-
response.once("close", () => {
|
|
17133
|
-
if (this.streams.get(id) === response) this.streams.delete(id);
|
|
17134
|
-
this.changed();
|
|
17135
|
-
});
|
|
17136
|
-
response.writeHead(200, { "Content-Type": "application/x-ndjson" }).flushHeaders();
|
|
17122
|
+
soon = () => {
|
|
17123
|
+
if (this.notify) return;
|
|
17124
|
+
this.notify = setTimeout(() => {
|
|
17125
|
+
this.notify = void 0;
|
|
17137
17126
|
this.changed();
|
|
17138
|
-
});
|
|
17139
|
-
|
|
17140
|
-
|
|
17141
|
-
|
|
17142
|
-
|
|
17143
|
-
|
|
17144
|
-
|
|
17145
|
-
|
|
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;
|
|
17146
17142
|
}
|
|
17147
|
-
//
|
|
17148
|
-
|
|
17149
|
-
const
|
|
17150
|
-
|
|
17151
|
-
|
|
17152
|
-
|
|
17153
|
-
|
|
17154
|
-
|
|
17155
|
-
|
|
17156
|
-
|
|
17157
|
-
|
|
17158
|
-
try {
|
|
17159
|
-
asked = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
17160
|
-
} catch {
|
|
17161
|
-
}
|
|
17162
|
-
if (typeof asked?.tool !== "string" || !asked.input || typeof asked.input !== "object")
|
|
17163
|
-
return void response.writeHead(400).end();
|
|
17164
|
-
const id = randomUUID3();
|
|
17165
|
-
const prompted = {
|
|
17166
|
-
tool: asked.tool,
|
|
17167
|
-
input: asked.input,
|
|
17168
|
-
...Array.isArray(asked.suggestions) ? { suggestions: asked.suggestions } : {}
|
|
17169
|
-
};
|
|
17170
|
-
this.prompts.set(id, {
|
|
17171
|
-
session,
|
|
17172
|
-
request: prompted,
|
|
17173
|
-
approval: ask(id, prompted, false),
|
|
17174
|
-
answer: (decision) => {
|
|
17175
|
-
this.prompts.delete(id);
|
|
17176
|
-
response.end(JSON.stringify(decision));
|
|
17177
|
-
this.changed();
|
|
17178
|
-
}
|
|
17179
|
-
});
|
|
17180
|
-
response.once("close", () => {
|
|
17181
|
-
if (this.prompts.delete(id)) this.changed();
|
|
17182
|
-
});
|
|
17183
|
-
response.writeHead(200, { "Content-Type": "application/json" }).flushHeaders();
|
|
17184
|
-
this.changed();
|
|
17185
|
-
});
|
|
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
|
+
}
|
|
17186
17154
|
}
|
|
17187
|
-
|
|
17188
|
-
const
|
|
17189
|
-
|
|
17190
|
-
return new Promise((resolve3, reject) => stream.write(`${JSON.stringify(delivery)}
|
|
17191
|
-
`, (error) => error ? reject(error) : resolve3()));
|
|
17155
|
+
async refresh() {
|
|
17156
|
+
const models2 = claudeModels(this.changed);
|
|
17157
|
+
return [...this.sessions.values()].map((session) => session.live(models2));
|
|
17192
17158
|
}
|
|
17193
|
-
|
|
17194
|
-
|
|
17195
|
-
const
|
|
17196
|
-
if (known) return known;
|
|
17197
|
-
if (this.requests.size >= MAX_REQUESTS)
|
|
17198
|
-
this.requests.delete(this.requests.keys().next().value);
|
|
17199
|
-
const result = action();
|
|
17200
|
-
this.requests.set(id, result);
|
|
17201
|
-
return result;
|
|
17159
|
+
stop() {
|
|
17160
|
+
clearTimeout(this.notify);
|
|
17161
|
+
for (const session of this.sessions.values()) session.end();
|
|
17202
17162
|
}
|
|
17203
|
-
|
|
17204
|
-
|
|
17205
|
-
|
|
17206
|
-
|
|
17207
|
-
|
|
17208
|
-
|
|
17209
|
-
|
|
17210
|
-
|
|
17211
|
-
|
|
17212
|
-
|
|
17213
|
-
|
|
17214
|
-
|
|
17215
|
-
|
|
17216
|
-
|
|
17217
|
-
|
|
17218
|
-
|
|
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
|
+
}
|
|
17219
17270
|
}
|
|
17220
17271
|
}
|
|
17221
|
-
|
|
17222
|
-
|
|
17223
|
-
|
|
17224
|
-
|
|
17225
|
-
|
|
17226
|
-
|
|
17227
|
-
|
|
17228
|
-
|
|
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";
|
|
17229
17301
|
}
|
|
17230
|
-
|
|
17231
|
-
|
|
17232
|
-
|
|
17233
|
-
|
|
17234
|
-
|
|
17235
|
-
var SWITCHES = { fast: "fastMode", ultracode: "ultracode" };
|
|
17236
|
-
var Session = class {
|
|
17237
|
-
// A resumed session is `saved` and keeps the title its history gives.
|
|
17238
|
-
constructor(id, cwd, title2, changed, saved = false) {
|
|
17239
|
-
this.id = id;
|
|
17240
|
-
this.cwd = cwd;
|
|
17241
|
-
this.title = title2;
|
|
17302
|
+
return "unknown";
|
|
17303
|
+
}
|
|
17304
|
+
var Client = class {
|
|
17305
|
+
constructor(path, changed) {
|
|
17306
|
+
this.path = path;
|
|
17242
17307
|
this.changed = changed;
|
|
17243
|
-
this.saved = saved;
|
|
17244
17308
|
}
|
|
17245
|
-
|
|
17246
|
-
cwd;
|
|
17247
|
-
title;
|
|
17309
|
+
path;
|
|
17248
17310
|
changed;
|
|
17249
|
-
|
|
17250
|
-
|
|
17251
|
-
|
|
17252
|
-
|
|
17253
|
-
|
|
17254
|
-
|
|
17255
|
-
|
|
17256
|
-
// The text Claude Code is writing, as far as it has streamed.
|
|
17257
|
-
typing = "";
|
|
17311
|
+
socket;
|
|
17312
|
+
connecting;
|
|
17313
|
+
stopped = false;
|
|
17314
|
+
sequence = 0;
|
|
17315
|
+
controls = false;
|
|
17316
|
+
notify;
|
|
17317
|
+
pending = /* @__PURE__ */ new Map();
|
|
17258
17318
|
approvals = /* @__PURE__ */ new Map();
|
|
17259
17319
|
requests = /* @__PURE__ */ new Map();
|
|
17260
|
-
//
|
|
17261
|
-
|
|
17262
|
-
|
|
17263
|
-
|
|
17264
|
-
|
|
17265
|
-
|
|
17266
|
-
|
|
17267
|
-
this.
|
|
17268
|
-
|
|
17269
|
-
|
|
17270
|
-
|
|
17271
|
-
|
|
17272
|
-
|
|
17273
|
-
|
|
17274
|
-
|
|
17275
|
-
type: "control_request",
|
|
17276
|
-
request_id: randomUUID4(),
|
|
17277
|
-
request: option2 === "model" ? { subtype: "set_model", model: value2 } : { subtype: "apply_flag_settings", settings: { [SWITCHES[option2]]: value2 } }
|
|
17278
|
-
});
|
|
17279
|
-
}
|
|
17280
|
-
once(id, action) {
|
|
17281
|
-
const known = this.requests.get(id);
|
|
17282
|
-
if (known) return known;
|
|
17283
|
-
if (this.requests.size >= MAX_REQUESTS2)
|
|
17284
|
-
throw new Error("Claude Code command history is full; restart Dashup before sending");
|
|
17285
|
-
const result = action();
|
|
17286
|
-
this.requests.set(id, result);
|
|
17287
|
-
return result;
|
|
17288
|
-
}
|
|
17289
|
-
// Resolves once Claude Code takes the message, or says why it could not start. `now` stops the
|
|
17290
|
-
// turn under way, so Claude Code takes the message at once.
|
|
17291
|
-
say(text3, now = false) {
|
|
17292
|
-
this.messages.push({ id: randomUUID4(), role: "user", text: text3 });
|
|
17293
|
-
this.typing = "";
|
|
17294
|
-
this.status = "working";
|
|
17295
|
-
this.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
17296
|
-
const message = { type: "user", message: { role: "user", content: text3 } };
|
|
17297
|
-
if (this.turn) {
|
|
17298
|
-
if (now) this.interrupt();
|
|
17299
|
-
this.open++;
|
|
17300
|
-
this.write(message);
|
|
17301
|
-
this.changed();
|
|
17302
|
-
return Promise.resolve();
|
|
17303
|
-
}
|
|
17304
|
-
const turn = this.turn = spawn3(
|
|
17305
|
-
"claude",
|
|
17306
|
-
[
|
|
17307
|
-
"-p",
|
|
17308
|
-
"--input-format",
|
|
17309
|
-
"stream-json",
|
|
17310
|
-
"--output-format",
|
|
17311
|
-
"stream-json",
|
|
17312
|
-
"--verbose",
|
|
17313
|
-
"--include-partial-messages",
|
|
17314
|
-
"--permission-prompt-tool",
|
|
17315
|
-
"stdio",
|
|
17316
|
-
...this.saved ? ["--resume", this.id] : ["--session-id", this.id]
|
|
17317
|
-
],
|
|
17318
|
-
{ cwd: this.cwd, stdio: "pipe", shell: process.platform === "win32" }
|
|
17319
|
-
);
|
|
17320
|
-
this.open = 1;
|
|
17321
|
-
let errors = "";
|
|
17322
|
-
turn.stdin.on("error", () => {
|
|
17323
|
-
});
|
|
17324
|
-
turn.stderr.on("data", (chunk) => {
|
|
17325
|
-
errors = (errors + String(chunk)).slice(-2e3);
|
|
17326
|
-
});
|
|
17327
|
-
const started = new Promise((resolve3, reject) => {
|
|
17328
|
-
createInterface3({ input: turn.stdout }).on("line", (line) => {
|
|
17329
|
-
if (this.receive(line)) resolve3();
|
|
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
|
|
17330
17335
|
});
|
|
17331
|
-
|
|
17332
|
-
|
|
17333
|
-
|
|
17334
|
-
|
|
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;
|
|
17336
17390
|
this.approvals.clear();
|
|
17337
|
-
this.
|
|
17338
|
-
|
|
17339
|
-
|
|
17340
|
-
|
|
17341
|
-
|
|
17342
|
-
|
|
17343
|
-
|
|
17344
|
-
|
|
17345
|
-
|
|
17346
|
-
|
|
17347
|
-
|
|
17348
|
-
|
|
17349
|
-
|
|
17350
|
-
|
|
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
|
+
})();
|
|
17351
17411
|
try {
|
|
17352
|
-
|
|
17353
|
-
} catch {
|
|
17354
|
-
|
|
17412
|
+
await this.connecting;
|
|
17413
|
+
} catch (error) {
|
|
17414
|
+
this.socket?.terminate();
|
|
17415
|
+
throw error;
|
|
17416
|
+
} finally {
|
|
17417
|
+
this.connecting = void 0;
|
|
17355
17418
|
}
|
|
17356
|
-
|
|
17357
|
-
|
|
17358
|
-
|
|
17359
|
-
|
|
17360
|
-
|
|
17361
|
-
|
|
17362
|
-
|
|
17363
|
-
|
|
17364
|
-
const
|
|
17365
|
-
|
|
17366
|
-
|
|
17367
|
-
|
|
17368
|
-
|
|
17369
|
-
|
|
17370
|
-
|
|
17371
|
-
|
|
17372
|
-
|
|
17373
|
-
|
|
17374
|
-
|
|
17375
|
-
|
|
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
|
+
}
|
|
17376
17439
|
});
|
|
17377
|
-
|
|
17378
|
-
|
|
17379
|
-
|
|
17380
|
-
|
|
17381
|
-
|
|
17382
|
-
|
|
17383
|
-
|
|
17384
|
-
|
|
17385
|
-
|
|
17386
|
-
|
|
17387
|
-
|
|
17388
|
-
|
|
17389
|
-
|
|
17390
|
-
|
|
17391
|
-
|
|
17392
|
-
}
|
|
17393
|
-
|
|
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: [] }] });
|
|
17394
17458
|
this.changed();
|
|
17395
|
-
return
|
|
17459
|
+
return id;
|
|
17396
17460
|
}
|
|
17397
|
-
|
|
17398
|
-
|
|
17399
|
-
|
|
17400
|
-
|
|
17401
|
-
|
|
17402
|
-
|
|
17403
|
-
|
|
17404
|
-
|
|
17405
|
-
|
|
17406
|
-
|
|
17407
|
-
|
|
17408
|
-
|
|
17409
|
-
|
|
17410
|
-
|
|
17411
|
-
|
|
17412
|
-
|
|
17413
|
-
|
|
17414
|
-
|
|
17415
|
-
|
|
17416
|
-
|
|
17417
|
-
|
|
17418
|
-
|
|
17419
|
-
|
|
17420
|
-
|
|
17421
|
-
|
|
17422
|
-
|
|
17423
|
-
|
|
17424
|
-
|
|
17425
|
-
|
|
17426
|
-
|
|
17427
|
-
|
|
17428
|
-
|
|
17429
|
-
|
|
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
|
|
17430
17529
|
});
|
|
17431
|
-
|
|
17432
|
-
|
|
17433
|
-
|
|
17434
|
-
|
|
17435
|
-
};
|
|
17530
|
+
} catch {
|
|
17531
|
+
}
|
|
17532
|
+
}
|
|
17533
|
+
return sessions;
|
|
17436
17534
|
}
|
|
17437
|
-
|
|
17438
|
-
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();
|
|
17439
17545
|
}
|
|
17440
17546
|
};
|
|
17441
|
-
var
|
|
17547
|
+
var Codex = class {
|
|
17442
17548
|
constructor(changed) {
|
|
17443
17549
|
this.changed = changed;
|
|
17444
17550
|
}
|
|
17445
17551
|
changed;
|
|
17446
|
-
|
|
17447
|
-
|
|
17448
|
-
|
|
17449
|
-
|
|
17450
|
-
|
|
17451
|
-
|
|
17452
|
-
|
|
17453
|
-
|
|
17454
|
-
|
|
17455
|
-
|
|
17456
|
-
|
|
17457
|
-
|
|
17458
|
-
|
|
17459
|
-
|
|
17460
|
-
|
|
17461
|
-
try {
|
|
17462
|
-
await session.say(text3);
|
|
17463
|
-
} catch (error) {
|
|
17464
|
-
this.sessions.delete(session.id);
|
|
17465
|
-
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);
|
|
17466
17567
|
}
|
|
17467
|
-
return
|
|
17568
|
+
return client.start(cwd, text3, saved);
|
|
17468
17569
|
}
|
|
17469
|
-
// Continues a saved session with a message; Claude Code resumes it from its own transcript.
|
|
17470
17570
|
async resume(id, cwd, text3) {
|
|
17471
|
-
|
|
17472
|
-
const session = known ?? new Session(id, cwd, void 0, this.soon, true);
|
|
17473
|
-
this.sessions.set(id, session);
|
|
17474
|
-
try {
|
|
17475
|
-
await session.say(text3);
|
|
17476
|
-
} catch (error) {
|
|
17477
|
-
if (!known) this.sessions.delete(id);
|
|
17478
|
-
throw error;
|
|
17479
|
-
}
|
|
17571
|
+
await this.start(cwd, text3, id);
|
|
17480
17572
|
}
|
|
17481
17573
|
async refresh() {
|
|
17482
|
-
const
|
|
17483
|
-
|
|
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 : []);
|
|
17484
17591
|
}
|
|
17485
17592
|
stop() {
|
|
17486
|
-
|
|
17487
|
-
|
|
17593
|
+
for (const client of this.clients.values()) client.stop();
|
|
17594
|
+
this.clients.clear();
|
|
17488
17595
|
}
|
|
17489
17596
|
};
|
|
17490
17597
|
|
|
@@ -17847,14 +17954,14 @@ var Host = class {
|
|
|
17847
17954
|
}
|
|
17848
17955
|
async read() {
|
|
17849
17956
|
await this.connect();
|
|
17850
|
-
const
|
|
17957
|
+
const catalog2 = await this.client.request("listSessions", {
|
|
17851
17958
|
channel: ROOT_CHANNEL,
|
|
17852
17959
|
limit: MAX_SESSIONS3
|
|
17853
17960
|
});
|
|
17854
|
-
if (!Array.isArray(
|
|
17961
|
+
if (!Array.isArray(catalog2.items)) return [];
|
|
17855
17962
|
const result = [];
|
|
17856
17963
|
const active = /* @__PURE__ */ new Set();
|
|
17857
|
-
for (const summary of
|
|
17964
|
+
for (const summary of catalog2.items.slice(0, MAX_SESSIONS3)) {
|
|
17858
17965
|
try {
|
|
17859
17966
|
const session = await this.subscribe(summary.resource);
|
|
17860
17967
|
if (!session || session.lifecycle !== "ready" || !Array.isArray(session.chats)) continue;
|
|
@@ -18571,6 +18678,12 @@ var LiveHarnesses = class {
|
|
|
18571
18678
|
});
|
|
18572
18679
|
return this.refreshing;
|
|
18573
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
|
+
}
|
|
18574
18687
|
// The agents whose own command runs on this computer. Claude Code is asked its models again.
|
|
18575
18688
|
async agents() {
|
|
18576
18689
|
const harnesses = ["claude", "codex", "opencode"];
|
|
@@ -18636,7 +18749,7 @@ import { randomUUID as randomUUID6 } from "node:crypto";
|
|
|
18636
18749
|
import { spawn as spawn5 } from "node:child_process";
|
|
18637
18750
|
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
18638
18751
|
import { join as join8, dirname as dirname6, basename as basename4 } from "node:path";
|
|
18639
|
-
import { statSync as
|
|
18752
|
+
import { statSync as statSync3, existsSync as existsSync4, readdirSync as readdirSync3, readFileSync as readFileSync5, readlinkSync, realpathSync as realpathSync2 } from "node:fs";
|
|
18640
18753
|
var CODEX_VERSION = "0.160.1";
|
|
18641
18754
|
var REQUEST_MS4 = 1e4;
|
|
18642
18755
|
var MAX_BYTES4 = 1024 * 1024;
|
|
@@ -18645,7 +18758,7 @@ var MAX_FILES = 4096;
|
|
|
18645
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;
|
|
18646
18759
|
function identity(path) {
|
|
18647
18760
|
const canonical2 = realpathSync2.native(path);
|
|
18648
|
-
const file =
|
|
18761
|
+
const file = statSync3(canonical2);
|
|
18649
18762
|
if (!file.isFile() || process.getuid && file.uid !== process.getuid())
|
|
18650
18763
|
throw new Error("Native queue files are not owned by this user");
|
|
18651
18764
|
return { path: canonical2, device: file.dev, inode: file.ino };
|
|
@@ -18658,17 +18771,17 @@ function packageVersion(executable) {
|
|
|
18658
18771
|
for (let depth = 0; depth < 8; depth++, directory2 = dirname6(directory2)) {
|
|
18659
18772
|
const path = join8(directory2, "package.json");
|
|
18660
18773
|
if (!existsSync4(path)) continue;
|
|
18661
|
-
const value2 = JSON.parse(
|
|
18774
|
+
const value2 = JSON.parse(readFileSync5(path, "utf8"));
|
|
18662
18775
|
if (value2.name?.startsWith("@openai/codex-")) return value2.version;
|
|
18663
18776
|
}
|
|
18664
18777
|
return void 0;
|
|
18665
18778
|
}
|
|
18666
18779
|
function processOwners(pid) {
|
|
18667
18780
|
const root = `/proc/${pid}`;
|
|
18668
|
-
if (process.getuid &&
|
|
18781
|
+
if (process.getuid && statSync3(root).uid !== process.getuid()) return [];
|
|
18669
18782
|
const executable = realpathSync2.native(join8(root, "exe"));
|
|
18670
18783
|
if (basename4(executable) !== "codex" || packageVersion(executable) !== CODEX_VERSION) return [];
|
|
18671
|
-
const status2 =
|
|
18784
|
+
const status2 = readFileSync5(join8(root, "stat"), "utf8");
|
|
18672
18785
|
const start2 = status2.slice(status2.lastIndexOf(")") + 2).split(" ")[19];
|
|
18673
18786
|
if (!start2 || !/^\d+$/.test(start2)) return [];
|
|
18674
18787
|
const fds = readdirSync3(join8(root, "fd")).slice(0, MAX_FILES);
|
|
@@ -18684,10 +18797,10 @@ function processOwners(pid) {
|
|
|
18684
18797
|
const thread = basename4(path, ".lock");
|
|
18685
18798
|
if (!THREAD_ID.test(thread)) return [];
|
|
18686
18799
|
try {
|
|
18687
|
-
const lock =
|
|
18800
|
+
const lock = readFileSync5(join8(root, "fdinfo", fd), "utf8");
|
|
18688
18801
|
if (!/FLOCK\s+ADVISORY\s+WRITE/.test(lock)) return [];
|
|
18689
18802
|
const home = realpathSync2.native(dirname6(dirname6(path)));
|
|
18690
|
-
const directory2 =
|
|
18803
|
+
const directory2 = statSync3(home);
|
|
18691
18804
|
if (process.getuid && directory2.uid !== process.getuid()) return [];
|
|
18692
18805
|
const queue = identity(join8(home, "queue_1.sqlite"));
|
|
18693
18806
|
if (!files2.some((file) => {
|
|
@@ -18755,7 +18868,7 @@ function validateOwner(owner) {
|
|
|
18755
18868
|
throw new Error("The original native conversation owner is no longer available");
|
|
18756
18869
|
}
|
|
18757
18870
|
function validateEvidence(owner) {
|
|
18758
|
-
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))
|
|
18759
18872
|
throw new Error("The original native history storage changed");
|
|
18760
18873
|
const path = join8(owner.home, "thread_history_1.sqlite");
|
|
18761
18874
|
if (existsSync4(path) && identity(path).path !== path)
|
|
@@ -19293,7 +19406,7 @@ var Connector = class {
|
|
|
19293
19406
|
const started = threads.filter((thread) => !thread.parent);
|
|
19294
19407
|
this.listedProjects.set(project.id, {
|
|
19295
19408
|
...project,
|
|
19296
|
-
active: started.reduce((
|
|
19409
|
+
active: started.reduce((newest2, thread) => thread.updatedAt > newest2 ? thread.updatedAt : newest2, ""),
|
|
19297
19410
|
started: started.length
|
|
19298
19411
|
});
|
|
19299
19412
|
}
|
|
@@ -19674,11 +19787,22 @@ var Connector = class {
|
|
|
19674
19787
|
};
|
|
19675
19788
|
}
|
|
19676
19789
|
thread(pair, project, thread, history) {
|
|
19790
|
+
const state = this.threadState(project, thread);
|
|
19677
19791
|
return this.send(pair, {
|
|
19678
|
-
...
|
|
19792
|
+
...state,
|
|
19793
|
+
thread: this.recorded(state.thread),
|
|
19679
19794
|
...history ? { history } : {}
|
|
19680
19795
|
});
|
|
19681
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
|
+
}
|
|
19682
19806
|
async command(pair, command2) {
|
|
19683
19807
|
if (command2.action.kind === "projects.refresh") {
|
|
19684
19808
|
await this.findAgents();
|