@staix/agent-hub 0.15.0 → 0.16.0
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/CHANGELOG.md +25 -0
- package/README.md +12 -8
- package/docs/agent-notes/README.md +4 -0
- package/docs/agent-notes/adapters.md +6 -3
- package/docs/agent-notes/budget.md +4 -2
- package/docs/agent-notes/daemon.md +9 -3
- package/docs/agent-notes/local-worker.md +2 -2
- package/docs/agent-notes/tasks.md +13 -1
- package/docs/agent-notes/tests.md +3 -0
- package/docs/dogfood.md +13 -4
- package/docs/events.md +1 -1
- package/docs/memory.md +78 -10
- package/docs/operations.md +116 -30
- package/docs/quickstart.md +8 -4
- package/docs/security.md +80 -18
- package/docs/smoke.md +7 -3
- package/docs/specs/2026-09-19-agent-hub-design.md +84 -8
- package/docs/verification/2026-10-24-always-ask-adapters.md +52 -0
- package/docs/verification/2026-10-24-amem-runtime.md +69 -0
- package/docs/verification/2026-10-24-codex-0164-reviewer-isolation.md +55 -0
- package/docs/verification/2026-10-24-delivery-holds-ac8.md +264 -0
- package/docs/verification/2026-10-24-hardcoded-values.md +2108 -0
- package/docs/verification/2026-10-25-0.16.0.md +82 -0
- package/docs/verified.json +26 -17
- package/package.json +2 -2
- package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-hub/server.js +2085 -1702
- package/src/adapters/acp.ts +5 -2
- package/src/adapters/codex-appserver.ts +69 -1
- package/src/adapters/compat.ts +2 -2
- package/src/adapters/definitions/builtin-metadata.ts +3 -3
- package/src/adapters/local-worker.ts +5 -4
- package/src/adapters/registry-types.ts +1 -1
- package/src/cli/console-state.ts +20 -2
- package/src/cli/console-symbols.ts +3 -2
- package/src/cli/console-usage.ts +5 -2
- package/src/cli/console.ts +5 -1
- package/src/cli/help.ts +7 -5
- package/src/cli/identity.ts +3 -1
- package/src/cli/main.ts +44 -10
- package/src/cli/memory.ts +119 -7
- package/src/cli/output.ts +12 -4
- package/src/hub/action-class.ts +211 -17
- package/src/hub/action-enforce.ts +13 -5
- package/src/hub/action-policy.ts +19 -8
- package/src/hub/ask.ts +16 -4
- package/src/hub/board.ts +86 -31
- package/src/hub/budget-notes.ts +65 -0
- package/src/hub/budget.ts +42 -10
- package/src/hub/bus.ts +1 -1
- package/src/hub/code-facts.ts +56 -16
- package/src/hub/conductor.ts +5 -2
- package/src/hub/config-apply.ts +7 -0
- package/src/hub/control-client.ts +1 -1
- package/src/hub/daemon.ts +250 -49
- package/src/hub/envelope.ts +1 -1
- package/src/hub/events.ts +2 -2
- package/src/hub/hub-tools.ts +3 -3
- package/src/hub/kimi-usage.ts +12 -0
- package/src/hub/lifecycle.ts +10 -5
- package/src/hub/memory-code-head.ts +44 -0
- package/src/hub/memory-code-overlay.ts +119 -0
- package/src/hub/memory-plan-guard.ts +10 -1
- package/src/hub/memory-stale-anchors.ts +159 -0
- package/src/hub/memory-symbols.ts +74 -0
- package/src/hub/planning.ts +10 -4
- package/src/hub/read-tools.ts +21 -3
- package/src/hub/sdlc.ts +34 -0
- package/src/hub/settings.ts +17 -2
- package/src/hub/settled-roles.ts +15 -3
- package/src/hub/task-sweep.ts +25 -3
- package/src/hub/tasks.ts +353 -64
- package/src/hub/token-window.ts +15 -2
- package/src/hub/window-kind.ts +13 -0
- package/src/local/tools.ts +5 -5
- package/src/memory/brief.ts +22 -12
- package/src/memory/capture.ts +26 -40
- package/src/memory/client.ts +137 -65
- package/src/memory/recall.ts +13 -22
- package/src/ui/index.html +100 -18
|
@@ -47,7 +47,7 @@ var __esm = (fn, res, err) => () => {
|
|
|
47
47
|
// node_modules/@staix/agent-mem/src/memory/platform.ts
|
|
48
48
|
import { realpathSync } from "fs";
|
|
49
49
|
import { basename, dirname, join, resolve, sep } from "path";
|
|
50
|
-
import {
|
|
50
|
+
import { createRequire } from "module";
|
|
51
51
|
import { createHash } from "crypto";
|
|
52
52
|
function canonicalPath(path) {
|
|
53
53
|
return canonical(path);
|
|
@@ -59,7 +59,7 @@ function realPath(path) {
|
|
|
59
59
|
if (!resolve(path).includes("\\"))
|
|
60
60
|
throw error;
|
|
61
61
|
const argv = process.platform === "darwin" ? ["/bin/realpath", "--", path] : ["realpath", "-e", "--", path];
|
|
62
|
-
const r = spawnSync(argv[0], argv.slice(1), { encoding: "utf8" });
|
|
62
|
+
const r = childProcess().spawnSync(argv[0], argv.slice(1), { encoding: "utf8" });
|
|
63
63
|
if (r.status !== 0 || !r.stdout.endsWith(`
|
|
64
64
|
`))
|
|
65
65
|
throw error;
|
|
@@ -82,11 +82,11 @@ function memoryGitEnv(env = process.env, options = {}) {
|
|
|
82
82
|
}
|
|
83
83
|
function memoryGitSync(args, options) {
|
|
84
84
|
const argv = memoryGitArgv(args);
|
|
85
|
-
return spawnSync(argv[0], argv.slice(1), { ...options, env: memoryGitEnv(options.env ?? process.env) });
|
|
85
|
+
return childProcess().spawnSync(argv[0], argv.slice(1), { ...options, env: memoryGitEnv(options.env ?? process.env) });
|
|
86
86
|
}
|
|
87
87
|
function processSignature(pid) {
|
|
88
88
|
try {
|
|
89
|
-
const text = execFileSync("ps", ["-p", String(pid), "-o", "lstart=,comm="], { encoding: "utf8", env: { ...process.env, LC_ALL: "C", TZ: "UTC" } }).trim();
|
|
89
|
+
const text = childProcess().execFileSync("ps", ["-p", String(pid), "-o", "lstart=,comm="], { encoding: "utf8", env: { ...process.env, LC_ALL: "C", TZ: "UTC" } }).trim();
|
|
90
90
|
return text ? createHash("sha256").update(text).digest("hex") : undefined;
|
|
91
91
|
} catch {
|
|
92
92
|
return;
|
|
@@ -110,7 +110,7 @@ function processLiveness(pid, signature, identity = processSignature) {
|
|
|
110
110
|
return error.code === "ESRCH" ? "gone" : "unknown";
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
|
-
var canonical = (path) => {
|
|
113
|
+
var require2, processes, childProcess = () => processes ??= require2("node:child_process"), canonical = (path) => {
|
|
114
114
|
const absolute = resolve(path);
|
|
115
115
|
try {
|
|
116
116
|
return realpathSync(absolute);
|
|
@@ -120,6 +120,7 @@ var canonical = (path) => {
|
|
|
120
120
|
}
|
|
121
121
|
}, DENY_SEGMENTS, DENY_NAMES, hasSegment = (rel, seg) => `/${rel}/`.includes(`/${seg}/`), SAFE_CONFIG, CREDENTIAL_KEYS, memoryGitArgv = (args, opts = {}) => ["git", "--no-pager", ...SAFE_CONFIG.filter((c) => !opts.credentials || !CREDENTIAL_KEYS.has(c)).flatMap((c) => ["-c", c]), ...args];
|
|
122
122
|
var init_platform = __esm(() => {
|
|
123
|
+
require2 = createRequire(import.meta.url);
|
|
123
124
|
DENY_SEGMENTS = [".maru/secrets", ".agenthub/state", ".agenthub/archive"];
|
|
124
125
|
DENY_NAMES = ["^\\.env", "\\.pem$", "\\.key$", "^id_rsa", "^id_ed25519", "^credentials(\\.|$)", "^auth\\.json$", "^\\.netrc$", "^\\.npmrc$", "^\\.pypirc$"];
|
|
125
126
|
SAFE_CONFIG = ["core.fsmonitor=false", "core.hooksPath=/dev/null", "diff.external=", "core.pager=cat", "credential.helper=", "core.askpass="];
|
|
@@ -300,7 +301,7 @@ var init_compat = __esm(() => {
|
|
|
300
301
|
},
|
|
301
302
|
"codex-review": {
|
|
302
303
|
label: "Codex reviewer (codex-cli)",
|
|
303
|
-
verified: ["0.146.0", "0.163.0"],
|
|
304
|
+
verified: ["0.146.0", "0.163.0", "0.164.0"],
|
|
304
305
|
gate: "isolation",
|
|
305
306
|
probe: ["codex", "--version"],
|
|
306
307
|
config: "codex_bin",
|
|
@@ -1020,7 +1021,7 @@ var init_child_process = __esm(() => {
|
|
|
1020
1021
|
});
|
|
1021
1022
|
|
|
1022
1023
|
// src/adapters/agy/entry.ts
|
|
1023
|
-
import { spawn, spawnSync
|
|
1024
|
+
import { spawn, spawnSync } from "child_process";
|
|
1024
1025
|
import { resolve as resolve4 } from "path";
|
|
1025
1026
|
function agyTuiSpawnOptions(cwd, env) {
|
|
1026
1027
|
return { cwd, env, stdio: "inherit" };
|
|
@@ -1078,7 +1079,7 @@ async function runAgyEntry(ctx) {
|
|
|
1078
1079
|
console.log(JSON.stringify({ command: "agy", args: argv.map(publicArg), mode: "tui", permission: "unmanaged", mcp: ".agents/plugins/agent-hub/", writes: false }));
|
|
1079
1080
|
return;
|
|
1080
1081
|
}
|
|
1081
|
-
const version =
|
|
1082
|
+
const version = spawnSync("agy", ["--version"], { encoding: "utf8", timeout: 5000, maxBuffer: 4096, env: peerChildEnv("agy") });
|
|
1082
1083
|
const gate = agyVersionGate(version.status === 0 ? version.stdout : "");
|
|
1083
1084
|
if (!gate.ok)
|
|
1084
1085
|
fail(gate.message);
|
|
@@ -1802,7 +1803,7 @@ var init_claude_print = __esm(() => {
|
|
|
1802
1803
|
import { createHash as createHash2 } from "crypto";
|
|
1803
1804
|
import { spawn as spawn3 } from "child_process";
|
|
1804
1805
|
import { existsSync as existsSync2, lstatSync as lstatSync4, mkdirSync as mkdirSync3, mkdtempSync, readFileSync as readFileSync3, rmSync, statSync, symlinkSync, writeFileSync as writeFileSync3 } from "fs";
|
|
1805
|
-
import { createRequire } from "module";
|
|
1806
|
+
import { createRequire as createRequire2 } from "module";
|
|
1806
1807
|
import { homedir as homedir2, tmpdir } from "os";
|
|
1807
1808
|
import { basename as basename2, delimiter, dirname as dirname4, isAbsolute as isAbsolute3, join as join5, relative, resolve as resolve5 } from "path";
|
|
1808
1809
|
function codexCoreDenyGlobs(names = DENY_NAMES, segments = DENY_SEGMENTS) {
|
|
@@ -1934,7 +1935,7 @@ function resolveCommand(ctx, cwd) {
|
|
|
1934
1935
|
if (!target || !["arm64", "x64"].includes(process.arch))
|
|
1935
1936
|
throw new Error("unverified native platform");
|
|
1936
1937
|
const suffix = `${process.platform}-${process.arch === "x64" ? "x64" : "arm64"}`;
|
|
1937
|
-
const manifest =
|
|
1938
|
+
const manifest = createRequire2(binary).resolve(`@openai/codex-${suffix}/package.json`);
|
|
1938
1939
|
binary = realPath(join5(dirname4(manifest), "vendor", target, "bin", "codex"));
|
|
1939
1940
|
}
|
|
1940
1941
|
const files = [binary];
|
|
@@ -2534,1680 +2535,1776 @@ var nonnegative = (n) => typeof n === "number" && Number.isFinite(n) && n >= 0,
|
|
|
2534
2535
|
// src/hub/context-window.ts
|
|
2535
2536
|
var init_context_window = () => {};
|
|
2536
2537
|
|
|
2537
|
-
// src/
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2538
|
+
// src/hub/activity.ts
|
|
2539
|
+
function activityText(value) {
|
|
2540
|
+
return typeof value === "string" ? value.slice(0, TEXT_CAP) : "";
|
|
2541
|
+
}
|
|
2542
|
+
function updateActivity(u) {
|
|
2543
|
+
if (u?.sessionUpdate === "agent_message_chunk" || u?.sessionUpdate === "agent_thought_chunk") {
|
|
2544
|
+
if (u.content?.type !== "text" || typeof u.content.text !== "string")
|
|
2545
|
+
return;
|
|
2546
|
+
return { kind: u.sessionUpdate === "agent_message_chunk" ? "message" : "thought", text: activityText(u.content.text) };
|
|
2547
|
+
}
|
|
2548
|
+
if (["tool_call", "tool_call_update"].includes(u?.sessionUpdate)) {
|
|
2549
|
+
const fields = [u.toolCallId, u.title, u.kind, u.status].filter((v) => typeof v === "string").map(activityText);
|
|
2550
|
+
for (const location of Array.isArray(u.locations) ? u.locations.slice(0, 12) : []) {
|
|
2551
|
+
if (typeof location?.path === "string")
|
|
2552
|
+
fields.push(activityText(location.path));
|
|
2553
|
+
}
|
|
2554
|
+
for (const c of Array.isArray(u.content) ? u.content.slice(0, 12) : []) {
|
|
2555
|
+
if (c?.type === "diff" && typeof c.path === "string") {
|
|
2556
|
+
const lines = (v) => typeof v === "string" && v ? v.split(`
|
|
2557
|
+
`).length : 0;
|
|
2558
|
+
fields.push(`+${lines(c.newText)} -${lines(c.oldText)} ${activityText(c.path)}`);
|
|
2559
|
+
} else if (c?.type === "content" && c.content?.type === "text") {
|
|
2560
|
+
fields.push(activityText(c.content.text).split(`
|
|
2561
|
+
`).slice(-8).join(`
|
|
2562
|
+
`));
|
|
2563
|
+
} else if (c?.type === "terminal")
|
|
2564
|
+
fields.push("terminal output not reported by this update");
|
|
2565
|
+
}
|
|
2566
|
+
return { kind: "tool", text: fields.join(" ").slice(0, TEXT_CAP) };
|
|
2544
2567
|
}
|
|
2568
|
+
if (u?.sessionUpdate === "plan" && Array.isArray(u.entries))
|
|
2569
|
+
return { kind: "plan", text: u.entries.slice(0, 32).map((e) => [e?.status, e?.priority, e?.content].filter((v) => typeof v === "string").map(activityText).join(" ")).join(`
|
|
2570
|
+
`).slice(0, TEXT_CAP) };
|
|
2545
2571
|
}
|
|
2546
|
-
var
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2572
|
+
var TEXT_CAP = 4096;
|
|
2573
|
+
|
|
2574
|
+
// src/adapters/acp-quota.ts
|
|
2575
|
+
function acpQuotaRefusal(message) {
|
|
2576
|
+
if (!message.startsWith(KIMI_2_1_1_5H_REFUSAL_PREFIX))
|
|
2577
|
+
return;
|
|
2578
|
+
return { id: "5h", used: 1, windowMins: 300, source: "kimi ACP usage-limit refusal" };
|
|
2579
|
+
}
|
|
2580
|
+
var KIMI_2_1_1_5H_REFUSAL_PREFIX = "Authentication required: 403 You've reached your 5-hour usage limit. Your quota will reset when the current 5-hour window ends.";
|
|
2581
|
+
|
|
2582
|
+
// src/adapters/definitions/kimi-native.ts
|
|
2583
|
+
var KIMI_MODE_IDS;
|
|
2584
|
+
var init_kimi_native = __esm(() => {
|
|
2585
|
+
KIMI_MODE_IDS = { ask: "default", "ask-when-needed": "yolo", "never-ask": "auto" };
|
|
2586
|
+
});
|
|
2587
|
+
|
|
2588
|
+
// src/adapters/definitions/qwen-native.ts
|
|
2589
|
+
function qwenNativeIdentity(instance, production, init, kind) {
|
|
2590
|
+
const native = init;
|
|
2591
|
+
const identity = production === true && (kind ?? instance) === "qwen" && native?.agentInfo?.name === "qwen-code";
|
|
2592
|
+
const version = identity ? assessVersion("qwen", native?.agentInfo?.version) : undefined;
|
|
2593
|
+
if (production && !version?.ok)
|
|
2594
|
+
throw new QwenIdentityError(version && !version.ok ? version.reason : undefined);
|
|
2595
|
+
const verified = !!version?.ok;
|
|
2596
|
+
return verified;
|
|
2597
|
+
}
|
|
2598
|
+
function buildQwenLaunch(command, model) {
|
|
2599
|
+
const [cmd, ...args] = command;
|
|
2600
|
+
if (!cmd)
|
|
2601
|
+
throw new Error("qwen_cmd must name an executable");
|
|
2602
|
+
return { cmd, args: [...args, ...model ? ["--model", model] : []] };
|
|
2603
|
+
}
|
|
2604
|
+
var QWEN_MODE_IDS, QwenIdentityError, qwenSessionId = (value) => typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/.test(value);
|
|
2605
|
+
var init_qwen_native = __esm(() => {
|
|
2606
|
+
init_compat();
|
|
2607
|
+
QWEN_MODE_IDS = { ask: "default", "ask-when-needed": "auto-edit", "never-ask": "yolo" };
|
|
2608
|
+
QwenIdentityError = class QwenIdentityError extends Error {
|
|
2609
|
+
constructor(reason) {
|
|
2610
|
+
super(reason ? `Qwen Code native identity is not verified: ${reason}` : "Qwen Code native identity is not verified; check the executable and its advertised ACP version");
|
|
2569
2611
|
}
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2612
|
+
};
|
|
2613
|
+
});
|
|
2614
|
+
|
|
2615
|
+
// src/adapters/acp-owned-group.ts
|
|
2616
|
+
class AcpOwnedGroup {
|
|
2617
|
+
proc;
|
|
2618
|
+
leader;
|
|
2619
|
+
ports;
|
|
2620
|
+
confirmedGone = false;
|
|
2621
|
+
constructor(proc, leader, ports) {
|
|
2622
|
+
this.proc = proc;
|
|
2623
|
+
this.leader = leader;
|
|
2624
|
+
this.ports = ports;
|
|
2625
|
+
}
|
|
2626
|
+
static async capture(proc, ports = PORTS) {
|
|
2627
|
+
const pid = proc.pid;
|
|
2628
|
+
if (!Number.isSafeInteger(pid) || !pid || pid <= 1 || proc.exitCode !== null || proc.signalCode !== null)
|
|
2629
|
+
throw ownershipError();
|
|
2630
|
+
const rows = await AcpOwnedGroup.read(ports);
|
|
2631
|
+
const leader = rows.find((row) => row.pid === pid);
|
|
2632
|
+
if (!leader || leader.pgid !== pid || leader.ppid !== process.pid)
|
|
2633
|
+
throw ownershipError();
|
|
2634
|
+
const signature = AcpOwnedGroup.identity(ports, pid);
|
|
2635
|
+
if (!signature || proc.pid !== pid || proc.exitCode !== null || proc.signalCode !== null)
|
|
2636
|
+
throw ownershipError();
|
|
2637
|
+
return new AcpOwnedGroup(proc, { pid, ppid: leader.ppid, pgid: leader.pgid, started: leader.started }, ports);
|
|
2638
|
+
}
|
|
2639
|
+
static async confirmGone(proc, ports = PORTS) {
|
|
2640
|
+
const pid = proc.pid;
|
|
2641
|
+
if (pid === undefined)
|
|
2642
|
+
return true;
|
|
2643
|
+
if (!Number.isSafeInteger(pid) || pid <= 1 || !(typeof proc.exitCode === "number" || typeof proc.signalCode === "string"))
|
|
2644
|
+
return false;
|
|
2645
|
+
try {
|
|
2646
|
+
const rows = await AcpOwnedGroup.read(ports);
|
|
2647
|
+
return proc.pid === pid && (typeof proc.exitCode === "number" || typeof proc.signalCode === "string") && !rows.some((row) => row.pid === pid || row.pgid === pid);
|
|
2648
|
+
} catch {
|
|
2649
|
+
return false;
|
|
2579
2650
|
}
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
constructor(id, opts) {
|
|
2594
|
-
super(id, opts.watchdogMs);
|
|
2595
|
-
this.opts = opts;
|
|
2596
|
-
this.permissionMode = opts.permissionMode ?? "ask";
|
|
2651
|
+
}
|
|
2652
|
+
async verify() {
|
|
2653
|
+
if (this.confirmedGone || !await this.inspect(false))
|
|
2654
|
+
throw ownershipError();
|
|
2655
|
+
}
|
|
2656
|
+
async stop({ killMs = 2000 } = {}) {
|
|
2657
|
+
if (this.confirmedGone)
|
|
2658
|
+
return;
|
|
2659
|
+
if (!Number.isFinite(killMs) || killMs <= 0 || killMs > 1e4)
|
|
2660
|
+
throw shutdownError();
|
|
2661
|
+
if (!await this.inspect(true)) {
|
|
2662
|
+
this.confirmedGone = true;
|
|
2663
|
+
return;
|
|
2597
2664
|
}
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
};
|
|
2665
|
+
this.signal("SIGSTOP");
|
|
2666
|
+
if (!await this.inspect(true)) {
|
|
2667
|
+
this.confirmedGone = true;
|
|
2668
|
+
return;
|
|
2603
2669
|
}
|
|
2604
|
-
|
|
2605
|
-
|
|
2670
|
+
this.signal("SIGKILL");
|
|
2671
|
+
const end = this.ports.now() + killMs;
|
|
2672
|
+
for (;; ) {
|
|
2673
|
+
const left = end - this.ports.now();
|
|
2674
|
+
if (left <= 0)
|
|
2675
|
+
throw shutdownError();
|
|
2676
|
+
const rows = await AcpOwnedGroup.read(this.ports, Math.min(READ_MS, left));
|
|
2677
|
+
const leader = rows.find((row) => row.pid === this.leader.pid);
|
|
2678
|
+
if (leader && (leader.started !== this.leader.started || leader.pgid !== this.leader.pgid))
|
|
2679
|
+
throw ownershipError();
|
|
2680
|
+
if (!rows.some((row) => row.pgid === this.leader.pgid) && !leader) {
|
|
2681
|
+
this.confirmedGone = true;
|
|
2682
|
+
return;
|
|
2683
|
+
}
|
|
2684
|
+
if (this.ports.now() >= end)
|
|
2685
|
+
throw shutdownError();
|
|
2686
|
+
await this.ports.sleep(Math.min(25, end - this.ports.now()));
|
|
2606
2687
|
}
|
|
2607
|
-
|
|
2608
|
-
|
|
2688
|
+
}
|
|
2689
|
+
async inspect(allowGone) {
|
|
2690
|
+
const rows = await AcpOwnedGroup.read(this.ports);
|
|
2691
|
+
const leader = rows.find((row) => row.pid === this.leader.pid);
|
|
2692
|
+
const members = rows.some((row) => row.pgid === this.leader.pgid);
|
|
2693
|
+
if (!leader && !members && allowGone)
|
|
2694
|
+
return false;
|
|
2695
|
+
if (!leader || !this.sameLeader(leader) || this.proc.pid !== this.leader.pid || this.proc.exitCode !== null || this.proc.signalCode !== null || !AcpOwnedGroup.identity(this.ports, this.leader.pid))
|
|
2696
|
+
throw ownershipError();
|
|
2697
|
+
return true;
|
|
2698
|
+
}
|
|
2699
|
+
sameLeader(row) {
|
|
2700
|
+
return row.pid === this.leader.pid && row.pgid === this.leader.pgid && row.ppid === this.leader.ppid && row.started === this.leader.started;
|
|
2701
|
+
}
|
|
2702
|
+
signal(signal) {
|
|
2703
|
+
try {
|
|
2704
|
+
this.ports.signal(-this.leader.pgid, signal);
|
|
2705
|
+
} catch {
|
|
2706
|
+
throw ownershipError();
|
|
2609
2707
|
}
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2708
|
+
}
|
|
2709
|
+
static identity(ports, pid) {
|
|
2710
|
+
try {
|
|
2711
|
+
return ports.signature(pid);
|
|
2712
|
+
} catch {
|
|
2713
|
+
return;
|
|
2613
2714
|
}
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
tui.close(4000, "codex is off roster; retry when a person adds it");
|
|
2632
|
-
return;
|
|
2633
|
-
}
|
|
2634
|
-
if (this.claimedTui) {
|
|
2635
|
-
tui.close(1013, "codex TUI already attached to this hub");
|
|
2636
|
-
return;
|
|
2637
|
-
}
|
|
2638
|
-
this.attach(tui, upstream);
|
|
2639
|
-
},
|
|
2640
|
-
message: (tui, data) => {
|
|
2641
|
-
if (this.opts.admitAttach?.() === false) {
|
|
2642
|
-
tui.close(4000, "codex is off roster");
|
|
2643
|
-
return;
|
|
2644
|
-
}
|
|
2645
|
-
if (tui.data?.up)
|
|
2646
|
-
this.fromTui(tui.data, String(data));
|
|
2647
|
-
},
|
|
2648
|
-
close: (tui) => {
|
|
2649
|
-
if (tui.data?.up)
|
|
2650
|
-
this.detach(tui.data);
|
|
2651
|
-
}
|
|
2652
|
-
}
|
|
2653
|
-
});
|
|
2654
|
-
} catch (error) {
|
|
2655
|
-
const proc = this.proc;
|
|
2656
|
-
if (proc && await this.stopQuietly(proc) && this.proc === proc)
|
|
2657
|
-
this.proc = undefined;
|
|
2658
|
-
throw error;
|
|
2659
|
-
}
|
|
2660
|
-
}
|
|
2661
|
-
stopQuietly(proc) {
|
|
2662
|
-
return stopOwnedProcess(proc, { group: true }).then(() => true, (error) => {
|
|
2663
|
-
this.opts.log?.(`[${this.id}] ${error.message}`);
|
|
2664
|
-
return false;
|
|
2665
|
-
});
|
|
2666
|
-
}
|
|
2667
|
-
async stop() {
|
|
2668
|
-
this.server?.stop(true);
|
|
2669
|
-
this.claimedTui?.tui.close(1001, "hub shutting down");
|
|
2670
|
-
const proc = this.proc;
|
|
2671
|
-
if (proc) {
|
|
2672
|
-
await stopOwnedProcess(proc, { group: true });
|
|
2673
|
-
if (this.proc === proc)
|
|
2674
|
-
this.proc = undefined;
|
|
2675
|
-
}
|
|
2676
|
-
this.setState("offline");
|
|
2677
|
-
}
|
|
2678
|
-
deliver(envs, deliveryId) {
|
|
2679
|
-
const link = this.link;
|
|
2680
|
-
if (this.state !== "idle" || !link || link.up.readyState !== WebSocket.OPEN) {
|
|
2681
|
-
if (deliveryId)
|
|
2682
|
-
this.delivery({ id: deliveryId, state: "failed_safe", reason: `${this.id} is not injectable` });
|
|
2683
|
-
return Promise.reject(new Error(`${this.id} is not injectable`));
|
|
2684
|
-
}
|
|
2685
|
-
const text = this.render(envs);
|
|
2686
|
-
const id = this.nextId--, epoch = this.deliveryEpoch, session = this.contextEpoch;
|
|
2687
|
-
this.setState("busy");
|
|
2688
|
-
return new Promise((resolve, reject) => {
|
|
2689
|
-
let invalidated = false;
|
|
2690
|
-
const retiredAtCancel = new Set;
|
|
2691
|
-
const invalidate = (reason) => {
|
|
2692
|
-
if (invalidated)
|
|
2693
|
-
return;
|
|
2694
|
-
invalidated = true;
|
|
2695
|
-
for (const turn of [...this.activeTurns, ...this.turnDeliveries.keys()])
|
|
2696
|
-
if (!turn.startsWith("unknown:"))
|
|
2697
|
-
retiredAtCancel.add(turn);
|
|
2698
|
-
envs = [];
|
|
2699
|
-
if (deliveryId)
|
|
2700
|
-
this.delivery({ id: deliveryId, state: "needs_review", reason });
|
|
2701
|
-
reject(new Error(reason));
|
|
2702
|
-
};
|
|
2703
|
-
this.pending.set(id, {
|
|
2704
|
-
deliveryId,
|
|
2705
|
-
kind: "deliver",
|
|
2706
|
-
invalidate,
|
|
2707
|
-
resolve: (result) => {
|
|
2708
|
-
const nativeTurn = typeof result?.turn?.id === "string" ? result.turn.id : undefined;
|
|
2709
|
-
if (invalidated || epoch !== this.deliveryEpoch || session !== this.contextEpoch || link !== this.link) {
|
|
2710
|
-
invalidate("Codex turn acceptance arrived after its delivery was invalidated");
|
|
2711
|
-
if (nativeTurn && session === this.contextEpoch && link === this.link && (!retiredAtCancel.has(nativeTurn) || this.retiredTurns.has(nativeTurn)))
|
|
2712
|
-
this.retireTurn(nativeTurn);
|
|
2713
|
-
return;
|
|
2714
|
-
}
|
|
2715
|
-
this.primed = true;
|
|
2716
|
-
this.injected = replyParent(envs);
|
|
2717
|
-
this.answering = [...envs];
|
|
2718
|
-
if (nativeTurn && !this.turnDeliveries.has(nativeTurn))
|
|
2719
|
-
this.turnDeliveries.set(nativeTurn, new Set);
|
|
2720
|
-
if (!nativeTurn && !this.activeTurns.size)
|
|
2721
|
-
this.unboundAccepted = true;
|
|
2722
|
-
if (deliveryId) {
|
|
2723
|
-
if (nativeTurn)
|
|
2724
|
-
this.addTurnDelivery(nativeTurn, deliveryId);
|
|
2725
|
-
else
|
|
2726
|
-
this.unboundDeliveries.add(deliveryId);
|
|
2727
|
-
this.delivery({ id: deliveryId, state: "accepted" });
|
|
2728
|
-
}
|
|
2729
|
-
resolve();
|
|
2730
|
-
},
|
|
2731
|
-
reject: (e) => {
|
|
2732
|
-
this.restoringApprovals.delete(id);
|
|
2733
|
-
if (invalidated)
|
|
2734
|
-
return;
|
|
2735
|
-
if (epoch !== this.deliveryEpoch || session !== this.contextEpoch || link !== this.link) {
|
|
2736
|
-
invalidate("Codex turn refusal arrived after its delivery was invalidated");
|
|
2737
|
-
return;
|
|
2738
|
-
}
|
|
2739
|
-
if (!this.activeTurns.size)
|
|
2740
|
-
this.setState("idle");
|
|
2741
|
-
if (deliveryId)
|
|
2742
|
-
this.delivery({ id: deliveryId, state: this.knownRejection(e) ? "failed_safe" : "needs_review", reason: e.message });
|
|
2743
|
-
reject(e);
|
|
2744
|
-
}
|
|
2745
|
-
});
|
|
2746
|
-
link.up.send(JSON.stringify({ method: "turn/start", id, params: this.applyPermissionPolicy({ threadId: this.threadId, input: [{ type: "text", text }] }, id) }));
|
|
2747
|
-
});
|
|
2715
|
+
}
|
|
2716
|
+
static async read(ports, ms = READ_MS) {
|
|
2717
|
+
let timer;
|
|
2718
|
+
try {
|
|
2719
|
+
const rows = await Promise.race([
|
|
2720
|
+
Promise.resolve().then(() => ports.table()),
|
|
2721
|
+
new Promise((resolve) => {
|
|
2722
|
+
timer = setTimeout(() => resolve(undefined), ms);
|
|
2723
|
+
})
|
|
2724
|
+
]);
|
|
2725
|
+
if (!rows)
|
|
2726
|
+
throw ownershipError();
|
|
2727
|
+
return rows;
|
|
2728
|
+
} catch {
|
|
2729
|
+
throw ownershipError();
|
|
2730
|
+
} finally {
|
|
2731
|
+
clearTimeout(timer);
|
|
2748
2732
|
}
|
|
2749
|
-
|
|
2750
|
-
|
|
2733
|
+
}
|
|
2734
|
+
}
|
|
2735
|
+
var PORTS, READ_MS = 1000, ownershipError = () => new Error("Configured ACP process ownership could not be verified"), shutdownError = () => new Error("Configured ACP process group shutdown incomplete");
|
|
2736
|
+
var init_acp_owned_group = __esm(() => {
|
|
2737
|
+
init_platform();
|
|
2738
|
+
init_child_process();
|
|
2739
|
+
PORTS = {
|
|
2740
|
+
table: readProcessTable,
|
|
2741
|
+
signature: processSignature,
|
|
2742
|
+
signal: (target, signal) => {
|
|
2743
|
+
process.kill(target, signal);
|
|
2744
|
+
},
|
|
2745
|
+
now: () => performance.now(),
|
|
2746
|
+
sleep: (ms) => Bun.sleep(ms)
|
|
2747
|
+
};
|
|
2748
|
+
});
|
|
2751
2749
|
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
this.
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
this.injected = undefined;
|
|
2847
|
-
this.answering = [];
|
|
2848
|
-
this.lastAnswer = "";
|
|
2849
|
-
this.deltas.clear();
|
|
2850
|
-
this.abandonSteers("turn went silent");
|
|
2851
|
-
if (this.unidentifiedCancellation)
|
|
2852
|
-
this.setState("offline");
|
|
2853
|
-
else
|
|
2854
|
-
super.onWatchdog();
|
|
2855
|
-
}
|
|
2856
|
-
retireTurn(turnId) {
|
|
2857
|
-
this.retiredTurns.add(turnId);
|
|
2858
|
-
if (this.link?.up.readyState === WebSocket.OPEN)
|
|
2859
|
-
this.link.up.send(JSON.stringify({ method: "turn/interrupt", id: this.nextId--, params: { threadId: this.threadId, turnId } }));
|
|
2860
|
-
}
|
|
2861
|
-
abandonSteers(reason) {
|
|
2862
|
-
for (const id of this.steers) {
|
|
2863
|
-
const p = this.pending.get(id);
|
|
2864
|
-
this.pending.delete(id);
|
|
2865
|
-
p?.reject(new Error(`steer unanswered: ${reason}`));
|
|
2866
|
-
}
|
|
2867
|
-
}
|
|
2868
|
-
endSteers(turnId) {
|
|
2869
|
-
for (const id of this.steers) {
|
|
2870
|
-
const pending = this.pending.get(id);
|
|
2871
|
-
if (!pending || pending.expectedTurnId !== turnId)
|
|
2872
|
-
continue;
|
|
2873
|
-
if (pending.deliveryId)
|
|
2874
|
-
pending.afterTurn?.();
|
|
2875
|
-
else {
|
|
2876
|
-
this.pending.delete(id);
|
|
2877
|
-
pending.reject(new Error("steer unanswered: turn completed"));
|
|
2878
|
-
}
|
|
2879
|
-
}
|
|
2880
|
-
}
|
|
2881
|
-
addTurnDelivery(turnId, deliveryId) {
|
|
2882
|
-
const ids = this.turnDeliveries.get(turnId) ?? new Set;
|
|
2883
|
-
ids.add(deliveryId);
|
|
2884
|
-
this.turnDeliveries.set(turnId, ids);
|
|
2885
|
-
this.unboundDeliveries.delete(deliveryId);
|
|
2886
|
-
}
|
|
2887
|
-
knownRejection(error) {
|
|
2888
|
-
return /turn in progress|no active turn|not injectable|rejected|usage limit/i.test(error.message);
|
|
2889
|
-
}
|
|
2890
|
-
async spawnAppServer() {
|
|
2891
|
-
const port = this.opts.appPort;
|
|
2892
|
-
const probe = () => fetch(`http://127.0.0.1:${port}/healthz`).then((r) => r.ok, () => false);
|
|
2893
|
-
if (await probe()) {
|
|
2894
|
-
throw new Error(`port ${port} already answers /healthz: an app-server the hub does not own is running (orphan from a crashed hub?)`);
|
|
2750
|
+
// src/adapters/acp.ts
|
|
2751
|
+
import { spawn as spawn5 } from "child_process";
|
|
2752
|
+
import { createInterface } from "readline";
|
|
2753
|
+
function normalizeACPUsage(value, source = "usage_update") {
|
|
2754
|
+
const unknown = { source, availability: "unsupported", shape: "none" };
|
|
2755
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
2756
|
+
return unknown;
|
|
2757
|
+
const raw = value;
|
|
2758
|
+
const f = raw.usage && typeof raw.usage === "object" && !Array.isArray(raw.usage) ? raw.usage : raw;
|
|
2759
|
+
const valid = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0;
|
|
2760
|
+
for (const key of ["totalTokens", "total_tokens"]) {
|
|
2761
|
+
if (f[key] === undefined)
|
|
2762
|
+
continue;
|
|
2763
|
+
const shape = "total";
|
|
2764
|
+
return valid(f[key]) ? { source, availability: "known", shape, total: f[key] } : { source, availability: "invalid", shape };
|
|
2765
|
+
}
|
|
2766
|
+
for (const [input, output] of [["inputTokens", "outputTokens"], ["input_tokens", "output_tokens"]]) {
|
|
2767
|
+
if (f[input] === undefined && f[output] === undefined)
|
|
2768
|
+
continue;
|
|
2769
|
+
const a = f[input], b = f[output];
|
|
2770
|
+
return valid(a) && valid(b) && Number.isFinite(a + b) ? { source, availability: "known", shape: "input-output", total: a + b } : { source, availability: "invalid", shape: "input-output" };
|
|
2771
|
+
}
|
|
2772
|
+
if (f.used !== undefined) {
|
|
2773
|
+
if (!valid(f.used) || !valid(f.size) || !Number.isSafeInteger(f.used) || !Number.isSafeInteger(f.size) || f.size <= 0)
|
|
2774
|
+
return { source, availability: "invalid", shape: "context-used" };
|
|
2775
|
+
return { source, availability: "unsupported", shape: "context-used", contextUsed: f.used, contextCapacity: f.size };
|
|
2776
|
+
}
|
|
2777
|
+
return unknown;
|
|
2778
|
+
}
|
|
2779
|
+
function canonicalMcpToolName(announcedTitle, serverNames) {
|
|
2780
|
+
const m = announcedTitle?.match(/^([\w-]{1,80}) \((.+) MCP Server\)$/);
|
|
2781
|
+
return m && serverNames?.some((name) => name === m[2]) ? `mcp__${m[2]}__${m[1]}` : undefined;
|
|
2782
|
+
}
|
|
2783
|
+
function jsonObject(text) {
|
|
2784
|
+
try {
|
|
2785
|
+
const v = text === undefined ? undefined : JSON.parse(text);
|
|
2786
|
+
return v !== null && typeof v === "object" && !Array.isArray(v) ? v : undefined;
|
|
2787
|
+
} catch {
|
|
2788
|
+
return;
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
var ACTION_BUDGET_OPTION = "agent-hub:action-budget", HANDSHAKE_MS = 30000, TOOL_INPUT_CAP = 64, TOOL_CEILING_MS, RpcTimeoutError, AcpPeer;
|
|
2792
|
+
var init_acp = __esm(() => {
|
|
2793
|
+
init_envelope();
|
|
2794
|
+
init_kimi_native();
|
|
2795
|
+
init_qwen_native();
|
|
2796
|
+
init_qwen_native();
|
|
2797
|
+
init_child_process();
|
|
2798
|
+
init_acp_owned_group();
|
|
2799
|
+
TOOL_CEILING_MS = 30 * 60000;
|
|
2800
|
+
RpcTimeoutError = class RpcTimeoutError extends Error {
|
|
2801
|
+
};
|
|
2802
|
+
AcpPeer = class AcpPeer extends BasePeer {
|
|
2803
|
+
opts;
|
|
2804
|
+
permissionMode;
|
|
2805
|
+
nativePolicy;
|
|
2806
|
+
modeUnknown = false;
|
|
2807
|
+
kimiAgent = false;
|
|
2808
|
+
qwenAgent = false;
|
|
2809
|
+
availableModes = new Set;
|
|
2810
|
+
proc;
|
|
2811
|
+
ownedGroup;
|
|
2812
|
+
sessionId = "";
|
|
2813
|
+
nextId = 1;
|
|
2814
|
+
pending = new Map;
|
|
2815
|
+
chunks = [];
|
|
2816
|
+
toolInputs = new Map;
|
|
2817
|
+
toolText = new Map;
|
|
2818
|
+
toolTitles = new Map;
|
|
2819
|
+
openTools = new Map;
|
|
2820
|
+
lastSignAt = 0;
|
|
2821
|
+
primed = false;
|
|
2822
|
+
turn = 0;
|
|
2823
|
+
stopping = false;
|
|
2824
|
+
activeDeliveryId;
|
|
2825
|
+
activeLegacy;
|
|
2826
|
+
deliveryAccepted = false;
|
|
2827
|
+
turnEnvs = [];
|
|
2828
|
+
steerIds = [];
|
|
2829
|
+
cancelWait;
|
|
2830
|
+
promptActivity = false;
|
|
2831
|
+
activityOpen = false;
|
|
2832
|
+
activityPrivate = false;
|
|
2833
|
+
activityStub = false;
|
|
2834
|
+
activityStarted = 0;
|
|
2835
|
+
activityPending;
|
|
2836
|
+
activityTimer;
|
|
2837
|
+
emitActivity(kind, text) {
|
|
2838
|
+
if (!this.opts.onActivity || !this.activityOpen)
|
|
2839
|
+
return;
|
|
2840
|
+
try {
|
|
2841
|
+
this.activityPrivate ||= this.opts.isPrivateTurn?.() === true;
|
|
2842
|
+
} catch {
|
|
2843
|
+
this.activityPrivate = true;
|
|
2895
2844
|
}
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
detached: true
|
|
2903
|
-
});
|
|
2904
|
-
trackGroup(this.proc);
|
|
2905
|
-
this.proc.stderr?.on("data", (d) => this.opts.log?.(`[${this.id}] ${String(d).trimEnd()}`));
|
|
2906
|
-
this.proc.on("error", (e) => gone = `cannot run ${this.opts.bin ?? "codex"}: ${e.message}`);
|
|
2907
|
-
this.proc.on("exit", (code) => {
|
|
2908
|
-
gone ||= `codex app-server exited with code ${code}`;
|
|
2909
|
-
this.opts.log?.(`[${this.id}] ${gone}`);
|
|
2910
|
-
this.setState("offline");
|
|
2911
|
-
});
|
|
2912
|
-
for (let i = 0;i < 100 && !gone; i++) {
|
|
2913
|
-
if (await probe())
|
|
2914
|
-
return `ws://127.0.0.1:${port}`;
|
|
2915
|
-
await Bun.sleep(100);
|
|
2845
|
+
if (this.activityPrivate) {
|
|
2846
|
+
if (this.activityStub)
|
|
2847
|
+
return;
|
|
2848
|
+
this.activityStub = true;
|
|
2849
|
+
kind = "private";
|
|
2850
|
+
text = "Private turn: activity withheld";
|
|
2916
2851
|
}
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
if (proc && await this.stopQuietly(proc) && this.proc === proc)
|
|
2921
|
-
this.proc = undefined;
|
|
2922
|
-
throw new Error("codex app-server did not become healthy within 10 s");
|
|
2852
|
+
try {
|
|
2853
|
+
this.opts.onActivity({ kind, text: activityText(text), at: Date.now(), turn: this.turn });
|
|
2854
|
+
} catch {}
|
|
2923
2855
|
}
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
up.send(frame);
|
|
2932
|
-
};
|
|
2933
|
-
up.onmessage = (ev) => this.fromAppServer(link, String(ev.data));
|
|
2934
|
-
up.onclose = () => tui.close();
|
|
2856
|
+
flushActivity() {
|
|
2857
|
+
clearTimeout(this.activityTimer);
|
|
2858
|
+
this.activityTimer = undefined;
|
|
2859
|
+
const pending = this.activityPending;
|
|
2860
|
+
this.activityPending = undefined;
|
|
2861
|
+
if (pending)
|
|
2862
|
+
this.emitActivity(pending.kind, pending.text);
|
|
2935
2863
|
}
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
if (this.claimedTui === link)
|
|
2939
|
-
this.claimedTui = undefined;
|
|
2940
|
-
if (this.link !== link)
|
|
2864
|
+
updateWatch(u) {
|
|
2865
|
+
if (!this.opts.onActivity)
|
|
2941
2866
|
return;
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
this.activeTurns.clear();
|
|
2951
|
-
this.unboundAccepted = false;
|
|
2952
|
-
for (const p of this.pending.values()) {
|
|
2953
|
-
if (p.kind === "deliver")
|
|
2954
|
-
p.invalidate?.("Codex TUI detached");
|
|
2867
|
+
const item = updateActivity(u);
|
|
2868
|
+
if (!item)
|
|
2869
|
+
return;
|
|
2870
|
+
if (item.kind === "message" || item.kind === "thought") {
|
|
2871
|
+
if (this.activityPending?.kind !== item.kind || this.activityPending.text.length + item.text.length > 4096)
|
|
2872
|
+
this.flushActivity();
|
|
2873
|
+
if (this.activityPending)
|
|
2874
|
+
this.activityPending.text += item.text;
|
|
2955
2875
|
else
|
|
2956
|
-
|
|
2876
|
+
this.activityPending = { kind: item.kind, text: item.text };
|
|
2877
|
+
this.activityTimer ??= setTimeout(() => this.flushActivity(), 50);
|
|
2878
|
+
this.activityTimer.unref?.();
|
|
2879
|
+
} else {
|
|
2880
|
+
this.flushActivity();
|
|
2881
|
+
this.emitActivity(item.kind, item.text);
|
|
2957
2882
|
}
|
|
2958
|
-
this.pending.clear();
|
|
2959
|
-
for (const ids of this.turnDeliveries.values())
|
|
2960
|
-
for (const id of ids)
|
|
2961
|
-
this.delivery({ id, state: "needs_review", reason: "Codex TUI detached" });
|
|
2962
|
-
for (const id of this.unboundDeliveries)
|
|
2963
|
-
this.delivery({ id, state: "needs_review", reason: "Codex TUI detached" });
|
|
2964
|
-
this.turnDeliveries.clear();
|
|
2965
|
-
this.unboundDeliveries.clear();
|
|
2966
|
-
this.forgetTurn();
|
|
2967
|
-
this.setState("offline");
|
|
2968
2883
|
}
|
|
2969
|
-
|
|
2970
|
-
this.
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
return;
|
|
2974
|
-
link.tui.close(4000, "codex is off roster; retry when a person adds it");
|
|
2975
|
-
this.detach(link);
|
|
2884
|
+
clearActivity() {
|
|
2885
|
+
clearTimeout(this.activityTimer);
|
|
2886
|
+
this.activityTimer = undefined;
|
|
2887
|
+
this.activityPending = undefined;
|
|
2976
2888
|
}
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2889
|
+
endActivity(reason, discardPending = false) {
|
|
2890
|
+
if (discardPending)
|
|
2891
|
+
this.clearActivity();
|
|
2892
|
+
else
|
|
2893
|
+
this.flushActivity();
|
|
2894
|
+
this.emitActivity("turn_end", `${reason}; ${Date.now() - this.activityStarted} ms`);
|
|
2895
|
+
this.activityOpen = false;
|
|
2981
2896
|
}
|
|
2982
|
-
|
|
2983
|
-
|
|
2897
|
+
permissionWaits = new Set;
|
|
2898
|
+
setState(next) {
|
|
2899
|
+
if (next === "offline") {
|
|
2900
|
+
this.clearActivity();
|
|
2901
|
+
this.cancelPermissions();
|
|
2902
|
+
}
|
|
2903
|
+
super.setState(next);
|
|
2904
|
+
}
|
|
2905
|
+
cancelPermissions() {
|
|
2906
|
+
for (const wait of this.permissionWaits)
|
|
2907
|
+
wait.abort();
|
|
2908
|
+
this.permissionWaits.clear();
|
|
2909
|
+
}
|
|
2910
|
+
constructor(id, opts) {
|
|
2911
|
+
super(id, opts.watchdogMs);
|
|
2912
|
+
this.opts = opts;
|
|
2913
|
+
this.nativePolicy = opts.nativePolicy === undefined ? undefined : { ...opts.nativePolicy, ...opts.nativePolicy.modes === undefined ? {} : { modes: { ...opts.nativePolicy.modes } } };
|
|
2914
|
+
this.permissionMode = this.nativePolicy?.lockedReadOnly ? "ask" : opts.permissionMode ?? "ask";
|
|
2915
|
+
}
|
|
2916
|
+
recoveryMetadata() {
|
|
2917
|
+
return { launch: { kind: "acp", ...this.opts.launchModel ? { model: this.opts.launchModel } : {} }, ...this.sessionId ? { sessionId: this.sessionId } : {} };
|
|
2918
|
+
}
|
|
2919
|
+
async start() {
|
|
2920
|
+
if (this.opts.requireOwnedGroup && this.proc)
|
|
2921
|
+
throw new Error("Configured ACP still has a process; stop it before starting another");
|
|
2922
|
+
this.stopping = false;
|
|
2923
|
+
const [bin, ...args] = this.opts.cmd;
|
|
2924
|
+
const proc = spawn5(bin, args, { cwd: this.opts.cwd, env: peerChildEnv(this.id, { ...process.env, ...this.opts.env ?? {} }), stdio: ["pipe", "pipe", "pipe"], detached: true });
|
|
2925
|
+
this.proc = proc;
|
|
2926
|
+
trackGroup(proc);
|
|
2927
|
+
proc.on("error", (e) => {
|
|
2928
|
+
if (!this.opts.requireOwnedGroup || this.proc === proc)
|
|
2929
|
+
this.down(`spawn failed: ${e.message}`);
|
|
2930
|
+
});
|
|
2931
|
+
proc.on("exit", (code) => {
|
|
2932
|
+
if (!this.opts.requireOwnedGroup || this.proc === proc)
|
|
2933
|
+
this.down(`exited with code ${code}`);
|
|
2934
|
+
});
|
|
2935
|
+
proc.stdin.on("error", () => {});
|
|
2936
|
+
proc.stderr.on("data", (d) => this.opts.log?.(this.nativePolicy !== undefined || this.opts.requireOwnedGroup ? "Configured ACP stderr output withheld" : `[${this.id}] ${String(d).trimEnd()}`));
|
|
2937
|
+
createInterface({ input: proc.stdout }).on("line", (line) => this.onLine(line));
|
|
2938
|
+
const handshake = async () => {
|
|
2939
|
+
const init = await this.request("initialize", {
|
|
2940
|
+
protocolVersion: 1,
|
|
2941
|
+
clientCapabilities: { fs: { readTextFile: false, writeTextFile: false }, terminal: false }
|
|
2942
|
+
});
|
|
2943
|
+
this.kimiAgent = this.nativePolicy === undefined && init?.agentInfo?.name === "Kimi Code CLI";
|
|
2944
|
+
const expected = this.opts.expectedAgent, version = init?.agentInfo?.version;
|
|
2945
|
+
if (expected && (init?.agentInfo?.name !== expected.name || !(typeof expected.version === "function" ? expected.version(version) : version === expected.version)))
|
|
2946
|
+
throw new Error("native ACP identity is not verified for this production adapter");
|
|
2947
|
+
const qwen = qwenNativeIdentity(this.id, this.opts.qwen025, init, this.opts.kind);
|
|
2948
|
+
this.qwenAgent = this.nativePolicy === undefined && qwen;
|
|
2949
|
+
const resume = this.opts.resumeSessionId;
|
|
2950
|
+
if (!resume)
|
|
2951
|
+
return this.request("session/new", { cwd: this.opts.cwd, mcpServers: this.opts.mcpServers ?? [] });
|
|
2952
|
+
if (!init?.agentCapabilities?.loadSession)
|
|
2953
|
+
throw new Error(`${this.id} cannot load an earlier session (the agent offers no loadSession)`);
|
|
2954
|
+
const loaded = await this.request("session/load", { sessionId: resume, cwd: this.opts.cwd, mcpServers: this.opts.mcpServers ?? [] });
|
|
2955
|
+
if (loaded?.sessionId !== undefined && loaded.sessionId !== resume || this.opts.requireLoadSessionEcho && loaded?.sessionId !== resume)
|
|
2956
|
+
throw new Error("native ACP load did not confirm the requested conversation; no restored session is claimed");
|
|
2957
|
+
return { ...loaded, sessionId: resume };
|
|
2958
|
+
};
|
|
2959
|
+
try {
|
|
2960
|
+
if (this.opts.requireOwnedGroup) {
|
|
2961
|
+
const proof = await AcpOwnedGroup.capture(proc);
|
|
2962
|
+
this.ownedGroup = { proc, proof };
|
|
2963
|
+
if (this.proc !== proc || this.stopping)
|
|
2964
|
+
throw new Error("Configured ACP process ownership could not be verified");
|
|
2965
|
+
}
|
|
2966
|
+
const timeout = new Promise((_, reject) => {
|
|
2967
|
+
setTimeout(() => reject(new Error(`${this.id} did not complete the ACP handshake within ${HANDSHAKE_MS / 1000} s`)), HANDSHAKE_MS).unref();
|
|
2968
|
+
});
|
|
2969
|
+
const session = await Promise.race([handshake(), timeout]);
|
|
2970
|
+
if (this.nativePolicy !== undefined && (typeof session?.sessionId !== "string" || !session.sessionId.trim() || Buffer.byteLength(session.sessionId) > 4096 || /[\p{Cc}\p{Cf}]/u.test(session.sessionId))) {
|
|
2971
|
+
throw new Error("Configured ACP did not provide a valid native session identity");
|
|
2972
|
+
}
|
|
2973
|
+
this.sessionId = session.sessionId;
|
|
2974
|
+
const modes = session.modes?.availableModes;
|
|
2975
|
+
this.availableModes = new Set(Array.isArray(modes) ? modes.flatMap((mode) => typeof mode?.id === "string" ? [mode.id] : []) : []);
|
|
2976
|
+
const resetResumed = this.opts.resumeSessionId && typeof session.modes?.currentModeId === "string" && session.modes.currentModeId !== KIMI_MODE_IDS.ask;
|
|
2977
|
+
if (this.nativePolicy?.lockedReadOnly)
|
|
2978
|
+
await this.applyNativeMode(this.nativePolicy.readOnly, "ask");
|
|
2979
|
+
else if (this.nativePolicy?.modes !== undefined || this.qwenAgent || this.kimiAgent && (this.permissionMode !== "ask" || resetResumed))
|
|
2980
|
+
await this.setPermissionMode(this.permissionMode);
|
|
2981
|
+
if (this.opts.requireOwnedGroup) {
|
|
2982
|
+
const owned = this.ownedGroup;
|
|
2983
|
+
if (!owned || owned.proc !== proc || this.proc !== proc || this.stopping)
|
|
2984
|
+
throw new Error("Configured ACP process ownership could not be verified");
|
|
2985
|
+
await owned.proof.verify();
|
|
2986
|
+
if (this.proc !== proc || this.stopping)
|
|
2987
|
+
throw new Error("Configured ACP process ownership could not be verified");
|
|
2988
|
+
}
|
|
2989
|
+
} catch (e) {
|
|
2990
|
+
this.setState("offline");
|
|
2991
|
+
if (this.proc === proc) {
|
|
2992
|
+
if (this.opts.requireOwnedGroup) {
|
|
2993
|
+
const owned = this.ownedGroup;
|
|
2994
|
+
if (owned?.proc === proc) {
|
|
2995
|
+
await owned.proof.stop().then(() => {
|
|
2996
|
+
if (this.proc === proc) {
|
|
2997
|
+
this.proc = undefined;
|
|
2998
|
+
this.ownedGroup = undefined;
|
|
2999
|
+
}
|
|
3000
|
+
}, () => this.opts.log?.("Configured ACP process group shutdown incomplete"));
|
|
3001
|
+
} else {
|
|
3002
|
+
proc.stdin.end();
|
|
3003
|
+
if (await AcpOwnedGroup.confirmGone(proc) && this.proc === proc) {
|
|
3004
|
+
this.proc = undefined;
|
|
3005
|
+
this.ownedGroup = undefined;
|
|
3006
|
+
} else
|
|
3007
|
+
this.opts.log?.("Configured ACP process ownership could not be verified; no process signals were sent");
|
|
3008
|
+
}
|
|
3009
|
+
} else
|
|
3010
|
+
await stopOwnedProcess(proc, { group: true }).then(() => {
|
|
3011
|
+
if (this.proc === proc)
|
|
3012
|
+
this.proc = undefined;
|
|
3013
|
+
}, (stop) => this.opts.log?.(`[${this.id}] ${stop.message}`));
|
|
3014
|
+
}
|
|
3015
|
+
throw e;
|
|
3016
|
+
}
|
|
3017
|
+
this.setState("idle");
|
|
2984
3018
|
}
|
|
2985
3019
|
getPermissionMode() {
|
|
2986
3020
|
return this.permissionMode;
|
|
2987
3021
|
}
|
|
2988
|
-
get
|
|
2989
|
-
return this.
|
|
3022
|
+
get actionGated() {
|
|
3023
|
+
return this.opts.actionGate !== undefined;
|
|
2990
3024
|
}
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
throw new Error("codex startup mode reconciliation requires a fresh unattached proxy");
|
|
2994
|
-
this.permissionMode = mode;
|
|
3025
|
+
get pendingOwnedProcess() {
|
|
3026
|
+
return this.opts.requireOwnedGroup === true && this.proc !== undefined;
|
|
2995
3027
|
}
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
this.permissionMode = "ask";
|
|
2999
|
-
return restorable;
|
|
3028
|
+
get permissionModeState() {
|
|
3029
|
+
return this.nativePolicy?.lockedReadOnly || this.nativePolicy?.modes !== undefined || this.kimiAgent || this.qwenAgent ? this.modeUnknown ? "unknown" : this.permissionMode : "unmanaged";
|
|
3000
3030
|
}
|
|
3001
3031
|
async setPermissionMode(mode) {
|
|
3002
|
-
if (
|
|
3003
|
-
throw new Error(
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
applyPermissionPolicy(params, id) {
|
|
3009
|
-
const thread = typeof params.threadId === "string" ? params.threadId : this.threadId;
|
|
3010
|
-
if (this.permissionMode !== "ask") {
|
|
3011
|
-
this.overriddenApprovals.add(thread);
|
|
3012
|
-
return { ...params, approvalPolicy: this.permissionMode === "never-ask" ? "never" : "on-request" };
|
|
3013
|
-
}
|
|
3014
|
-
if (this.overriddenApprovals.has(thread) && this.nativeApprovalPolicies.has(thread)) {
|
|
3015
|
-
this.restoringApprovals.set(id, thread);
|
|
3016
|
-
return { ...params, approvalPolicy: this.nativeApprovalPolicies.get(thread) };
|
|
3017
|
-
}
|
|
3018
|
-
return params;
|
|
3019
|
-
}
|
|
3020
|
-
settlePermissionRestore(id, failed) {
|
|
3021
|
-
const thread = this.restoringApprovals.get(id);
|
|
3022
|
-
this.restoringApprovals.delete(id);
|
|
3023
|
-
if (thread !== undefined && !failed && this.permissionMode === "ask")
|
|
3024
|
-
this.overriddenApprovals.delete(thread);
|
|
3032
|
+
if (this.nativePolicy?.lockedReadOnly)
|
|
3033
|
+
throw new Error(`${this.id} permission mode is locked read-only; runtime changes are refused`);
|
|
3034
|
+
const modes = this.nativePolicy !== undefined ? this.nativePolicy.modes : this.qwenAgent ? QWEN_MODE_IDS : this.kimiAgent ? KIMI_MODE_IDS : undefined;
|
|
3035
|
+
if (modes === undefined)
|
|
3036
|
+
throw new Error(`${this.id} permission mode ${mode} unmanaged: this ACP agent has no verified mode mapping; use Kimi Code CLI or add an agent-specific mapping`);
|
|
3037
|
+
await this.applyNativeMode(modes[mode], mode);
|
|
3025
3038
|
}
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3039
|
+
async applyNativeMode(id, mode) {
|
|
3040
|
+
if (this.modeUnknown)
|
|
3041
|
+
throw new Error(`${this.id} permission mode unknown after an unanswered change; restart the peer before changing modes`);
|
|
3042
|
+
if (typeof id !== "string" || !id || !this.sessionId || !this.proc || !this.availableModes.has(id))
|
|
3043
|
+
throw new Error(`${this.id} permission mode ${mode} unavailable: session does not offer ${id}`);
|
|
3044
|
+
try {
|
|
3045
|
+
await this.request("session/set_mode", { sessionId: this.sessionId, modeId: id }, this.opts.permissionModeTimeoutMs ?? HANDSHAKE_MS);
|
|
3046
|
+
} catch (error) {
|
|
3047
|
+
if (error instanceof RpcTimeoutError) {
|
|
3048
|
+
this.modeUnknown = true;
|
|
3049
|
+
this.turn++;
|
|
3050
|
+
this.setState("offline");
|
|
3051
|
+
await this.stop().catch((stop) => this.opts.log?.(`[${this.id}] ${stop.message}`));
|
|
3052
|
+
throw new Error(this.nativePolicy !== undefined || this.opts.requireOwnedGroup ? "Configured ACP native permission mode unknown; peer is offline, restart it before sending work" : `${this.id} permission mode ${mode} unknown: ${error.message}; peer is offline, restart it before sending work`);
|
|
3036
3053
|
}
|
|
3054
|
+
throw new Error(this.nativePolicy !== undefined || this.opts.requireOwnedGroup ? "Configured ACP native permission mode refused" : `${this.id} permission mode ${mode} refused: ${error.message}`);
|
|
3037
3055
|
}
|
|
3038
|
-
if (
|
|
3039
|
-
|
|
3040
|
-
if (
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
link.backlog.push(raw);
|
|
3056
|
+
if (this.cancelWait?.reported)
|
|
3057
|
+
await this.cancelWait.released;
|
|
3058
|
+
if (this.modeUnknown)
|
|
3059
|
+
throw new Error(`${this.id} permission mode unknown; restart the peer`);
|
|
3060
|
+
this.permissionMode = mode;
|
|
3044
3061
|
}
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
this.
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3062
|
+
async stop(reason) {
|
|
3063
|
+
if (this.activityOpen)
|
|
3064
|
+
this.endActivity("stopped", true);
|
|
3065
|
+
this.stopping = true;
|
|
3066
|
+
this.cancelPermissions();
|
|
3067
|
+
this.clearCancelWait();
|
|
3068
|
+
const legacy = this.activeLegacy;
|
|
3069
|
+
this.activeLegacy = undefined;
|
|
3070
|
+
if (legacy?.turn === this.turn)
|
|
3071
|
+
this.onFailed?.(legacy.envs);
|
|
3072
|
+
if (reason !== undefined)
|
|
3073
|
+
this.turn++;
|
|
3074
|
+
if (this.activeDeliveryId)
|
|
3075
|
+
this.delivery({ id: this.activeDeliveryId, state: "needs_review", reason: reason ?? "ACP session stopped before settlement" });
|
|
3076
|
+
this.settleSteers("needs_review", reason ?? "ACP session stopped before settlement");
|
|
3077
|
+
this.activeDeliveryId = undefined;
|
|
3078
|
+
const proc = this.proc;
|
|
3079
|
+
if (!proc) {
|
|
3080
|
+
this.setState("offline");
|
|
3061
3081
|
return;
|
|
3062
3082
|
}
|
|
3063
|
-
if (
|
|
3064
|
-
this.
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
}
|
|
3073
|
-
} else if (msg.method)
|
|
3074
|
-
this.onNotification(link, msg.method, msg.params ?? {});
|
|
3075
|
-
link.tui.send(raw);
|
|
3076
|
-
}
|
|
3077
|
-
adopt(link, threadId, fresh = false) {
|
|
3078
|
-
if (typeof threadId !== "string" || !threadId || link !== this.claimedTui || link.tui.readyState !== WebSocket.OPEN || link.up.readyState !== WebSocket.OPEN)
|
|
3079
|
-
return;
|
|
3080
|
-
this.abandonSteers("thread changed");
|
|
3081
|
-
for (const [id, pending] of this.pending)
|
|
3082
|
-
if (pending.kind === "deliver") {
|
|
3083
|
-
pending.invalidate?.("Codex native thread changed before acceptance was confirmed");
|
|
3084
|
-
this.pending.delete(id);
|
|
3083
|
+
if (this.opts.requireOwnedGroup) {
|
|
3084
|
+
this.setState("offline");
|
|
3085
|
+
const owned = this.ownedGroup;
|
|
3086
|
+
if (!owned) {
|
|
3087
|
+
if (await AcpOwnedGroup.confirmGone(proc) && this.proc === proc) {
|
|
3088
|
+
this.proc = undefined;
|
|
3089
|
+
return;
|
|
3090
|
+
}
|
|
3091
|
+
throw new Error("Configured ACP process ownership could not be verified");
|
|
3085
3092
|
}
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
this.
|
|
3094
|
-
|
|
3095
|
-
this.
|
|
3096
|
-
this.link = link;
|
|
3097
|
-
this.threadId = threadId;
|
|
3098
|
-
this.contextEpoch++;
|
|
3099
|
-
this.deliveryEpoch++;
|
|
3100
|
-
this.retiredTurns.clear();
|
|
3101
|
-
this.unidentifiedCancellation = false;
|
|
3102
|
-
this.activeTurns.clear();
|
|
3103
|
-
this.unboundAccepted = false;
|
|
3104
|
-
this.forgetTurn();
|
|
3105
|
-
this.opts.log?.(`[${this.id}] thread ${threadId}`);
|
|
3106
|
-
this.opts.onThread?.(threadId, fresh);
|
|
3107
|
-
this.setState("idle");
|
|
3108
|
-
this.readUsage();
|
|
3109
|
-
clearInterval(this.usageTimer);
|
|
3110
|
-
this.usageTimer = setInterval(() => this.readUsage(), this.opts.usagePollMs ?? 600000);
|
|
3111
|
-
this.usageTimer.unref?.();
|
|
3112
|
-
}
|
|
3113
|
-
revert(turnId) {
|
|
3114
|
-
const link = this.link;
|
|
3115
|
-
if (this.state !== "idle" || !link || link.up.readyState !== WebSocket.OPEN)
|
|
3116
|
-
return Promise.reject(new Error(`${this.id} is not idle`));
|
|
3117
|
-
const id = this.nextId--;
|
|
3118
|
-
return new Promise((resolve, reject) => {
|
|
3119
|
-
const timer = setTimeout(() => this.pending.delete(id) && reject(new Error("thread/revert got no answer")), 30000);
|
|
3120
|
-
timer.unref?.();
|
|
3121
|
-
this.pending.set(id, { resolve: () => (clearTimeout(timer), resolve()), reject: (e) => (clearTimeout(timer), reject(e)) });
|
|
3122
|
-
link.up.send(JSON.stringify({ method: "thread/revert", id, params: { threadId: this.threadId, beforeTurnId: turnId } }));
|
|
3123
|
-
});
|
|
3124
|
-
}
|
|
3125
|
-
readUsage() {
|
|
3126
|
-
const link = this.link;
|
|
3127
|
-
if (!this.opts.onUsage || !link || link.up.readyState !== WebSocket.OPEN)
|
|
3128
|
-
return;
|
|
3129
|
-
const id = this.nextId--;
|
|
3130
|
-
this.pending.set(id, { resolve: (result) => this.opts.onUsage?.(result?.rateLimits, false), reject: () => {} });
|
|
3131
|
-
link.up.send(JSON.stringify({ method: "account/rateLimits/read", id }));
|
|
3132
|
-
setTimeout(() => this.pending.delete(id), 30000).unref?.();
|
|
3093
|
+
if (owned.proc !== proc)
|
|
3094
|
+
throw new Error("Configured ACP process ownership could not be verified");
|
|
3095
|
+
await owned.proof.stop();
|
|
3096
|
+
if (this.ownedGroup === owned)
|
|
3097
|
+
this.ownedGroup = undefined;
|
|
3098
|
+
} else
|
|
3099
|
+
await stopOwnedProcess(proc, { group: true });
|
|
3100
|
+
if (this.proc === proc)
|
|
3101
|
+
this.proc = undefined;
|
|
3102
|
+
this.setState("offline");
|
|
3133
3103
|
}
|
|
3134
|
-
|
|
3135
|
-
if (
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
this.
|
|
3139
|
-
}
|
|
3140
|
-
if (link !== this.link || params.threadId !== this.threadId)
|
|
3141
|
-
return;
|
|
3142
|
-
const nativeId = method === "turn/started" || method === "turn/completed" ? params.turn?.id : params.turnId;
|
|
3143
|
-
if (typeof nativeId === "string" && this.retiredTurns.has(nativeId)) {
|
|
3144
|
-
if (method === "turn/completed")
|
|
3145
|
-
this.retiredTurns.delete(nativeId);
|
|
3146
|
-
return;
|
|
3104
|
+
async deliver(envs, deliveryId) {
|
|
3105
|
+
if (this.state !== "idle" || this.stopping) {
|
|
3106
|
+
if (deliveryId)
|
|
3107
|
+
this.delivery({ id: deliveryId, state: "failed_safe", reason: `${this.id} is ${this.state}` });
|
|
3108
|
+
throw new Error(`${this.id} is ${this.state}`);
|
|
3147
3109
|
}
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
};
|
|
3164
|
-
const added = this.tokenTotal === undefined ? this.freshThread ? total : 0 : Math.max(0, total - this.tokenTotal);
|
|
3165
|
-
const base = previous ?? (this.freshThread && this.tokenTotal === undefined ? { input: 0, cachedInput: 0, output: 0, reasoning: 0 } : undefined);
|
|
3166
|
-
this.tokenTotal = total;
|
|
3167
|
-
this.tokenKinds = kinds;
|
|
3168
|
-
if (added > 0)
|
|
3169
|
-
this.opts.onTokens?.(added);
|
|
3170
|
-
if (added > 0 && base)
|
|
3171
|
-
this.opts.onTokenBreakdown?.({
|
|
3172
|
-
input: Math.max(0, kinds.input - base.input),
|
|
3173
|
-
cachedInput: Math.max(0, kinds.cachedInput - base.cachedInput),
|
|
3174
|
-
output: Math.max(0, kinds.output - base.output),
|
|
3175
|
-
reasoning: Math.max(0, kinds.reasoning - base.reasoning),
|
|
3176
|
-
total: added
|
|
3177
|
-
});
|
|
3178
|
-
return;
|
|
3110
|
+
const turn = ++this.turn;
|
|
3111
|
+
this.activeDeliveryId = deliveryId;
|
|
3112
|
+
this.activeLegacy = deliveryId ? undefined : { turn, envs };
|
|
3113
|
+
this.turnEnvs = envs;
|
|
3114
|
+
this.steerIds = [];
|
|
3115
|
+
this.deliveryAccepted = false;
|
|
3116
|
+
this.promptActivity = false;
|
|
3117
|
+
this.chunks = [];
|
|
3118
|
+
this.openTools.clear();
|
|
3119
|
+
this.activityOpen = true;
|
|
3120
|
+
this.activityPrivate = envs.some((env) => env.private === true);
|
|
3121
|
+
try {
|
|
3122
|
+
this.activityPrivate ||= this.opts.isPrivateTurn?.() === true;
|
|
3123
|
+
} catch {
|
|
3124
|
+
this.activityPrivate = true;
|
|
3179
3125
|
}
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
if (
|
|
3189
|
-
this.
|
|
3190
|
-
}
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
buf.push(params.delta);
|
|
3195
|
-
this.deltas.set(params.itemId, buf);
|
|
3196
|
-
} else if (method === "item/completed" && ["fileChange", "commandExecution", "mcpToolCall", "userMessage"].includes(params.item?.type)) {
|
|
3197
|
-
if (typeof params.turnId === "string" && !this.activeTurns.has(params.turnId) && !unknownTurn())
|
|
3126
|
+
this.activityStub = false;
|
|
3127
|
+
this.activityStarted = Date.now();
|
|
3128
|
+
this.setState("busy");
|
|
3129
|
+
this.emitActivity("delivery", envs.map((env) => `${env.from} ${env.id}`).join(", "));
|
|
3130
|
+
const prompt = this.request("session/prompt", { sessionId: this.sessionId, prompt: [{ type: "text", text: this.primed || !this.opts.preamble ? renderDigest(envs, this.primed) : `${this.opts.preamble}
|
|
3131
|
+
|
|
3132
|
+
${renderDigest(envs, false)}` }] });
|
|
3133
|
+
const reported = () => {
|
|
3134
|
+
if (this.cancelWait?.turn === turn)
|
|
3135
|
+
this.cancelWait.reported = true;
|
|
3136
|
+
};
|
|
3137
|
+
prompt.then(reported, reported);
|
|
3138
|
+
prompt.then((result) => {
|
|
3139
|
+
if (turn !== this.turn)
|
|
3198
3140
|
return;
|
|
3199
|
-
|
|
3200
|
-
this.
|
|
3201
|
-
|
|
3202
|
-
|
|
3141
|
+
if (this.activeLegacy?.turn === turn)
|
|
3142
|
+
this.activeLegacy = undefined;
|
|
3143
|
+
this.endActivity(typeof result?.stopReason === "string" ? result.stopReason : "unknown", result?.stopReason === "cancelled");
|
|
3144
|
+
this.observeUsage(result, "prompt_result");
|
|
3145
|
+
this.primed = true;
|
|
3146
|
+
const body = this.chunks.join("").trim();
|
|
3147
|
+
const answering = this.turnEnvs.length ? this.turnEnvs : envs;
|
|
3148
|
+
if (body)
|
|
3149
|
+
this.onMessage?.(body, { inReplyTo: replyParent(answering), to: replyAudience(answering) });
|
|
3150
|
+
if (result?.stopReason !== "end_turn")
|
|
3151
|
+
this.reportTurnFailure(envs, `ACP prompt ended without normal completion (${String(result?.stopReason ?? "unknown")})`);
|
|
3152
|
+
if (deliveryId && this.activeDeliveryId === deliveryId) {
|
|
3153
|
+
this.acceptDelivery();
|
|
3154
|
+
this.delivery({ id: deliveryId, state: result?.stopReason === "end_turn" ? "completed" : "needs_review", ...result?.stopReason === "end_turn" ? {} : { reason: "ACP prompt ended without normal completion" } });
|
|
3203
3155
|
}
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
this.deltas.delete(item.id);
|
|
3210
|
-
if (item.phase !== "commentary" && text.trim())
|
|
3211
|
-
this.lastAnswer = text.trim();
|
|
3212
|
-
} else if (method === "turn/completed") {
|
|
3213
|
-
const nativeTurn = params.turn?.id;
|
|
3214
|
-
if (!this.activeTurns.size || typeof nativeTurn === "string" && !this.activeTurns.has(nativeTurn) && ![...this.activeTurns].some((id) => id.startsWith("unknown:")))
|
|
3156
|
+
this.settleSteers(result?.stopReason === "end_turn" ? "completed" : "needs_review", "ACP prompt ended without normal completion");
|
|
3157
|
+
}).catch((e) => {
|
|
3158
|
+
if (turn !== this.turn) {
|
|
3159
|
+
if (this.cancelWait?.turn === turn)
|
|
3160
|
+
this.opts.log?.(this.nativePolicy !== undefined || this.opts.requireOwnedGroup ? "Configured ACP cancelled prompt rejected; native details withheld" : `[${this.id}] cancelled prompt rejected: ${sanitize(e.message)}`);
|
|
3215
3161
|
return;
|
|
3216
|
-
this.activeTurns.delete(nativeTurn);
|
|
3217
|
-
for (const id of this.activeTurns)
|
|
3218
|
-
if (id.startsWith("unknown:"))
|
|
3219
|
-
this.activeTurns.delete(id);
|
|
3220
|
-
if (params.turn?.status === "failed") {
|
|
3221
|
-
this.opts.log?.(`[${this.id}] turn failed: ${params.turn.error?.message ?? "unknown error"}`);
|
|
3222
3162
|
}
|
|
3223
|
-
this.
|
|
3224
|
-
|
|
3163
|
+
this.endActivity("failed", true);
|
|
3164
|
+
const reason = this.nativePolicy !== undefined || this.opts.requireOwnedGroup ? "Configured ACP prompt failed" : sanitize(e.message);
|
|
3165
|
+
this.settleSteers("needs_review", reason);
|
|
3166
|
+
this.opts.log?.(this.nativePolicy !== undefined || this.opts.requireOwnedGroup ? "Configured ACP prompt failed; native details withheld" : `[${this.id}] prompt failed: ${reason}`);
|
|
3167
|
+
const quota = !this.stopping && this.kimiAgent ? acpQuotaRefusal(e.message) : undefined;
|
|
3168
|
+
if (quota) {
|
|
3169
|
+
let paused = false;
|
|
3170
|
+
try {
|
|
3171
|
+
paused = this.opts.onQuotaRefusal?.(quota) === true;
|
|
3172
|
+
} catch (error) {
|
|
3173
|
+
this.opts.log?.(`[${this.id}] quota pause failed: ${sanitize(error instanceof Error ? error.message : String(error))}`);
|
|
3174
|
+
}
|
|
3175
|
+
const safe = paused && !this.deliveryAccepted && !this.promptActivity;
|
|
3176
|
+
if (deliveryId && this.activeDeliveryId === deliveryId)
|
|
3177
|
+
this.delivery({ id: deliveryId, state: safe ? "failed_safe" : "needs_review", reason, ...safe ? { quota: true } : {} });
|
|
3178
|
+
else if (!deliveryId && this.activeLegacy?.turn === turn) {
|
|
3179
|
+
this.activeLegacy = undefined;
|
|
3180
|
+
this.onFailed?.(envs, safe ? { quota: true } : undefined);
|
|
3181
|
+
}
|
|
3225
3182
|
return;
|
|
3226
|
-
if (typeof nativeTurn === "string") {
|
|
3227
|
-
for (const id of this.turnDeliveries.get(nativeTurn) ?? [])
|
|
3228
|
-
this.delivery({ id, state: params.turn?.status === "failed" ? "needs_review" : "completed", ...params.turn?.status === "failed" ? { reason: params.turn?.error?.message ?? "Codex turn failed" } : {} });
|
|
3229
|
-
this.turnDeliveries.delete(nativeTurn);
|
|
3230
3183
|
}
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
this.
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
this.
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
return { kind: u.sessionUpdate === "agent_message_chunk" ? "message" : "thought", text: activityText(u.content.text) };
|
|
3254
|
-
}
|
|
3255
|
-
if (["tool_call", "tool_call_update"].includes(u?.sessionUpdate)) {
|
|
3256
|
-
const fields = [u.toolCallId, u.title, u.kind, u.status].filter((v) => typeof v === "string").map(activityText);
|
|
3257
|
-
for (const location of Array.isArray(u.locations) ? u.locations.slice(0, 12) : []) {
|
|
3258
|
-
if (typeof location?.path === "string")
|
|
3259
|
-
fields.push(activityText(location.path));
|
|
3260
|
-
}
|
|
3261
|
-
for (const c of Array.isArray(u.content) ? u.content.slice(0, 12) : []) {
|
|
3262
|
-
if (c?.type === "diff" && typeof c.path === "string") {
|
|
3263
|
-
const lines = (v) => typeof v === "string" && v ? v.split(`
|
|
3264
|
-
`).length : 0;
|
|
3265
|
-
fields.push(`+${lines(c.newText)} -${lines(c.oldText)} ${activityText(c.path)}`);
|
|
3266
|
-
} else if (c?.type === "content" && c.content?.type === "text") {
|
|
3267
|
-
fields.push(activityText(c.content.text).split(`
|
|
3268
|
-
`).slice(-8).join(`
|
|
3269
|
-
`));
|
|
3270
|
-
} else if (c?.type === "terminal")
|
|
3271
|
-
fields.push("terminal output not reported by this update");
|
|
3272
|
-
}
|
|
3273
|
-
return { kind: "tool", text: fields.join(" ").slice(0, TEXT_CAP) };
|
|
3274
|
-
}
|
|
3275
|
-
if (u?.sessionUpdate === "plan" && Array.isArray(u.entries))
|
|
3276
|
-
return { kind: "plan", text: u.entries.slice(0, 32).map((e) => [e?.status, e?.priority, e?.content].filter((v) => typeof v === "string").map(activityText).join(" ")).join(`
|
|
3277
|
-
`).slice(0, TEXT_CAP) };
|
|
3278
|
-
}
|
|
3279
|
-
var TEXT_CAP = 4096;
|
|
3280
|
-
|
|
3281
|
-
// src/adapters/acp-quota.ts
|
|
3282
|
-
function acpQuotaRefusal(message) {
|
|
3283
|
-
if (!message.startsWith(KIMI_2_1_1_5H_REFUSAL_PREFIX))
|
|
3284
|
-
return;
|
|
3285
|
-
return { id: "5h", used: 1, windowMins: 300, source: "kimi ACP usage-limit refusal" };
|
|
3286
|
-
}
|
|
3287
|
-
var KIMI_2_1_1_5H_REFUSAL_PREFIX = "Authentication required: 403 You've reached your 5-hour usage limit. Your quota will reset when the current 5-hour window ends.";
|
|
3288
|
-
|
|
3289
|
-
// src/adapters/definitions/kimi-native.ts
|
|
3290
|
-
var KIMI_MODE_IDS;
|
|
3291
|
-
var init_kimi_native = __esm(() => {
|
|
3292
|
-
KIMI_MODE_IDS = { ask: "default", "ask-when-needed": "yolo", "never-ask": "auto" };
|
|
3293
|
-
});
|
|
3294
|
-
|
|
3295
|
-
// src/adapters/definitions/qwen-native.ts
|
|
3296
|
-
function qwenNativeIdentity(instance, production, init, kind) {
|
|
3297
|
-
const native = init;
|
|
3298
|
-
const identity = production === true && (kind ?? instance) === "qwen" && native?.agentInfo?.name === "qwen-code";
|
|
3299
|
-
const version = identity ? assessVersion("qwen", native?.agentInfo?.version) : undefined;
|
|
3300
|
-
if (production && !version?.ok)
|
|
3301
|
-
throw new QwenIdentityError(version && !version.ok ? version.reason : undefined);
|
|
3302
|
-
const verified = !!version?.ok;
|
|
3303
|
-
return verified;
|
|
3304
|
-
}
|
|
3305
|
-
function buildQwenLaunch(command, model) {
|
|
3306
|
-
const [cmd, ...args] = command;
|
|
3307
|
-
if (!cmd)
|
|
3308
|
-
throw new Error("qwen_cmd must name an executable");
|
|
3309
|
-
return { cmd, args: [...args, ...model ? ["--model", model] : []] };
|
|
3310
|
-
}
|
|
3311
|
-
var QWEN_MODE_IDS, QwenIdentityError, qwenSessionId = (value) => typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/.test(value);
|
|
3312
|
-
var init_qwen_native = __esm(() => {
|
|
3313
|
-
init_compat();
|
|
3314
|
-
QWEN_MODE_IDS = { ask: "default", "ask-when-needed": "auto-edit", "never-ask": "yolo" };
|
|
3315
|
-
QwenIdentityError = class QwenIdentityError extends Error {
|
|
3316
|
-
constructor(reason) {
|
|
3317
|
-
super(reason ? `Qwen Code native identity is not verified: ${reason}` : "Qwen Code native identity is not verified; check the executable and its advertised ACP version");
|
|
3318
|
-
}
|
|
3319
|
-
};
|
|
3320
|
-
});
|
|
3321
|
-
|
|
3322
|
-
// src/adapters/acp-owned-group.ts
|
|
3323
|
-
class AcpOwnedGroup {
|
|
3324
|
-
proc;
|
|
3325
|
-
leader;
|
|
3326
|
-
ports;
|
|
3327
|
-
confirmedGone = false;
|
|
3328
|
-
constructor(proc, leader, ports) {
|
|
3329
|
-
this.proc = proc;
|
|
3330
|
-
this.leader = leader;
|
|
3331
|
-
this.ports = ports;
|
|
3332
|
-
}
|
|
3333
|
-
static async capture(proc, ports = PORTS) {
|
|
3334
|
-
const pid = proc.pid;
|
|
3335
|
-
if (!Number.isSafeInteger(pid) || !pid || pid <= 1 || proc.exitCode !== null || proc.signalCode !== null)
|
|
3336
|
-
throw ownershipError();
|
|
3337
|
-
const rows = await AcpOwnedGroup.read(ports);
|
|
3338
|
-
const leader = rows.find((row) => row.pid === pid);
|
|
3339
|
-
if (!leader || leader.pgid !== pid || leader.ppid !== process.pid)
|
|
3340
|
-
throw ownershipError();
|
|
3341
|
-
const signature = AcpOwnedGroup.identity(ports, pid);
|
|
3342
|
-
if (!signature || proc.pid !== pid || proc.exitCode !== null || proc.signalCode !== null)
|
|
3343
|
-
throw ownershipError();
|
|
3344
|
-
return new AcpOwnedGroup(proc, { pid, ppid: leader.ppid, pgid: leader.pgid, started: leader.started }, ports);
|
|
3345
|
-
}
|
|
3346
|
-
static async confirmGone(proc, ports = PORTS) {
|
|
3347
|
-
const pid = proc.pid;
|
|
3348
|
-
if (pid === undefined)
|
|
3349
|
-
return true;
|
|
3350
|
-
if (!Number.isSafeInteger(pid) || pid <= 1 || !(typeof proc.exitCode === "number" || typeof proc.signalCode === "string"))
|
|
3351
|
-
return false;
|
|
3352
|
-
try {
|
|
3353
|
-
const rows = await AcpOwnedGroup.read(ports);
|
|
3354
|
-
return proc.pid === pid && (typeof proc.exitCode === "number" || typeof proc.signalCode === "string") && !rows.some((row) => row.pid === pid || row.pgid === pid);
|
|
3355
|
-
} catch {
|
|
3356
|
-
return false;
|
|
3357
|
-
}
|
|
3358
|
-
}
|
|
3359
|
-
async verify() {
|
|
3360
|
-
if (this.confirmedGone || !await this.inspect(false))
|
|
3361
|
-
throw ownershipError();
|
|
3362
|
-
}
|
|
3363
|
-
async stop({ killMs = 2000 } = {}) {
|
|
3364
|
-
if (this.confirmedGone)
|
|
3365
|
-
return;
|
|
3366
|
-
if (!Number.isFinite(killMs) || killMs <= 0 || killMs > 1e4)
|
|
3367
|
-
throw shutdownError();
|
|
3368
|
-
if (!await this.inspect(true)) {
|
|
3369
|
-
this.confirmedGone = true;
|
|
3370
|
-
return;
|
|
3184
|
+
this.reportTurnFailure(envs, reason);
|
|
3185
|
+
if (deliveryId && this.activeDeliveryId === deliveryId)
|
|
3186
|
+
this.delivery({ id: deliveryId, state: "needs_review", reason });
|
|
3187
|
+
else if (!deliveryId && this.activeLegacy?.turn === turn) {
|
|
3188
|
+
this.activeLegacy = undefined;
|
|
3189
|
+
this.onFailed?.(envs);
|
|
3190
|
+
}
|
|
3191
|
+
}).finally(() => {
|
|
3192
|
+
const waiting = this.cancelWait;
|
|
3193
|
+
if (waiting?.turn === turn) {
|
|
3194
|
+
this.clearCancelWait();
|
|
3195
|
+
if (!this.stopping && this.turn === waiting.successor && this.state === "busy")
|
|
3196
|
+
this.setState("idle");
|
|
3197
|
+
return;
|
|
3198
|
+
}
|
|
3199
|
+
if (turn === this.turn && !this.stopping && this.state === "busy")
|
|
3200
|
+
this.setState("idle");
|
|
3201
|
+
if (this.activeLegacy?.turn === turn)
|
|
3202
|
+
this.activeLegacy = undefined;
|
|
3203
|
+
if (turn === this.turn && this.activeDeliveryId === deliveryId)
|
|
3204
|
+
this.activeDeliveryId = undefined;
|
|
3205
|
+
});
|
|
3371
3206
|
}
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3207
|
+
reportTurnFailure(envs, reason) {
|
|
3208
|
+
try {
|
|
3209
|
+
const result = this.opts.onTurnFailure?.(envs, this.nativePolicy !== undefined || this.opts.requireOwnedGroup ? "Configured ACP prompt failed" : reason);
|
|
3210
|
+
if (result)
|
|
3211
|
+
result.catch(() => this.opts.log?.("ACP failure handoff could not be completed"));
|
|
3212
|
+
} catch {
|
|
3213
|
+
this.opts.log?.("ACP failure handoff could not be completed");
|
|
3214
|
+
}
|
|
3376
3215
|
}
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
throw shutdownError();
|
|
3383
|
-
const rows = await AcpOwnedGroup.read(this.ports, Math.min(READ_MS, left));
|
|
3384
|
-
const leader = rows.find((row) => row.pid === this.leader.pid);
|
|
3385
|
-
if (leader && (leader.started !== this.leader.started || leader.pgid !== this.leader.pgid))
|
|
3386
|
-
throw ownershipError();
|
|
3387
|
-
if (!rows.some((row) => row.pgid === this.leader.pgid) && !leader) {
|
|
3388
|
-
this.confirmedGone = true;
|
|
3216
|
+
onWatchdog() {
|
|
3217
|
+
if (this.state !== "busy")
|
|
3218
|
+
return;
|
|
3219
|
+
if (this.cancelWait) {
|
|
3220
|
+
this.clearWatchdog();
|
|
3389
3221
|
return;
|
|
3390
3222
|
}
|
|
3391
|
-
if (this.
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3223
|
+
if (this.openTools.size) {
|
|
3224
|
+
const ceiling = Math.max(this.watchdogMs, this.opts.toolCeilingMs ?? TOOL_CEILING_MS);
|
|
3225
|
+
const remaining = ceiling - (Date.now() - this.lastSignAt);
|
|
3226
|
+
if (remaining > 0) {
|
|
3227
|
+
this.touch(Math.min(this.watchdogMs, remaining));
|
|
3228
|
+
return;
|
|
3229
|
+
}
|
|
3230
|
+
try {
|
|
3231
|
+
this.activityPrivate ||= this.opts.isPrivateTurn?.() === true;
|
|
3232
|
+
} catch {
|
|
3233
|
+
this.activityPrivate = true;
|
|
3234
|
+
}
|
|
3235
|
+
const tools = this.activityPrivate ? "a tool call" : [...new Set(this.openTools.values())].map((title) => JSON.stringify(activityText(title).replace(/[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/gu, " ").slice(0, 80))).join(", ");
|
|
3236
|
+
this.opts.log?.(`[${this.id}] open tool call (${tools}) silent past the ${Math.round(ceiling / 60000)} min tool ceiling; cancelling the turn`);
|
|
3237
|
+
}
|
|
3238
|
+
this.endActivity("watchdog cancelled", true);
|
|
3239
|
+
const cancelledTurn = this.turn;
|
|
3240
|
+
this.cancelPermissions();
|
|
3241
|
+
this.activeLegacy = undefined;
|
|
3242
|
+
const durable = !!this.activeDeliveryId;
|
|
3243
|
+
if (this.activeDeliveryId)
|
|
3244
|
+
this.delivery({ id: this.activeDeliveryId, state: "needs_review", reason: "ACP turn watchdog timeout" });
|
|
3245
|
+
this.settleSteers("needs_review", "ACP turn watchdog timeout");
|
|
3246
|
+
this.activeDeliveryId = undefined;
|
|
3247
|
+
this.notify("session/cancel", { sessionId: this.sessionId });
|
|
3248
|
+
this.turn++;
|
|
3249
|
+
if (durable)
|
|
3250
|
+
this.setState("offline");
|
|
3251
|
+
else {
|
|
3252
|
+
this.clearWatchdog();
|
|
3253
|
+
const successor = this.turn;
|
|
3254
|
+
const timer = setTimeout(() => {
|
|
3255
|
+
const waiting = this.cancelWait;
|
|
3256
|
+
if (!waiting || waiting.turn !== cancelledTurn || waiting.successor !== successor)
|
|
3257
|
+
return;
|
|
3258
|
+
this.clearCancelWait();
|
|
3259
|
+
if (!this.stopping && this.turn === successor) {
|
|
3260
|
+
this.setState("offline");
|
|
3261
|
+
this.opts.log?.(`[${this.id}] cancelled prompt did not report within the cancellation deadline; peer offline, queued work retained; stop and restart the peer to resume delivery`);
|
|
3262
|
+
}
|
|
3263
|
+
}, this.opts.cancelWaitMs ?? HANDSHAKE_MS);
|
|
3264
|
+
timer.unref?.();
|
|
3265
|
+
let release;
|
|
3266
|
+
const released = new Promise((resolve) => {
|
|
3267
|
+
release = resolve;
|
|
3268
|
+
});
|
|
3269
|
+
this.cancelWait = { turn: cancelledTurn, successor, timer, reported: false, released, release };
|
|
3270
|
+
}
|
|
3421
3271
|
}
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
timer = setTimeout(() => resolve(undefined), ms);
|
|
3430
|
-
})
|
|
3431
|
-
]);
|
|
3432
|
-
if (!rows)
|
|
3433
|
-
throw ownershipError();
|
|
3434
|
-
return rows;
|
|
3435
|
-
} catch {
|
|
3436
|
-
throw ownershipError();
|
|
3437
|
-
} finally {
|
|
3438
|
-
clearTimeout(timer);
|
|
3272
|
+
clearCancelWait() {
|
|
3273
|
+
const waiting = this.cancelWait;
|
|
3274
|
+
this.cancelWait = undefined;
|
|
3275
|
+
if (waiting) {
|
|
3276
|
+
clearTimeout(waiting.timer);
|
|
3277
|
+
waiting.release();
|
|
3278
|
+
}
|
|
3439
3279
|
}
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
}
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
return m && serverNames?.some((name) => name === m[2]) ? `mcp__${m[2]}__${m[1]}` : undefined;
|
|
3489
|
-
}
|
|
3490
|
-
function jsonObject(text) {
|
|
3491
|
-
try {
|
|
3492
|
-
const v = text === undefined ? undefined : JSON.parse(text);
|
|
3493
|
-
return v !== null && typeof v === "object" && !Array.isArray(v) ? v : undefined;
|
|
3494
|
-
} catch {
|
|
3495
|
-
return;
|
|
3496
|
-
}
|
|
3497
|
-
}
|
|
3498
|
-
var ACTION_BUDGET_OPTION = "agent-hub:action-budget", HANDSHAKE_MS = 30000, TOOL_INPUT_CAP = 64, TOOL_CEILING_MS, RpcTimeoutError, AcpPeer;
|
|
3499
|
-
var init_acp = __esm(() => {
|
|
3500
|
-
init_envelope();
|
|
3501
|
-
init_kimi_native();
|
|
3502
|
-
init_qwen_native();
|
|
3503
|
-
init_qwen_native();
|
|
3504
|
-
init_child_process();
|
|
3505
|
-
init_acp_owned_group();
|
|
3506
|
-
TOOL_CEILING_MS = 30 * 60000;
|
|
3507
|
-
RpcTimeoutError = class RpcTimeoutError extends Error {
|
|
3508
|
-
};
|
|
3509
|
-
AcpPeer = class AcpPeer extends BasePeer {
|
|
3510
|
-
opts;
|
|
3511
|
-
permissionMode;
|
|
3512
|
-
nativePolicy;
|
|
3513
|
-
modeUnknown = false;
|
|
3514
|
-
kimiAgent = false;
|
|
3515
|
-
qwenAgent = false;
|
|
3516
|
-
availableModes = new Set;
|
|
3517
|
-
proc;
|
|
3518
|
-
ownedGroup;
|
|
3519
|
-
sessionId = "";
|
|
3520
|
-
nextId = 1;
|
|
3521
|
-
pending = new Map;
|
|
3522
|
-
chunks = [];
|
|
3523
|
-
toolInputs = new Map;
|
|
3524
|
-
toolText = new Map;
|
|
3525
|
-
toolTitles = new Map;
|
|
3526
|
-
openTools = new Map;
|
|
3527
|
-
lastSignAt = 0;
|
|
3528
|
-
primed = false;
|
|
3529
|
-
turn = 0;
|
|
3530
|
-
stopping = false;
|
|
3531
|
-
activeDeliveryId;
|
|
3532
|
-
activeLegacy;
|
|
3533
|
-
deliveryAccepted = false;
|
|
3534
|
-
turnEnvs = [];
|
|
3535
|
-
steerIds = [];
|
|
3536
|
-
cancelWait;
|
|
3537
|
-
promptActivity = false;
|
|
3538
|
-
activityOpen = false;
|
|
3539
|
-
activityPrivate = false;
|
|
3540
|
-
activityStub = false;
|
|
3541
|
-
activityStarted = 0;
|
|
3542
|
-
activityPending;
|
|
3543
|
-
activityTimer;
|
|
3544
|
-
emitActivity(kind, text) {
|
|
3545
|
-
if (!this.opts.onActivity || !this.activityOpen)
|
|
3280
|
+
observeUsage(value, source) {
|
|
3281
|
+
const observation = normalizeACPUsage(value, source);
|
|
3282
|
+
this.opts.onUsageDiagnostic?.(observation, this.sessionId);
|
|
3283
|
+
if (observation.availability === "known")
|
|
3284
|
+
this.opts.onTokens?.(observation.total, this.sessionId);
|
|
3285
|
+
}
|
|
3286
|
+
currentTurn() {
|
|
3287
|
+
return this.turn;
|
|
3288
|
+
}
|
|
3289
|
+
adoptIntoTurn(turn, envs, deliveryId) {
|
|
3290
|
+
if (turn !== this.turn || this.state !== "busy" || this.cancelWait || this.stopping)
|
|
3291
|
+
throw new Error(`${this.id} is ${this.state}, or its turn changed`);
|
|
3292
|
+
this.turnEnvs = [...this.turnEnvs, ...envs];
|
|
3293
|
+
this.activityPrivate ||= envs.some((env) => env.private === true);
|
|
3294
|
+
this.primed = true;
|
|
3295
|
+
if (deliveryId) {
|
|
3296
|
+
this.steerIds.push(deliveryId);
|
|
3297
|
+
this.delivery({ id: deliveryId, state: "accepted" });
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
settleSteers(state, reason) {
|
|
3301
|
+
const ids = this.steerIds;
|
|
3302
|
+
this.steerIds = [];
|
|
3303
|
+
for (const id of ids)
|
|
3304
|
+
this.delivery({ id, state, ...state === "needs_review" ? { reason: reason ?? "steered turn did not complete normally" } : {} });
|
|
3305
|
+
}
|
|
3306
|
+
acceptDelivery() {
|
|
3307
|
+
if (!this.activeDeliveryId || this.deliveryAccepted)
|
|
3308
|
+
return;
|
|
3309
|
+
this.deliveryAccepted = true;
|
|
3310
|
+
this.delivery({ id: this.activeDeliveryId, state: "accepted" });
|
|
3311
|
+
}
|
|
3312
|
+
down(reason) {
|
|
3313
|
+
this.clearCancelWait();
|
|
3314
|
+
if (this.nativePolicy !== undefined || this.opts.requireOwnedGroup)
|
|
3315
|
+
reason = "Configured ACP transport stopped";
|
|
3316
|
+
this.opts.log?.(`[${this.id}] ${reason}`);
|
|
3317
|
+
for (const p of this.pending.values())
|
|
3318
|
+
p.reject(new Error(reason));
|
|
3319
|
+
this.pending.clear();
|
|
3320
|
+
if (this.activeDeliveryId)
|
|
3321
|
+
this.delivery({ id: this.activeDeliveryId, state: "needs_review", reason });
|
|
3322
|
+
this.settleSteers("needs_review", reason);
|
|
3323
|
+
this.activeDeliveryId = undefined;
|
|
3324
|
+
this.setState("offline");
|
|
3325
|
+
}
|
|
3326
|
+
onLine(line) {
|
|
3327
|
+
if (this.stopping)
|
|
3546
3328
|
return;
|
|
3329
|
+
let msg;
|
|
3547
3330
|
try {
|
|
3548
|
-
|
|
3331
|
+
msg = JSON.parse(line);
|
|
3549
3332
|
} catch {
|
|
3550
|
-
|
|
3333
|
+
return;
|
|
3551
3334
|
}
|
|
3552
|
-
if (this.
|
|
3553
|
-
|
|
3335
|
+
if (this.state === "busy" && !this.cancelWait) {
|
|
3336
|
+
this.lastSignAt = Date.now();
|
|
3337
|
+
this.touch();
|
|
3338
|
+
}
|
|
3339
|
+
if (msg.method === "session/update") {
|
|
3340
|
+
if (msg.params?.sessionId && msg.params.sessionId !== this.sessionId)
|
|
3554
3341
|
return;
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3342
|
+
const u = msg.params?.update;
|
|
3343
|
+
if (u?.sessionUpdate === "usage_update" && this.state !== "offline") {
|
|
3344
|
+
const observation = normalizeACPUsage(u, "usage_update");
|
|
3345
|
+
if (observation.shape === "context-used") {
|
|
3346
|
+
if (this.state === "busy" && !this.cancelWait || msg.params?.sessionId === this.sessionId)
|
|
3347
|
+
this.opts.onUsageDiagnostic?.(observation, this.sessionId);
|
|
3348
|
+
return;
|
|
3349
|
+
}
|
|
3350
|
+
}
|
|
3351
|
+
if (this.state !== "busy" || this.cancelWait)
|
|
3352
|
+
return;
|
|
3353
|
+
this.promptActivity = true;
|
|
3354
|
+
this.acceptDelivery();
|
|
3355
|
+
this.updateWatch(u);
|
|
3356
|
+
if (u?.sessionUpdate === "agent_message_chunk" && u.content?.type === "text")
|
|
3357
|
+
this.chunks.push(u.content.text);
|
|
3358
|
+
else if ((u?.sessionUpdate === "tool_call" || u?.sessionUpdate === "tool_call_update") && typeof u.toolCallId === "string") {
|
|
3359
|
+
if (u.sessionUpdate === "tool_call")
|
|
3360
|
+
this.toolInputs.delete(u.toolCallId), this.toolText.delete(u.toolCallId), this.toolTitles.delete(u.toolCallId);
|
|
3361
|
+
if (u.rawInput !== undefined)
|
|
3362
|
+
this.toolInputs.set(u.toolCallId, u.rawInput);
|
|
3363
|
+
if (u.sessionUpdate === "tool_call" && typeof u.title === "string")
|
|
3364
|
+
this.toolTitles.set(u.toolCallId, u.title);
|
|
3365
|
+
const text = Array.isArray(u.content) ? u.content.map((c) => c?.type === "content" && c.content?.type === "text" ? String(c.content.text) : "").join("") : "";
|
|
3366
|
+
if (text)
|
|
3367
|
+
this.toolText.set(u.toolCallId, text);
|
|
3368
|
+
if (u.status === "completed" || u.status === "failed")
|
|
3369
|
+
this.toolInputs.delete(u.toolCallId), this.toolText.delete(u.toolCallId), this.toolTitles.delete(u.toolCallId), this.openTools.delete(u.toolCallId);
|
|
3370
|
+
else if (u.sessionUpdate === "tool_call" || this.openTools.has(u.toolCallId))
|
|
3371
|
+
this.openTools.set(u.toolCallId, this.toolTitles.get(u.toolCallId) ?? "tool");
|
|
3372
|
+
while (this.openTools.size > TOOL_INPUT_CAP)
|
|
3373
|
+
this.openTools.delete(this.openTools.keys().next().value);
|
|
3374
|
+
for (const map of [this.toolInputs, this.toolText, this.toolTitles])
|
|
3375
|
+
while (map.size > TOOL_INPUT_CAP)
|
|
3376
|
+
map.delete(map.keys().next().value);
|
|
3377
|
+
} else if (u?.sessionUpdate === "usage_update")
|
|
3378
|
+
this.observeUsage(u, "usage_update");
|
|
3379
|
+
} else if (msg.method === "session/request_permission") {
|
|
3380
|
+
if (this.cancelWait || this.state === "offline") {
|
|
3381
|
+
this.send({ jsonrpc: "2.0", id: msg.id, result: { outcome: { outcome: "cancelled" } } });
|
|
3382
|
+
return;
|
|
3383
|
+
}
|
|
3384
|
+
if (this.state === "busy" && (!msg.params?.sessionId || msg.params.sessionId === this.sessionId))
|
|
3385
|
+
this.promptActivity = true;
|
|
3386
|
+
this.answerPermission(msg);
|
|
3387
|
+
} else if (msg.method && msg.id !== undefined) {
|
|
3388
|
+
this.send({ jsonrpc: "2.0", id: msg.id, error: { code: -32601, message: "method not supported by agent-hub" } });
|
|
3389
|
+
} else if (msg.id !== undefined) {
|
|
3390
|
+
const p = this.pending.get(msg.id);
|
|
3391
|
+
this.pending.delete(msg.id);
|
|
3392
|
+
if (msg.error)
|
|
3393
|
+
p?.reject(new Error(msg.error.message ?? "rpc error"));
|
|
3394
|
+
else
|
|
3395
|
+
p?.resolve(msg.result);
|
|
3558
3396
|
}
|
|
3559
|
-
try {
|
|
3560
|
-
this.opts.onActivity({ kind, text: activityText(text), at: Date.now(), turn: this.turn });
|
|
3561
|
-
} catch {}
|
|
3562
|
-
}
|
|
3563
|
-
flushActivity() {
|
|
3564
|
-
clearTimeout(this.activityTimer);
|
|
3565
|
-
this.activityTimer = undefined;
|
|
3566
|
-
const pending = this.activityPending;
|
|
3567
|
-
this.activityPending = undefined;
|
|
3568
|
-
if (pending)
|
|
3569
|
-
this.emitActivity(pending.kind, pending.text);
|
|
3570
3397
|
}
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
const
|
|
3575
|
-
if (!
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3398
|
+
async answerPermission(msg) {
|
|
3399
|
+
this.flushActivity();
|
|
3400
|
+
const call = msg.params?.toolCall ?? {};
|
|
3401
|
+
const kind = typeof call.kind === "string" ? call.kind : undefined;
|
|
3402
|
+
if (this.nativePolicy?.lockedReadOnly && !["read", "search", "think"].includes(kind ?? "")) {
|
|
3403
|
+
const rejected = (msg.params?.options ?? []).find((option) => option.kind === "reject_once");
|
|
3404
|
+
return this.send({ jsonrpc: "2.0", id: msg.id, result: { outcome: rejected ? { outcome: "selected", optionId: rejected.optionId } : { outcome: "cancelled" } } });
|
|
3405
|
+
}
|
|
3406
|
+
const once = (msg.params?.options ?? []).find((o) => o.kind === "allow_once");
|
|
3407
|
+
const id = typeof call.toolCallId === "string" ? call.toolCallId : undefined;
|
|
3408
|
+
const announced = id === undefined ? undefined : this.toolTitles.get(id);
|
|
3409
|
+
const canonical = canonicalMcpToolName(announced, this.opts.mcpServers?.map((s) => s.name));
|
|
3410
|
+
const identity = this.nativePolicy !== undefined || this.opts.requireOwnedGroup ? undefined : [typeof call.title === "string" ? call.title : undefined, canonical].find((candidate) => candidate !== undefined && this.opts.autoApprove?.(candidate));
|
|
3411
|
+
const rawInput = call.rawInput ?? (id === undefined ? undefined : this.toolInputs.get(id) ?? jsonObject(this.toolText.get(id)));
|
|
3412
|
+
let gate;
|
|
3413
|
+
try {
|
|
3414
|
+
gate = rawInput === undefined || !this.sessionId ? undefined : this.opts.actionGate?.({ names: [call.title, announced, canonical].filter((name) => typeof name === "string"), ...kind ? { kind } : {}, input: rawInput }, this.sessionId);
|
|
3415
|
+
} catch {
|
|
3416
|
+
gate = { title: "Action approval: the always-ask check failed; command withheld", denyOnly: true };
|
|
3417
|
+
}
|
|
3418
|
+
if (once && identity && !gate) {
|
|
3419
|
+
this.opts.log?.(`permission auto-approved for ${this.id}: ${identity}`);
|
|
3420
|
+
this.emitActivity("ask", `${String(msg.id)}: ${activityText(announced ?? call.title)} (automatic)`);
|
|
3421
|
+
this.emitActivity("answered", `${String(msg.id)}: allow_once (automatic)`);
|
|
3422
|
+
return this.send({ jsonrpc: "2.0", id: msg.id, result: { outcome: { outcome: "selected", optionId: once.optionId } } });
|
|
3423
|
+
}
|
|
3424
|
+
const raw = rawInput;
|
|
3425
|
+
const full = raw === undefined ? "" : typeof raw === "string" ? raw : JSON.stringify(raw);
|
|
3426
|
+
const cut = full.length > 600;
|
|
3427
|
+
const input = raw === undefined ? "" : `: ${cut ? `${full.slice(0, 600)} [cut, ${full.length} chars]` : full}`;
|
|
3428
|
+
const named = typeof call.title === "string" && jsonObject(call.title) === undefined ? call.title : announced;
|
|
3429
|
+
const title = gate ? gate.title : raw === undefined ? `${named ?? "tool call"} (payload not reported by the agent)` : `${named ?? "tool call"}${input}`;
|
|
3430
|
+
this.emitActivity("ask", `${String(msg.id)}: ${activityText(title)}; answer in ahub console`);
|
|
3431
|
+
let options = (msg.params?.options ?? []).filter((o) => o.kind !== "allow_always" || !gate && !this.nativePolicy?.lockedReadOnly && raw !== undefined && !cut);
|
|
3432
|
+
if (gate?.denyOnly)
|
|
3433
|
+
options = options.filter((o) => o.kind === "reject_once" || o.kind === "reject_always");
|
|
3434
|
+
const budgetOption = !gate?.denyOnly && gate?.budget && once ? { optionId: ACTION_BUDGET_OPTION, name: `Allow ${gate.budget.label} for this session`, kind: "allow_once" } : undefined;
|
|
3435
|
+
if (budgetOption)
|
|
3436
|
+
options = [...options, budgetOption];
|
|
3437
|
+
const tool = typeof named === "string" && /^[\w.:@-]{1,80}$/.test(named) ? named : undefined;
|
|
3438
|
+
const turn = this.turn;
|
|
3439
|
+
const alive = setInterval(() => this.state === "busy" && turn === this.turn && this.touch(), Math.max(10, Math.min(30000, Math.floor(this.watchdogMs / 3))));
|
|
3440
|
+
const controller = new AbortController;
|
|
3441
|
+
this.permissionWaits.add(controller);
|
|
3442
|
+
let picked;
|
|
3443
|
+
try {
|
|
3444
|
+
const cancelled = new Promise((resolve) => controller.signal.addEventListener("abort", () => resolve(undefined), { once: true }));
|
|
3445
|
+
picked = await Promise.race([this.opts.onPermission?.({ peer: this.id, title, options, ...tool ? { tool } : {}, ...kind ? { kind } : {}, ...gate ? { classed: true } : {} }, controller.signal).catch(() => {
|
|
3446
|
+
return;
|
|
3447
|
+
}), cancelled]);
|
|
3448
|
+
} finally {
|
|
3449
|
+
clearInterval(alive);
|
|
3450
|
+
this.permissionWaits.delete(controller);
|
|
3451
|
+
}
|
|
3452
|
+
let valid = !controller.signal.aborted && this.state !== "offline" && turn === this.turn && !this.stopping && options.some((o) => o.optionId === picked);
|
|
3453
|
+
let answer = picked;
|
|
3454
|
+
if (valid && budgetOption && picked === budgetOption.optionId) {
|
|
3455
|
+
let granted = false;
|
|
3456
|
+
try {
|
|
3457
|
+
granted = gate.budget.approve();
|
|
3458
|
+
} catch {}
|
|
3459
|
+
if (granted && once)
|
|
3460
|
+
answer = once.optionId;
|
|
3582
3461
|
else
|
|
3583
|
-
|
|
3584
|
-
this.activityTimer ??= setTimeout(() => this.flushActivity(), 50);
|
|
3585
|
-
this.activityTimer.unref?.();
|
|
3586
|
-
} else {
|
|
3587
|
-
this.flushActivity();
|
|
3588
|
-
this.emitActivity(item.kind, item.text);
|
|
3462
|
+
valid = false;
|
|
3589
3463
|
}
|
|
3464
|
+
if (turn === this.turn)
|
|
3465
|
+
this.emitActivity("answered", `${String(msg.id)}: ${valid ? options.find((o) => o.optionId === picked)?.kind : "cancelled"}`);
|
|
3466
|
+
this.send({
|
|
3467
|
+
jsonrpc: "2.0",
|
|
3468
|
+
id: msg.id,
|
|
3469
|
+
result: { outcome: valid ? { outcome: "selected", optionId: answer } : { outcome: "cancelled" } }
|
|
3470
|
+
});
|
|
3590
3471
|
}
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3472
|
+
request(method, params, timeoutMs) {
|
|
3473
|
+
const id = this.nextId++;
|
|
3474
|
+
return new Promise((resolve, reject) => {
|
|
3475
|
+
const timer = timeoutMs === undefined ? undefined : setTimeout(() => {
|
|
3476
|
+
this.pending.delete(id);
|
|
3477
|
+
reject(new RpcTimeoutError(`${method} did not answer within ${timeoutMs / 1000} s`));
|
|
3478
|
+
}, timeoutMs);
|
|
3479
|
+
timer?.unref();
|
|
3480
|
+
this.pending.set(id, {
|
|
3481
|
+
resolve: (value) => {
|
|
3482
|
+
clearTimeout(timer);
|
|
3483
|
+
resolve(value);
|
|
3484
|
+
},
|
|
3485
|
+
reject: (error) => {
|
|
3486
|
+
clearTimeout(timer);
|
|
3487
|
+
reject(error);
|
|
3488
|
+
}
|
|
3489
|
+
});
|
|
3490
|
+
this.send({ jsonrpc: "2.0", id, method, params });
|
|
3491
|
+
});
|
|
3595
3492
|
}
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3493
|
+
notify(method, params) {
|
|
3494
|
+
this.send({ jsonrpc: "2.0", method, params });
|
|
3495
|
+
}
|
|
3496
|
+
send(msg) {
|
|
3497
|
+
if (this.proc?.stdin.writable)
|
|
3498
|
+
this.proc.stdin.write(`${JSON.stringify(msg)}
|
|
3499
|
+
`);
|
|
3500
|
+
}
|
|
3501
|
+
};
|
|
3502
|
+
});
|
|
3503
|
+
|
|
3504
|
+
// src/adapters/codex-appserver.ts
|
|
3505
|
+
import { spawn as spawn6 } from "child_process";
|
|
3506
|
+
function parse(raw) {
|
|
3507
|
+
try {
|
|
3508
|
+
return JSON.parse(raw);
|
|
3509
|
+
} catch {
|
|
3510
|
+
return;
|
|
3511
|
+
}
|
|
3512
|
+
}
|
|
3513
|
+
var UNIDENTIFIED_CANCELLATION_NOTICE = "Codex cancelled turn has no verified native id; reattach the native session before further hub delivery", counter = (value) => {
|
|
3514
|
+
const n = Number(value);
|
|
3515
|
+
return Number.isFinite(n) && n >= 0 ? n : 0;
|
|
3516
|
+
}, TRACKED, CodexPeer;
|
|
3517
|
+
var init_codex_appserver = __esm(() => {
|
|
3518
|
+
init_envelope();
|
|
3519
|
+
init_context_window();
|
|
3520
|
+
init_child_process();
|
|
3521
|
+
init_acp();
|
|
3522
|
+
TRACKED = new Set(["thread/start", "thread/resume"]);
|
|
3523
|
+
CodexPeer = class CodexPeer extends BasePeer {
|
|
3524
|
+
opts;
|
|
3525
|
+
permissionMode;
|
|
3526
|
+
actionWaits = new Map;
|
|
3527
|
+
get actionGated() {
|
|
3528
|
+
return this.opts.actionGate !== undefined;
|
|
3603
3529
|
}
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3530
|
+
nativeApprovalPolicies = new Map;
|
|
3531
|
+
overriddenApprovals = new Set;
|
|
3532
|
+
restoringApprovals = new Map;
|
|
3533
|
+
proc;
|
|
3534
|
+
server;
|
|
3535
|
+
link;
|
|
3536
|
+
claimedTui;
|
|
3537
|
+
threadId = "";
|
|
3538
|
+
contextEpoch = 0;
|
|
3539
|
+
get sessionGeneration() {
|
|
3540
|
+
return this.contextEpoch;
|
|
3611
3541
|
}
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3542
|
+
tokenTotal;
|
|
3543
|
+
tokenKinds;
|
|
3544
|
+
freshThread = false;
|
|
3545
|
+
activeTurns = new Set;
|
|
3546
|
+
deliveryEpoch = 0;
|
|
3547
|
+
retiredTurns = new Set;
|
|
3548
|
+
unidentifiedCancellation = false;
|
|
3549
|
+
get statusNotice() {
|
|
3550
|
+
return this.unidentifiedCancellation ? { code: "unidentified-cancellation", message: UNIDENTIFIED_CANCELLATION_NOTICE } : undefined;
|
|
3616
3551
|
}
|
|
3552
|
+
unboundAccepted = false;
|
|
3553
|
+
nextId = -1;
|
|
3554
|
+
pending = new Map;
|
|
3555
|
+
usageTimer;
|
|
3556
|
+
injected;
|
|
3557
|
+
answering = [];
|
|
3558
|
+
lastAnswer = "";
|
|
3559
|
+
deltas = new Map;
|
|
3560
|
+
primed = false;
|
|
3561
|
+
version;
|
|
3562
|
+
steers = new Set;
|
|
3563
|
+
turnDeliveries = new Map;
|
|
3564
|
+
unboundDeliveries = new Set;
|
|
3617
3565
|
constructor(id, opts) {
|
|
3618
3566
|
super(id, opts.watchdogMs);
|
|
3619
3567
|
this.opts = opts;
|
|
3620
|
-
this.
|
|
3621
|
-
this.permissionMode = this.nativePolicy?.lockedReadOnly ? "ask" : opts.permissionMode ?? "ask";
|
|
3568
|
+
this.permissionMode = opts.permissionMode ?? "ask";
|
|
3622
3569
|
}
|
|
3623
3570
|
recoveryMetadata() {
|
|
3624
|
-
return {
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
if (this.opts.requireOwnedGroup && this.proc)
|
|
3628
|
-
throw new Error("Configured ACP still has a process; stop it before starting another");
|
|
3629
|
-
this.stopping = false;
|
|
3630
|
-
const [bin, ...args] = this.opts.cmd;
|
|
3631
|
-
const proc = spawn6(bin, args, { cwd: this.opts.cwd, env: peerChildEnv(this.id, { ...process.env, ...this.opts.env ?? {} }), stdio: ["pipe", "pipe", "pipe"], detached: true });
|
|
3632
|
-
this.proc = proc;
|
|
3633
|
-
trackGroup(proc);
|
|
3634
|
-
proc.on("error", (e) => {
|
|
3635
|
-
if (!this.opts.requireOwnedGroup || this.proc === proc)
|
|
3636
|
-
this.down(`spawn failed: ${e.message}`);
|
|
3637
|
-
});
|
|
3638
|
-
proc.on("exit", (code) => {
|
|
3639
|
-
if (!this.opts.requireOwnedGroup || this.proc === proc)
|
|
3640
|
-
this.down(`exited with code ${code}`);
|
|
3641
|
-
});
|
|
3642
|
-
proc.stdin.on("error", () => {});
|
|
3643
|
-
proc.stderr.on("data", (d) => this.opts.log?.(this.nativePolicy !== undefined || this.opts.requireOwnedGroup ? "Configured ACP stderr output withheld" : `[${this.id}] ${String(d).trimEnd()}`));
|
|
3644
|
-
createInterface({ input: proc.stdout }).on("line", (line) => this.onLine(line));
|
|
3645
|
-
const handshake = async () => {
|
|
3646
|
-
const init = await this.request("initialize", {
|
|
3647
|
-
protocolVersion: 1,
|
|
3648
|
-
clientCapabilities: { fs: { readTextFile: false, writeTextFile: false }, terminal: false }
|
|
3649
|
-
});
|
|
3650
|
-
this.kimiAgent = this.nativePolicy === undefined && init?.agentInfo?.name === "Kimi Code CLI";
|
|
3651
|
-
const expected = this.opts.expectedAgent, version = init?.agentInfo?.version;
|
|
3652
|
-
if (expected && (init?.agentInfo?.name !== expected.name || !(typeof expected.version === "function" ? expected.version(version) : version === expected.version)))
|
|
3653
|
-
throw new Error("native ACP identity is not verified for this production adapter");
|
|
3654
|
-
const qwen = qwenNativeIdentity(this.id, this.opts.qwen025, init, this.opts.kind);
|
|
3655
|
-
this.qwenAgent = this.nativePolicy === undefined && qwen;
|
|
3656
|
-
const resume = this.opts.resumeSessionId;
|
|
3657
|
-
if (!resume)
|
|
3658
|
-
return this.request("session/new", { cwd: this.opts.cwd, mcpServers: this.opts.mcpServers ?? [] });
|
|
3659
|
-
if (!init?.agentCapabilities?.loadSession)
|
|
3660
|
-
throw new Error(`${this.id} cannot load an earlier session (the agent offers no loadSession)`);
|
|
3661
|
-
const loaded = await this.request("session/load", { sessionId: resume, cwd: this.opts.cwd, mcpServers: this.opts.mcpServers ?? [] });
|
|
3662
|
-
if (loaded?.sessionId !== undefined && loaded.sessionId !== resume || this.opts.requireLoadSessionEcho && loaded?.sessionId !== resume)
|
|
3663
|
-
throw new Error("native ACP load did not confirm the requested conversation; no restored session is claimed");
|
|
3664
|
-
return { ...loaded, sessionId: resume };
|
|
3571
|
+
return {
|
|
3572
|
+
launch: { kind: "codex", bin: this.opts.bin ?? "codex", cwd: this.opts.cwd, appPort: this.opts.appPort, proxyPort: this.opts.proxyPort },
|
|
3573
|
+
...this.threadId ? { threadId: this.threadId } : {}
|
|
3665
3574
|
};
|
|
3666
|
-
try {
|
|
3667
|
-
if (this.opts.requireOwnedGroup) {
|
|
3668
|
-
const proof = await AcpOwnedGroup.capture(proc);
|
|
3669
|
-
this.ownedGroup = { proc, proof };
|
|
3670
|
-
if (this.proc !== proc || this.stopping)
|
|
3671
|
-
throw new Error("Configured ACP process ownership could not be verified");
|
|
3672
|
-
}
|
|
3673
|
-
const timeout = new Promise((_, reject) => {
|
|
3674
|
-
setTimeout(() => reject(new Error(`${this.id} did not complete the ACP handshake within ${HANDSHAKE_MS / 1000} s`)), HANDSHAKE_MS).unref();
|
|
3675
|
-
});
|
|
3676
|
-
const session = await Promise.race([handshake(), timeout]);
|
|
3677
|
-
if (this.nativePolicy !== undefined && (typeof session?.sessionId !== "string" || !session.sessionId.trim() || Buffer.byteLength(session.sessionId) > 4096 || /[\p{Cc}\p{Cf}]/u.test(session.sessionId))) {
|
|
3678
|
-
throw new Error("Configured ACP did not provide a valid native session identity");
|
|
3679
|
-
}
|
|
3680
|
-
this.sessionId = session.sessionId;
|
|
3681
|
-
const modes = session.modes?.availableModes;
|
|
3682
|
-
this.availableModes = new Set(Array.isArray(modes) ? modes.flatMap((mode) => typeof mode?.id === "string" ? [mode.id] : []) : []);
|
|
3683
|
-
const resetResumed = this.opts.resumeSessionId && typeof session.modes?.currentModeId === "string" && session.modes.currentModeId !== KIMI_MODE_IDS.ask;
|
|
3684
|
-
if (this.nativePolicy?.lockedReadOnly)
|
|
3685
|
-
await this.applyNativeMode(this.nativePolicy.readOnly, "ask");
|
|
3686
|
-
else if (this.nativePolicy?.modes !== undefined || this.qwenAgent || this.kimiAgent && (this.permissionMode !== "ask" || resetResumed))
|
|
3687
|
-
await this.setPermissionMode(this.permissionMode);
|
|
3688
|
-
if (this.opts.requireOwnedGroup) {
|
|
3689
|
-
const owned = this.ownedGroup;
|
|
3690
|
-
if (!owned || owned.proc !== proc || this.proc !== proc || this.stopping)
|
|
3691
|
-
throw new Error("Configured ACP process ownership could not be verified");
|
|
3692
|
-
await owned.proof.verify();
|
|
3693
|
-
if (this.proc !== proc || this.stopping)
|
|
3694
|
-
throw new Error("Configured ACP process ownership could not be verified");
|
|
3695
|
-
}
|
|
3696
|
-
} catch (e) {
|
|
3697
|
-
this.setState("offline");
|
|
3698
|
-
if (this.proc === proc) {
|
|
3699
|
-
if (this.opts.requireOwnedGroup) {
|
|
3700
|
-
const owned = this.ownedGroup;
|
|
3701
|
-
if (owned?.proc === proc) {
|
|
3702
|
-
await owned.proof.stop().then(() => {
|
|
3703
|
-
if (this.proc === proc) {
|
|
3704
|
-
this.proc = undefined;
|
|
3705
|
-
this.ownedGroup = undefined;
|
|
3706
|
-
}
|
|
3707
|
-
}, () => this.opts.log?.("Configured ACP process group shutdown incomplete"));
|
|
3708
|
-
} else {
|
|
3709
|
-
proc.stdin.end();
|
|
3710
|
-
if (await AcpOwnedGroup.confirmGone(proc) && this.proc === proc) {
|
|
3711
|
-
this.proc = undefined;
|
|
3712
|
-
this.ownedGroup = undefined;
|
|
3713
|
-
} else
|
|
3714
|
-
this.opts.log?.("Configured ACP process ownership could not be verified; no process signals were sent");
|
|
3715
|
-
}
|
|
3716
|
-
} else
|
|
3717
|
-
await stopOwnedProcess(proc, { group: true }).then(() => {
|
|
3718
|
-
if (this.proc === proc)
|
|
3719
|
-
this.proc = undefined;
|
|
3720
|
-
}, (stop) => this.opts.log?.(`[${this.id}] ${stop.message}`));
|
|
3721
|
-
}
|
|
3722
|
-
throw e;
|
|
3723
|
-
}
|
|
3724
|
-
this.setState("idle");
|
|
3725
|
-
}
|
|
3726
|
-
getPermissionMode() {
|
|
3727
|
-
return this.permissionMode;
|
|
3728
|
-
}
|
|
3729
|
-
get actionGated() {
|
|
3730
|
-
return this.opts.actionGate !== undefined;
|
|
3731
3575
|
}
|
|
3732
|
-
get
|
|
3733
|
-
return this.
|
|
3576
|
+
get thread() {
|
|
3577
|
+
return this.threadId;
|
|
3734
3578
|
}
|
|
3735
|
-
get
|
|
3736
|
-
return
|
|
3579
|
+
get proxyUrl() {
|
|
3580
|
+
return `ws://127.0.0.1:${this.server?.port ?? this.opts.proxyPort}`;
|
|
3737
3581
|
}
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
const modes = this.nativePolicy !== undefined ? this.nativePolicy.modes : this.qwenAgent ? QWEN_MODE_IDS : this.kimiAgent ? KIMI_MODE_IDS : undefined;
|
|
3742
|
-
if (modes === undefined)
|
|
3743
|
-
throw new Error(`${this.id} permission mode ${mode} unmanaged: this ACP agent has no verified mode mapping; use Kimi Code CLI or add an agent-specific mapping`);
|
|
3744
|
-
await this.applyNativeMode(modes[mode], mode);
|
|
3582
|
+
terminalOwner(launchId) {
|
|
3583
|
+
const link = this.link;
|
|
3584
|
+
return this.threadId && link && link === this.claimedTui && link.launchId === launchId && link.tui.readyState === WebSocket.OPEN && link.up.readyState === WebSocket.OPEN ? { generation: String(this.sessionGeneration) } : undefined;
|
|
3745
3585
|
}
|
|
3746
|
-
async
|
|
3747
|
-
|
|
3748
|
-
throw new Error(`${this.id} permission mode unknown after an unanswered change; restart the peer before changing modes`);
|
|
3749
|
-
if (typeof id !== "string" || !id || !this.sessionId || !this.proc || !this.availableModes.has(id))
|
|
3750
|
-
throw new Error(`${this.id} permission mode ${mode} unavailable: session does not offer ${id}`);
|
|
3586
|
+
async start() {
|
|
3587
|
+
const upstream = this.opts.upstreamUrl ?? await this.spawnAppServer();
|
|
3751
3588
|
try {
|
|
3752
|
-
|
|
3589
|
+
this.server = Bun.serve({
|
|
3590
|
+
hostname: "127.0.0.1",
|
|
3591
|
+
port: this.opts.proxyPort,
|
|
3592
|
+
fetch: (req, server) => {
|
|
3593
|
+
if (req.headers.has("origin"))
|
|
3594
|
+
return new Response("forbidden", { status: 403 });
|
|
3595
|
+
const launchId = /^Bearer (\S+)$/.exec(req.headers.get("authorization") ?? "")?.[1];
|
|
3596
|
+
if (server.upgrade(req, { data: { ...launchId && launchId.length <= 128 ? { launchId } : {} } }))
|
|
3597
|
+
return;
|
|
3598
|
+
return new Response("agent-hub codex proxy");
|
|
3599
|
+
},
|
|
3600
|
+
websocket: {
|
|
3601
|
+
open: (tui) => {
|
|
3602
|
+
if (this.opts.admitAttach?.() === false) {
|
|
3603
|
+
tui.close(4000, "codex is off roster; retry when a person adds it");
|
|
3604
|
+
return;
|
|
3605
|
+
}
|
|
3606
|
+
if (this.claimedTui) {
|
|
3607
|
+
tui.close(1013, "codex TUI already attached to this hub");
|
|
3608
|
+
return;
|
|
3609
|
+
}
|
|
3610
|
+
this.attach(tui, upstream);
|
|
3611
|
+
},
|
|
3612
|
+
message: (tui, data) => {
|
|
3613
|
+
if (this.opts.admitAttach?.() === false) {
|
|
3614
|
+
tui.close(4000, "codex is off roster");
|
|
3615
|
+
return;
|
|
3616
|
+
}
|
|
3617
|
+
if (tui.data?.up)
|
|
3618
|
+
this.fromTui(tui.data, String(data));
|
|
3619
|
+
},
|
|
3620
|
+
close: (tui) => {
|
|
3621
|
+
if (tui.data?.up)
|
|
3622
|
+
this.detach(tui.data);
|
|
3623
|
+
}
|
|
3624
|
+
}
|
|
3625
|
+
});
|
|
3753
3626
|
} catch (error) {
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
this.
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
throw new Error(this.nativePolicy !== undefined || this.opts.requireOwnedGroup ? "Configured ACP native permission mode unknown; peer is offline, restart it before sending work" : `${this.id} permission mode ${mode} unknown: ${error.message}; peer is offline, restart it before sending work`);
|
|
3760
|
-
}
|
|
3761
|
-
throw new Error(this.nativePolicy !== undefined || this.opts.requireOwnedGroup ? "Configured ACP native permission mode refused" : `${this.id} permission mode ${mode} refused: ${error.message}`);
|
|
3762
|
-
}
|
|
3763
|
-
if (this.cancelWait?.reported)
|
|
3764
|
-
await this.cancelWait.released;
|
|
3765
|
-
if (this.modeUnknown)
|
|
3766
|
-
throw new Error(`${this.id} permission mode unknown; restart the peer`);
|
|
3767
|
-
this.permissionMode = mode;
|
|
3627
|
+
const proc = this.proc;
|
|
3628
|
+
if (proc && await this.stopQuietly(proc) && this.proc === proc)
|
|
3629
|
+
this.proc = undefined;
|
|
3630
|
+
throw error;
|
|
3631
|
+
}
|
|
3768
3632
|
}
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
this.
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
this.
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
if (reason !== undefined)
|
|
3780
|
-
this.turn++;
|
|
3781
|
-
if (this.activeDeliveryId)
|
|
3782
|
-
this.delivery({ id: this.activeDeliveryId, state: "needs_review", reason: reason ?? "ACP session stopped before settlement" });
|
|
3783
|
-
this.settleSteers("needs_review", reason ?? "ACP session stopped before settlement");
|
|
3784
|
-
this.activeDeliveryId = undefined;
|
|
3633
|
+
stopQuietly(proc) {
|
|
3634
|
+
return stopOwnedProcess(proc, { group: true }).then(() => true, (error) => {
|
|
3635
|
+
this.opts.log?.(`[${this.id}] ${error.message}`);
|
|
3636
|
+
return false;
|
|
3637
|
+
});
|
|
3638
|
+
}
|
|
3639
|
+
async stop() {
|
|
3640
|
+
this.abortActionWaits();
|
|
3641
|
+
this.server?.stop(true);
|
|
3642
|
+
this.claimedTui?.tui.close(1001, "hub shutting down");
|
|
3785
3643
|
const proc = this.proc;
|
|
3786
|
-
if (
|
|
3787
|
-
this.setState("offline");
|
|
3788
|
-
return;
|
|
3789
|
-
}
|
|
3790
|
-
if (this.opts.requireOwnedGroup) {
|
|
3791
|
-
this.setState("offline");
|
|
3792
|
-
const owned = this.ownedGroup;
|
|
3793
|
-
if (!owned) {
|
|
3794
|
-
if (await AcpOwnedGroup.confirmGone(proc) && this.proc === proc) {
|
|
3795
|
-
this.proc = undefined;
|
|
3796
|
-
return;
|
|
3797
|
-
}
|
|
3798
|
-
throw new Error("Configured ACP process ownership could not be verified");
|
|
3799
|
-
}
|
|
3800
|
-
if (owned.proc !== proc)
|
|
3801
|
-
throw new Error("Configured ACP process ownership could not be verified");
|
|
3802
|
-
await owned.proof.stop();
|
|
3803
|
-
if (this.ownedGroup === owned)
|
|
3804
|
-
this.ownedGroup = undefined;
|
|
3805
|
-
} else
|
|
3644
|
+
if (proc) {
|
|
3806
3645
|
await stopOwnedProcess(proc, { group: true });
|
|
3807
|
-
|
|
3808
|
-
|
|
3646
|
+
if (this.proc === proc)
|
|
3647
|
+
this.proc = undefined;
|
|
3648
|
+
}
|
|
3809
3649
|
this.setState("offline");
|
|
3810
3650
|
}
|
|
3811
|
-
|
|
3812
|
-
|
|
3651
|
+
deliver(envs, deliveryId) {
|
|
3652
|
+
const link = this.link;
|
|
3653
|
+
if (this.state !== "idle" || !link || link.up.readyState !== WebSocket.OPEN) {
|
|
3813
3654
|
if (deliveryId)
|
|
3814
|
-
this.delivery({ id: deliveryId, state: "failed_safe", reason: `${this.id} is
|
|
3815
|
-
|
|
3816
|
-
}
|
|
3817
|
-
const turn = ++this.turn;
|
|
3818
|
-
this.activeDeliveryId = deliveryId;
|
|
3819
|
-
this.activeLegacy = deliveryId ? undefined : { turn, envs };
|
|
3820
|
-
this.turnEnvs = envs;
|
|
3821
|
-
this.steerIds = [];
|
|
3822
|
-
this.deliveryAccepted = false;
|
|
3823
|
-
this.promptActivity = false;
|
|
3824
|
-
this.chunks = [];
|
|
3825
|
-
this.openTools.clear();
|
|
3826
|
-
this.activityOpen = true;
|
|
3827
|
-
this.activityPrivate = envs.some((env) => env.private === true);
|
|
3828
|
-
try {
|
|
3829
|
-
this.activityPrivate ||= this.opts.isPrivateTurn?.() === true;
|
|
3830
|
-
} catch {
|
|
3831
|
-
this.activityPrivate = true;
|
|
3655
|
+
this.delivery({ id: deliveryId, state: "failed_safe", reason: `${this.id} is not injectable` });
|
|
3656
|
+
return Promise.reject(new Error(`${this.id} is not injectable`));
|
|
3832
3657
|
}
|
|
3833
|
-
|
|
3834
|
-
this.
|
|
3658
|
+
const text = this.render(envs);
|
|
3659
|
+
const id = this.nextId--, epoch = this.deliveryEpoch, session = this.contextEpoch;
|
|
3835
3660
|
this.setState("busy");
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
this.
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
this.
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
this.
|
|
3661
|
+
return new Promise((resolve, reject) => {
|
|
3662
|
+
let invalidated = false;
|
|
3663
|
+
const retiredAtCancel = new Set;
|
|
3664
|
+
const invalidate = (reason) => {
|
|
3665
|
+
if (invalidated)
|
|
3666
|
+
return;
|
|
3667
|
+
invalidated = true;
|
|
3668
|
+
for (const turn of [...this.activeTurns, ...this.turnDeliveries.keys()])
|
|
3669
|
+
if (!turn.startsWith("unknown:"))
|
|
3670
|
+
retiredAtCancel.add(turn);
|
|
3671
|
+
envs = [];
|
|
3672
|
+
if (deliveryId)
|
|
3673
|
+
this.delivery({ id: deliveryId, state: "needs_review", reason });
|
|
3674
|
+
reject(new Error(reason));
|
|
3675
|
+
};
|
|
3676
|
+
this.pending.set(id, {
|
|
3677
|
+
deliveryId,
|
|
3678
|
+
kind: "deliver",
|
|
3679
|
+
invalidate,
|
|
3680
|
+
resolve: (result) => {
|
|
3681
|
+
const nativeTurn = typeof result?.turn?.id === "string" ? result.turn.id : undefined;
|
|
3682
|
+
if (invalidated || epoch !== this.deliveryEpoch || session !== this.contextEpoch || link !== this.link) {
|
|
3683
|
+
invalidate("Codex turn acceptance arrived after its delivery was invalidated");
|
|
3684
|
+
if (nativeTurn && session === this.contextEpoch && link === this.link && (!retiredAtCancel.has(nativeTurn) || this.retiredTurns.has(nativeTurn)))
|
|
3685
|
+
this.retireTurn(nativeTurn);
|
|
3686
|
+
return;
|
|
3687
|
+
}
|
|
3688
|
+
this.primed = true;
|
|
3689
|
+
this.injected = replyParent(envs);
|
|
3690
|
+
this.answering = [...envs];
|
|
3691
|
+
if (nativeTurn && !this.turnDeliveries.has(nativeTurn))
|
|
3692
|
+
this.turnDeliveries.set(nativeTurn, new Set);
|
|
3693
|
+
if (!nativeTurn && !this.activeTurns.size)
|
|
3694
|
+
this.unboundAccepted = true;
|
|
3695
|
+
if (deliveryId) {
|
|
3696
|
+
if (nativeTurn)
|
|
3697
|
+
this.addTurnDelivery(nativeTurn, deliveryId);
|
|
3698
|
+
else
|
|
3699
|
+
this.unboundDeliveries.add(deliveryId);
|
|
3700
|
+
this.delivery({ id: deliveryId, state: "accepted" });
|
|
3701
|
+
}
|
|
3702
|
+
resolve();
|
|
3703
|
+
},
|
|
3704
|
+
reject: (e) => {
|
|
3705
|
+
this.restoringApprovals.delete(id);
|
|
3706
|
+
if (invalidated)
|
|
3707
|
+
return;
|
|
3708
|
+
if (epoch !== this.deliveryEpoch || session !== this.contextEpoch || link !== this.link) {
|
|
3709
|
+
invalidate("Codex turn refusal arrived after its delivery was invalidated");
|
|
3710
|
+
return;
|
|
3711
|
+
}
|
|
3712
|
+
if (!this.activeTurns.size)
|
|
3713
|
+
this.setState("idle");
|
|
3714
|
+
if (deliveryId)
|
|
3715
|
+
this.delivery({ id: deliveryId, state: this.knownRejection(e) ? "failed_safe" : "needs_review", reason: e.message });
|
|
3716
|
+
reject(e);
|
|
3888
3717
|
}
|
|
3889
|
-
|
|
3890
|
-
}
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3718
|
+
});
|
|
3719
|
+
link.up.send(JSON.stringify({ method: "turn/start", id, params: this.applyPermissionPolicy({ threadId: this.threadId, input: [{ type: "text", text }] }, id) }));
|
|
3720
|
+
});
|
|
3721
|
+
}
|
|
3722
|
+
render(envs) {
|
|
3723
|
+
return this.primed || !this.opts.preamble ? renderDigest(envs, this.primed) : `${this.opts.preamble}
|
|
3724
|
+
|
|
3725
|
+
${renderDigest(envs, false)}`;
|
|
3726
|
+
}
|
|
3727
|
+
steer(envs, deliveryId) {
|
|
3728
|
+
const link = this.link;
|
|
3729
|
+
const expectedTurnId = [...this.activeTurns].reverse().find((t) => !t.startsWith("unknown:"));
|
|
3730
|
+
if (!link || link.up.readyState !== WebSocket.OPEN || !expectedTurnId) {
|
|
3731
|
+
if (deliveryId)
|
|
3732
|
+
this.delivery({ id: deliveryId, state: "failed_safe", reason: `${this.id} has no steerable turn` });
|
|
3733
|
+
return Promise.reject(new Error(`${this.id} has no steerable turn`));
|
|
3734
|
+
}
|
|
3735
|
+
const id = this.nextId--;
|
|
3736
|
+
const expectedEpoch = this.contextEpoch;
|
|
3737
|
+
this.steers.add(id);
|
|
3738
|
+
return new Promise((resolve, reject) => {
|
|
3739
|
+
let timer;
|
|
3740
|
+
const settle = () => {
|
|
3741
|
+
clearTimeout(timer);
|
|
3742
|
+
this.steers.delete(id);
|
|
3743
|
+
};
|
|
3744
|
+
const fail = (e) => {
|
|
3745
|
+
settle();
|
|
3746
|
+
if (deliveryId)
|
|
3747
|
+
this.delivery({ id: deliveryId, state: this.knownRejection(e) ? "failed_safe" : "needs_review", reason: e.message });
|
|
3748
|
+
reject(e);
|
|
3749
|
+
};
|
|
3750
|
+
this.pending.set(id, {
|
|
3751
|
+
deliveryId,
|
|
3752
|
+
kind: "steer",
|
|
3753
|
+
expectedTurnId,
|
|
3754
|
+
afterTurn: deliveryId ? () => {
|
|
3755
|
+
if (timer !== undefined)
|
|
3756
|
+
return;
|
|
3757
|
+
timer = setTimeout(() => {
|
|
3758
|
+
const pending = this.pending.get(id);
|
|
3759
|
+
this.pending.delete(id);
|
|
3760
|
+
pending?.reject(new Error("steer unanswered: RPC timeout"));
|
|
3761
|
+
}, this.opts.steerTimeoutMs ?? 1e4);
|
|
3762
|
+
timer.unref?.();
|
|
3763
|
+
} : undefined,
|
|
3764
|
+
resolve: (result) => {
|
|
3765
|
+
if (deliveryId && (this.link !== link || this.contextEpoch !== expectedEpoch || !this.activeTurns.has(expectedTurnId) || typeof result?.turnId === "string" && result.turnId !== expectedTurnId)) {
|
|
3766
|
+
fail(new Error("steer accepted after its original native turn ended or changed"));
|
|
3767
|
+
return;
|
|
3768
|
+
}
|
|
3769
|
+
settle();
|
|
3770
|
+
this.primed = true;
|
|
3771
|
+
this.injected = replyParent(this.injected ? [this.injected, ...envs] : envs);
|
|
3772
|
+
this.answering.push(...envs);
|
|
3773
|
+
if (deliveryId) {
|
|
3774
|
+
this.addTurnDelivery(expectedTurnId, deliveryId);
|
|
3775
|
+
this.delivery({ id: deliveryId, state: "accepted" });
|
|
3776
|
+
}
|
|
3777
|
+
resolve();
|
|
3778
|
+
},
|
|
3779
|
+
reject: fail
|
|
3780
|
+
});
|
|
3781
|
+
const input = [{ type: "text", text: this.render(envs) }];
|
|
3782
|
+
link.up.send(JSON.stringify({ method: "turn/steer", id, params: { threadId: this.threadId, expectedTurnId, input } }));
|
|
3912
3783
|
});
|
|
3913
3784
|
}
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3785
|
+
steerText(text) {
|
|
3786
|
+
const link = this.link;
|
|
3787
|
+
const expectedTurnId = [...this.activeTurns].reverse().find((t) => !t.startsWith("unknown:"));
|
|
3788
|
+
if (!link || link.up.readyState !== WebSocket.OPEN || !expectedTurnId)
|
|
3789
|
+
return Promise.resolve("refused");
|
|
3790
|
+
const id = this.nextId--;
|
|
3791
|
+
return new Promise((resolve) => {
|
|
3792
|
+
const timer = setTimeout(() => this.pending.delete(id) && resolve("unanswered"), this.opts.steerTimeoutMs ?? 1e4);
|
|
3793
|
+
timer.unref?.();
|
|
3794
|
+
this.pending.set(id, { resolve: () => (clearTimeout(timer), resolve("accepted")), reject: () => (clearTimeout(timer), resolve("refused")) });
|
|
3795
|
+
link.up.send(JSON.stringify({ method: "turn/steer", id, params: { threadId: this.threadId, expectedTurnId, input: [{ type: "text", text }] } }));
|
|
3796
|
+
});
|
|
3922
3797
|
}
|
|
3923
3798
|
onWatchdog() {
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3799
|
+
this.abortActionWaits();
|
|
3800
|
+
this.deliveryEpoch++;
|
|
3801
|
+
const known = new Set([...this.activeTurns, ...this.turnDeliveries.keys()].filter((id) => !id.startsWith("unknown:")));
|
|
3802
|
+
const pendingStarts = [...this.pending.values()].filter((pending) => pending.kind === "deliver");
|
|
3803
|
+
if ([...this.activeTurns].some((id) => id.startsWith("unknown:")) || (this.unboundDeliveries.size || this.unboundAccepted || pendingStarts.length) && !known.size) {
|
|
3804
|
+
this.unidentifiedCancellation = true;
|
|
3805
|
+
this.opts.log?.(UNIDENTIFIED_CANCELLATION_NOTICE);
|
|
3929
3806
|
}
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
this.
|
|
3939
|
-
|
|
3940
|
-
|
|
3807
|
+
for (const turnId of known)
|
|
3808
|
+
this.retireTurn(turnId);
|
|
3809
|
+
for (const pending of pendingStarts)
|
|
3810
|
+
pending.invalidate?.("Codex turn watchdog timeout before native acceptance was confirmed");
|
|
3811
|
+
this.activeTurns.clear();
|
|
3812
|
+
this.unboundAccepted = false;
|
|
3813
|
+
for (const ids of this.turnDeliveries.values())
|
|
3814
|
+
for (const id of ids)
|
|
3815
|
+
this.delivery({ id, state: "needs_review", reason: "Codex turn watchdog timeout" });
|
|
3816
|
+
for (const id of this.unboundDeliveries)
|
|
3817
|
+
this.delivery({ id, state: "needs_review", reason: "Codex turn watchdog timeout" });
|
|
3818
|
+
this.turnDeliveries.clear();
|
|
3819
|
+
this.unboundDeliveries.clear();
|
|
3820
|
+
this.injected = undefined;
|
|
3821
|
+
this.answering = [];
|
|
3822
|
+
this.lastAnswer = "";
|
|
3823
|
+
this.deltas.clear();
|
|
3824
|
+
this.abandonSteers("turn went silent");
|
|
3825
|
+
if (this.unidentifiedCancellation)
|
|
3826
|
+
this.setState("offline");
|
|
3827
|
+
else
|
|
3828
|
+
super.onWatchdog();
|
|
3829
|
+
}
|
|
3830
|
+
retireTurn(turnId) {
|
|
3831
|
+
this.abortActionWaits(undefined, turnId);
|
|
3832
|
+
this.retiredTurns.add(turnId);
|
|
3833
|
+
if (this.link?.up.readyState === WebSocket.OPEN)
|
|
3834
|
+
this.link.up.send(JSON.stringify({ method: "turn/interrupt", id: this.nextId--, params: { threadId: this.threadId, turnId } }));
|
|
3835
|
+
}
|
|
3836
|
+
abandonSteers(reason) {
|
|
3837
|
+
for (const id of this.steers) {
|
|
3838
|
+
const p = this.pending.get(id);
|
|
3839
|
+
this.pending.delete(id);
|
|
3840
|
+
p?.reject(new Error(`steer unanswered: ${reason}`));
|
|
3841
|
+
}
|
|
3842
|
+
}
|
|
3843
|
+
endSteers(turnId) {
|
|
3844
|
+
for (const id of this.steers) {
|
|
3845
|
+
const pending = this.pending.get(id);
|
|
3846
|
+
if (!pending || pending.expectedTurnId !== turnId)
|
|
3847
|
+
continue;
|
|
3848
|
+
if (pending.deliveryId)
|
|
3849
|
+
pending.afterTurn?.();
|
|
3850
|
+
else {
|
|
3851
|
+
this.pending.delete(id);
|
|
3852
|
+
pending.reject(new Error("steer unanswered: turn completed"));
|
|
3941
3853
|
}
|
|
3942
|
-
const tools = this.activityPrivate ? "a tool call" : [...new Set(this.openTools.values())].map((title) => JSON.stringify(activityText(title).replace(/[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/gu, " ").slice(0, 80))).join(", ");
|
|
3943
|
-
this.opts.log?.(`[${this.id}] open tool call (${tools}) silent past the ${Math.round(ceiling / 60000)} min tool ceiling; cancelling the turn`);
|
|
3944
3854
|
}
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
this.
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3855
|
+
}
|
|
3856
|
+
addTurnDelivery(turnId, deliveryId) {
|
|
3857
|
+
const ids = this.turnDeliveries.get(turnId) ?? new Set;
|
|
3858
|
+
ids.add(deliveryId);
|
|
3859
|
+
this.turnDeliveries.set(turnId, ids);
|
|
3860
|
+
this.unboundDeliveries.delete(deliveryId);
|
|
3861
|
+
}
|
|
3862
|
+
knownRejection(error) {
|
|
3863
|
+
return /turn in progress|no active turn|not injectable|rejected|usage limit/i.test(error.message);
|
|
3864
|
+
}
|
|
3865
|
+
async spawnAppServer() {
|
|
3866
|
+
const port = this.opts.appPort;
|
|
3867
|
+
const probe = () => fetch(`http://127.0.0.1:${port}/healthz`).then((r) => r.ok, () => false);
|
|
3868
|
+
if (await probe()) {
|
|
3869
|
+
throw new Error(`port ${port} already answers /healthz: an app-server the hub does not own is running (orphan from a crashed hub?)`);
|
|
3870
|
+
}
|
|
3871
|
+
let gone = "";
|
|
3872
|
+
const env = peerChildEnv(this.id, { ...process.env, ...this.opts.env ?? {} });
|
|
3873
|
+
this.proc = spawn6(this.opts.bin ?? "codex", ["app-server", "--listen", `ws://127.0.0.1:${port}`, ...this.opts.extraArgs ?? []], {
|
|
3874
|
+
cwd: this.opts.cwd,
|
|
3875
|
+
env,
|
|
3876
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
3877
|
+
detached: true
|
|
3878
|
+
});
|
|
3879
|
+
trackGroup(this.proc);
|
|
3880
|
+
this.proc.stderr?.on("data", (d) => this.opts.log?.(`[${this.id}] ${String(d).trimEnd()}`));
|
|
3881
|
+
this.proc.on("error", (e) => gone = `cannot run ${this.opts.bin ?? "codex"}: ${e.message}`);
|
|
3882
|
+
this.proc.on("exit", (code) => {
|
|
3883
|
+
gone ||= `codex app-server exited with code ${code}`;
|
|
3884
|
+
this.opts.log?.(`[${this.id}] ${gone}`);
|
|
3957
3885
|
this.setState("offline");
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
if (!waiting || waiting.turn !== cancelledTurn || waiting.successor !== successor)
|
|
3964
|
-
return;
|
|
3965
|
-
this.clearCancelWait();
|
|
3966
|
-
if (!this.stopping && this.turn === successor) {
|
|
3967
|
-
this.setState("offline");
|
|
3968
|
-
this.opts.log?.(`[${this.id}] cancelled prompt did not report within the cancellation deadline; peer offline, queued work retained; stop and restart the peer to resume delivery`);
|
|
3969
|
-
}
|
|
3970
|
-
}, this.opts.cancelWaitMs ?? HANDSHAKE_MS);
|
|
3971
|
-
timer.unref?.();
|
|
3972
|
-
let release;
|
|
3973
|
-
const released = new Promise((resolve) => {
|
|
3974
|
-
release = resolve;
|
|
3975
|
-
});
|
|
3976
|
-
this.cancelWait = { turn: cancelledTurn, successor, timer, reported: false, released, release };
|
|
3886
|
+
});
|
|
3887
|
+
for (let i = 0;i < 100 && !gone; i++) {
|
|
3888
|
+
if (await probe())
|
|
3889
|
+
return `ws://127.0.0.1:${port}`;
|
|
3890
|
+
await Bun.sleep(100);
|
|
3977
3891
|
}
|
|
3892
|
+
if (gone)
|
|
3893
|
+
throw new Error(gone);
|
|
3894
|
+
const proc = this.proc;
|
|
3895
|
+
if (proc && await this.stopQuietly(proc) && this.proc === proc)
|
|
3896
|
+
this.proc = undefined;
|
|
3897
|
+
throw new Error("codex app-server did not become healthy within 10 s");
|
|
3978
3898
|
}
|
|
3979
|
-
|
|
3980
|
-
const
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3899
|
+
attach(tui, upstream) {
|
|
3900
|
+
const up = new WebSocket(upstream);
|
|
3901
|
+
const link = { tui, up, backlog: [], tracked: new Map, approvalIds: new Set, ...tui.data.launchId ? { launchId: tui.data.launchId } : {} };
|
|
3902
|
+
this.claimedTui = link;
|
|
3903
|
+
tui.data = link;
|
|
3904
|
+
up.onopen = () => {
|
|
3905
|
+
for (const frame of link.backlog.splice(0))
|
|
3906
|
+
up.send(frame);
|
|
3907
|
+
};
|
|
3908
|
+
up.onmessage = (ev) => this.fromAppServer(link, String(ev.data));
|
|
3909
|
+
up.onclose = () => tui.close();
|
|
3910
|
+
}
|
|
3911
|
+
detach(link) {
|
|
3912
|
+
this.abortActionWaits(link);
|
|
3913
|
+
link.up.close();
|
|
3914
|
+
if (this.claimedTui === link)
|
|
3915
|
+
this.claimedTui = undefined;
|
|
3916
|
+
if (this.link !== link)
|
|
3917
|
+
return;
|
|
3918
|
+
clearInterval(this.usageTimer);
|
|
3919
|
+
this.link = undefined;
|
|
3920
|
+
this.restoringApprovals.clear();
|
|
3921
|
+
this.threadId = "";
|
|
3922
|
+
this.contextEpoch++;
|
|
3923
|
+
this.deliveryEpoch++;
|
|
3924
|
+
this.retiredTurns.clear();
|
|
3925
|
+
this.unidentifiedCancellation = false;
|
|
3926
|
+
this.activeTurns.clear();
|
|
3927
|
+
this.unboundAccepted = false;
|
|
3928
|
+
for (const p of this.pending.values()) {
|
|
3929
|
+
if (p.kind === "deliver")
|
|
3930
|
+
p.invalidate?.("Codex TUI detached");
|
|
3931
|
+
else
|
|
3932
|
+
p.reject(new Error(p.kind === "steer" ? "Codex TUI detached" : "codex TUI detached"));
|
|
3985
3933
|
}
|
|
3934
|
+
this.pending.clear();
|
|
3935
|
+
for (const ids of this.turnDeliveries.values())
|
|
3936
|
+
for (const id of ids)
|
|
3937
|
+
this.delivery({ id, state: "needs_review", reason: "Codex TUI detached" });
|
|
3938
|
+
for (const id of this.unboundDeliveries)
|
|
3939
|
+
this.delivery({ id, state: "needs_review", reason: "Codex TUI detached" });
|
|
3940
|
+
this.turnDeliveries.clear();
|
|
3941
|
+
this.unboundDeliveries.clear();
|
|
3942
|
+
this.forgetTurn();
|
|
3943
|
+
this.setState("offline");
|
|
3986
3944
|
}
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
if (
|
|
3991
|
-
|
|
3945
|
+
detachForRoster(mode = "ask") {
|
|
3946
|
+
this.permissionMode = mode;
|
|
3947
|
+
const link = this.claimedTui;
|
|
3948
|
+
if (!link)
|
|
3949
|
+
return;
|
|
3950
|
+
link.tui.close(4000, "codex is off roster; retry when a person adds it");
|
|
3951
|
+
this.detach(link);
|
|
3952
|
+
}
|
|
3953
|
+
forgetTurn() {
|
|
3954
|
+
this.injected = undefined;
|
|
3955
|
+
this.answering = [];
|
|
3956
|
+
this.lastAnswer = "";
|
|
3957
|
+
}
|
|
3958
|
+
get proxyAttached() {
|
|
3959
|
+
return !!this.link && this.link.up.readyState === WebSocket.OPEN;
|
|
3960
|
+
}
|
|
3961
|
+
getPermissionMode() {
|
|
3962
|
+
return this.permissionMode;
|
|
3963
|
+
}
|
|
3964
|
+
get permissionModeState() {
|
|
3965
|
+
return this.getPermissionMode();
|
|
3992
3966
|
}
|
|
3993
|
-
|
|
3994
|
-
|
|
3967
|
+
setStartupPermissionMode(mode) {
|
|
3968
|
+
if (this.link || this.state !== "offline" || this.overriddenApprovals.size)
|
|
3969
|
+
throw new Error("codex startup mode reconciliation requires a fresh unattached proxy");
|
|
3970
|
+
this.permissionMode = mode;
|
|
3995
3971
|
}
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
this.activityPrivate ||= envs.some((env) => env.private === true);
|
|
4001
|
-
this.primed = true;
|
|
4002
|
-
if (deliveryId) {
|
|
4003
|
-
this.steerIds.push(deliveryId);
|
|
4004
|
-
this.delivery({ id: deliveryId, state: "accepted" });
|
|
4005
|
-
}
|
|
3972
|
+
clearPermissionMode() {
|
|
3973
|
+
const restorable = [...this.overriddenApprovals].every((thread) => this.nativeApprovalPolicies.has(thread));
|
|
3974
|
+
this.permissionMode = "ask";
|
|
3975
|
+
return restorable;
|
|
4006
3976
|
}
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
3977
|
+
async setPermissionMode(mode) {
|
|
3978
|
+
if (!this.link || this.link.up.readyState !== WebSocket.OPEN)
|
|
3979
|
+
throw new Error("codex permission mode unavailable: attach the TUI through ahub codex first");
|
|
3980
|
+
if (mode === "ask" && [...this.overriddenApprovals].some((thread) => !this.nativeApprovalPolicies.has(thread)))
|
|
3981
|
+
throw new Error("codex cannot restore ask: native approval policy unavailable; close the TUI, then run ahub permission codex ask before you restart the Codex session with ahub codex");
|
|
3982
|
+
this.permissionMode = mode;
|
|
4012
3983
|
}
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
3984
|
+
applyPermissionPolicy(params, id) {
|
|
3985
|
+
const thread = typeof params.threadId === "string" ? params.threadId : this.threadId;
|
|
3986
|
+
if (this.permissionMode !== "ask") {
|
|
3987
|
+
this.overriddenApprovals.add(thread);
|
|
3988
|
+
return { ...params, approvalPolicy: this.permissionMode === "never-ask" ? "never" : "on-request" };
|
|
3989
|
+
}
|
|
3990
|
+
if (this.overriddenApprovals.has(thread) && this.nativeApprovalPolicies.has(thread)) {
|
|
3991
|
+
this.restoringApprovals.set(id, thread);
|
|
3992
|
+
return { ...params, approvalPolicy: this.nativeApprovalPolicies.get(thread) };
|
|
3993
|
+
}
|
|
3994
|
+
return params;
|
|
4018
3995
|
}
|
|
4019
|
-
|
|
4020
|
-
this.
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
for (const p of this.pending.values())
|
|
4025
|
-
p.reject(new Error(reason));
|
|
4026
|
-
this.pending.clear();
|
|
4027
|
-
if (this.activeDeliveryId)
|
|
4028
|
-
this.delivery({ id: this.activeDeliveryId, state: "needs_review", reason });
|
|
4029
|
-
this.settleSteers("needs_review", reason);
|
|
4030
|
-
this.activeDeliveryId = undefined;
|
|
4031
|
-
this.setState("offline");
|
|
3996
|
+
settlePermissionRestore(id, failed) {
|
|
3997
|
+
const thread = this.restoringApprovals.get(id);
|
|
3998
|
+
this.restoringApprovals.delete(id);
|
|
3999
|
+
if (thread !== undefined && !failed && this.permissionMode === "ask")
|
|
4000
|
+
this.overriddenApprovals.delete(thread);
|
|
4032
4001
|
}
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
let msg;
|
|
4037
|
-
try {
|
|
4038
|
-
msg = JSON.parse(line);
|
|
4039
|
-
} catch {
|
|
4002
|
+
fromTui(link, raw) {
|
|
4003
|
+
const msg = parse(raw);
|
|
4004
|
+
if (msg?.id !== undefined && !msg.method && link.approvalIds.has(msg.id))
|
|
4040
4005
|
return;
|
|
4006
|
+
if (msg?.method === "turn/interrupt" && link === this.link)
|
|
4007
|
+
this.abortActionWaits(link, typeof msg.params?.turnId === "string" ? msg.params.turnId : undefined);
|
|
4008
|
+
if (msg?.method === "turn/start" && msg.params && link === this.link) {
|
|
4009
|
+
const thread = typeof msg.params.threadId === "string" ? msg.params.threadId : this.threadId;
|
|
4010
|
+
const restoring = this.overriddenApprovals.has(thread) && this.nativeApprovalPolicies.has(thread);
|
|
4011
|
+
if (msg.params.approvalPolicy != null && !restoring)
|
|
4012
|
+
this.nativeApprovalPolicies.set(thread, msg.params.approvalPolicy);
|
|
4013
|
+
if (this.permissionMode !== "ask" || this.overriddenApprovals.has(thread)) {
|
|
4014
|
+
msg.params = this.applyPermissionPolicy(msg.params, msg.id);
|
|
4015
|
+
raw = JSON.stringify(msg);
|
|
4016
|
+
}
|
|
4041
4017
|
}
|
|
4042
|
-
if (
|
|
4043
|
-
|
|
4018
|
+
if (msg?.id !== undefined && TRACKED.has(msg.method))
|
|
4019
|
+
link.tracked.set(msg.id, msg.method);
|
|
4020
|
+
if (link.up.readyState === WebSocket.OPEN)
|
|
4021
|
+
link.up.send(raw);
|
|
4022
|
+
else
|
|
4023
|
+
link.backlog.push(raw);
|
|
4024
|
+
}
|
|
4025
|
+
fromAppServer(link, raw) {
|
|
4026
|
+
const msg = parse(raw);
|
|
4027
|
+
const nativeId = msg?.params?.turnId ?? msg?.params?.turn?.id ?? msg?.result?.turn?.id;
|
|
4028
|
+
if (this.state === "busy" && !(typeof nativeId === "string" && this.retiredTurns.has(nativeId)))
|
|
4044
4029
|
this.touch();
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
if (observation.shape === "context-used") {
|
|
4053
|
-
if (this.state === "busy" && !this.cancelWait || msg.params?.sessionId === this.sessionId)
|
|
4054
|
-
this.opts.onUsageDiagnostic?.(observation, this.sessionId);
|
|
4055
|
-
return;
|
|
4056
|
-
}
|
|
4057
|
-
}
|
|
4058
|
-
if (this.state !== "busy" || this.cancelWait)
|
|
4059
|
-
return;
|
|
4060
|
-
this.promptActivity = true;
|
|
4061
|
-
this.acceptDelivery();
|
|
4062
|
-
this.updateWatch(u);
|
|
4063
|
-
if (u?.sessionUpdate === "agent_message_chunk" && u.content?.type === "text")
|
|
4064
|
-
this.chunks.push(u.content.text);
|
|
4065
|
-
else if ((u?.sessionUpdate === "tool_call" || u?.sessionUpdate === "tool_call_update") && typeof u.toolCallId === "string") {
|
|
4066
|
-
if (u.sessionUpdate === "tool_call")
|
|
4067
|
-
this.toolInputs.delete(u.toolCallId), this.toolText.delete(u.toolCallId), this.toolTitles.delete(u.toolCallId);
|
|
4068
|
-
if (u.rawInput !== undefined)
|
|
4069
|
-
this.toolInputs.set(u.toolCallId, u.rawInput);
|
|
4070
|
-
if (u.sessionUpdate === "tool_call" && typeof u.title === "string")
|
|
4071
|
-
this.toolTitles.set(u.toolCallId, u.title);
|
|
4072
|
-
const text = Array.isArray(u.content) ? u.content.map((c) => c?.type === "content" && c.content?.type === "text" ? String(c.content.text) : "").join("") : "";
|
|
4073
|
-
if (text)
|
|
4074
|
-
this.toolText.set(u.toolCallId, text);
|
|
4075
|
-
if (u.status === "completed" || u.status === "failed")
|
|
4076
|
-
this.toolInputs.delete(u.toolCallId), this.toolText.delete(u.toolCallId), this.toolTitles.delete(u.toolCallId), this.openTools.delete(u.toolCallId);
|
|
4077
|
-
else if (u.sessionUpdate === "tool_call" || this.openTools.has(u.toolCallId))
|
|
4078
|
-
this.openTools.set(u.toolCallId, this.toolTitles.get(u.toolCallId) ?? "tool");
|
|
4079
|
-
while (this.openTools.size > TOOL_INPUT_CAP)
|
|
4080
|
-
this.openTools.delete(this.openTools.keys().next().value);
|
|
4081
|
-
for (const map of [this.toolInputs, this.toolText, this.toolTitles])
|
|
4082
|
-
while (map.size > TOOL_INPUT_CAP)
|
|
4083
|
-
map.delete(map.keys().next().value);
|
|
4084
|
-
} else if (u?.sessionUpdate === "usage_update")
|
|
4085
|
-
this.observeUsage(u, "usage_update");
|
|
4086
|
-
} else if (msg.method === "session/request_permission") {
|
|
4087
|
-
if (this.cancelWait || this.state === "offline") {
|
|
4088
|
-
this.send({ jsonrpc: "2.0", id: msg.id, result: { outcome: { outcome: "cancelled" } } });
|
|
4089
|
-
return;
|
|
4090
|
-
}
|
|
4091
|
-
if (this.state === "busy" && (!msg.params?.sessionId || msg.params.sessionId === this.sessionId))
|
|
4092
|
-
this.promptActivity = true;
|
|
4093
|
-
this.answerPermission(msg);
|
|
4094
|
-
} else if (msg.method && msg.id !== undefined) {
|
|
4095
|
-
this.send({ jsonrpc: "2.0", id: msg.id, error: { code: -32601, message: "method not supported by agent-hub" } });
|
|
4096
|
-
} else if (msg.id !== undefined) {
|
|
4030
|
+
if (!msg)
|
|
4031
|
+
return void link.tui.send(raw);
|
|
4032
|
+
if (this.interceptActionApproval(link, msg))
|
|
4033
|
+
return;
|
|
4034
|
+
if (msg.id !== undefined && !msg.method)
|
|
4035
|
+
this.settlePermissionRestore(msg.id, !!msg.error);
|
|
4036
|
+
if (typeof msg.id === "number" && msg.id < 0 && !msg.method) {
|
|
4097
4037
|
const p = this.pending.get(msg.id);
|
|
4098
4038
|
this.pending.delete(msg.id);
|
|
4099
4039
|
if (msg.error)
|
|
4100
|
-
p?.reject(new Error(msg.error.message ?? "
|
|
4040
|
+
p?.reject(new Error(msg.error.message ?? "app-server rejected the request"));
|
|
4101
4041
|
else
|
|
4102
4042
|
p?.resolve(msg.result);
|
|
4043
|
+
return;
|
|
4103
4044
|
}
|
|
4045
|
+
if (typeof msg.result?.userAgent === "string")
|
|
4046
|
+
this.version = /^[^/\s]+\/(\d+\.\d+\.\d+(?:-[\w.]+)?)/.exec(msg.result.userAgent)?.[1] ?? this.version;
|
|
4047
|
+
const tracked = msg.id !== undefined && !msg.method ? link.tracked.get(msg.id) : undefined;
|
|
4048
|
+
if (tracked && link.tracked.delete(msg.id)) {
|
|
4049
|
+
if (typeof msg.result?.thread?.id === "string") {
|
|
4050
|
+
const thread = msg.result.thread.id;
|
|
4051
|
+
if (!this.overriddenApprovals.has(thread) && msg.result.approvalPolicy != null)
|
|
4052
|
+
this.nativeApprovalPolicies.set(thread, msg.result.approvalPolicy);
|
|
4053
|
+
this.adopt(link, msg.result.thread.id, tracked === "thread/start");
|
|
4054
|
+
}
|
|
4055
|
+
} else if (msg.method)
|
|
4056
|
+
this.onNotification(link, msg.method, msg.params ?? {});
|
|
4057
|
+
link.tui.send(raw);
|
|
4104
4058
|
}
|
|
4105
|
-
|
|
4106
|
-
this.
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
const
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4059
|
+
abortActionWaits(link, turn) {
|
|
4060
|
+
for (const [controller, wait] of this.actionWaits)
|
|
4061
|
+
if ((!link || wait.link === link) && (!turn || wait.turn === turn))
|
|
4062
|
+
controller.abort();
|
|
4063
|
+
}
|
|
4064
|
+
interceptActionApproval(link, msg) {
|
|
4065
|
+
if (!this.opts.actionGate || !(typeof msg.id === "string" || Number.isSafeInteger(msg.id)))
|
|
4066
|
+
return false;
|
|
4067
|
+
const legacy = msg.method === "execCommandApproval", params = msg.params;
|
|
4068
|
+
if (!legacy && msg.method !== "item/commandExecution/requestApproval")
|
|
4069
|
+
return false;
|
|
4070
|
+
if (!params || typeof params !== "object")
|
|
4071
|
+
return false;
|
|
4072
|
+
if (link.approvalIds.has(msg.id))
|
|
4073
|
+
return true;
|
|
4074
|
+
if (legacy ? !Array.isArray(params.command) || !params.command.length || !params.command.every((word) => typeof word === "string") : typeof params.command !== "string" || !params.command.length || params.kind != null && params.kind !== "command")
|
|
4075
|
+
return false;
|
|
4076
|
+
const thread = legacy ? params.conversationId : params.threadId;
|
|
4119
4077
|
let gate;
|
|
4120
4078
|
try {
|
|
4121
|
-
gate =
|
|
4079
|
+
gate = this.opts.actionGate({ names: ["shell"], kind: "execute", input: { command: params.command, cwd: params.cwd } }, typeof thread === "string" ? thread : "");
|
|
4122
4080
|
} catch {
|
|
4123
|
-
gate = { title: "Action approval: the always-ask check failed;
|
|
4081
|
+
gate = { title: "Action approval: the always-ask check failed; command withheld", denyOnly: true };
|
|
4082
|
+
}
|
|
4083
|
+
if (!gate)
|
|
4084
|
+
return false;
|
|
4085
|
+
const verdict = gate;
|
|
4086
|
+
if (link.approvalIds.size >= 1024) {
|
|
4087
|
+
this.abortActionWaits(link);
|
|
4088
|
+
link.up.close();
|
|
4089
|
+
link.tui.close(1008, "Codex action approval id capacity reached; reattach");
|
|
4090
|
+
return true;
|
|
4091
|
+
}
|
|
4092
|
+
link.approvalIds.add(msg.id);
|
|
4093
|
+
const named = [...this.activeTurns].filter((turn) => !turn.startsWith("unknown:") && !this.retiredTurns.has(turn));
|
|
4094
|
+
const turn = legacy ? named.length === 1 ? named[0] : undefined : typeof params.turnId === "string" ? params.turnId : undefined;
|
|
4095
|
+
const epoch = this.contextEpoch, controller = new AbortController;
|
|
4096
|
+
const live = () => !controller.signal.aborted && link === this.link && epoch === this.contextEpoch && thread === this.threadId && typeof turn === "string" && this.activeTurns.has(turn) && !this.retiredTurns.has(turn) && !this.unidentifiedCancellation && this.state === "busy" && link.tui.readyState === WebSocket.OPEN && link.up.readyState === WebSocket.OPEN;
|
|
4097
|
+
const answer = (allow) => {
|
|
4098
|
+
if (link.up.readyState === WebSocket.OPEN)
|
|
4099
|
+
link.up.send(JSON.stringify({ id: msg.id, result: { decision: legacy ? allow ? "approved" : "abort" : allow ? "accept" : "decline" } }));
|
|
4100
|
+
};
|
|
4101
|
+
if (!live()) {
|
|
4102
|
+
answer(false);
|
|
4103
|
+
return true;
|
|
4104
|
+
}
|
|
4105
|
+
this.actionWaits.set(controller, { link, turn });
|
|
4106
|
+
const options = [{ optionId: "allow", name: "Allow once", kind: "allow_once" }, { optionId: "deny", name: "Deny", kind: "reject_once" }];
|
|
4107
|
+
if (verdict.denyOnly)
|
|
4108
|
+
options.splice(0, 1);
|
|
4109
|
+
if (!verdict.denyOnly && verdict.budget)
|
|
4110
|
+
options.push({ optionId: ACTION_BUDGET_OPTION, name: `Allow ${verdict.budget.label} for this session`, kind: "allow_once" });
|
|
4111
|
+
const alive = setInterval(() => {
|
|
4112
|
+
if (live())
|
|
4113
|
+
this.touch();
|
|
4114
|
+
}, Math.max(10, Math.min(30000, Math.floor(this.watchdogMs / 3))));
|
|
4115
|
+
(async () => {
|
|
4116
|
+
let picked;
|
|
4117
|
+
try {
|
|
4118
|
+
const cancelled = new Promise((resolve) => controller.signal.addEventListener("abort", () => resolve(undefined), { once: true }));
|
|
4119
|
+
picked = await Promise.race([this.opts.onPermission?.({ peer: this.id, title: verdict.title, tool: "shell", kind: "execute", classed: true, options }, controller.signal), cancelled]);
|
|
4120
|
+
} catch {} finally {
|
|
4121
|
+
clearInterval(alive);
|
|
4122
|
+
this.actionWaits.delete(controller);
|
|
4123
|
+
}
|
|
4124
|
+
let allow = !verdict.denyOnly && live() && picked === "allow";
|
|
4125
|
+
if (!verdict.denyOnly && live() && verdict.budget && picked === ACTION_BUDGET_OPTION) {
|
|
4126
|
+
try {
|
|
4127
|
+
allow = verdict.budget.approve() && live();
|
|
4128
|
+
} catch {
|
|
4129
|
+
allow = false;
|
|
4130
|
+
}
|
|
4131
|
+
}
|
|
4132
|
+
answer(allow);
|
|
4133
|
+
})();
|
|
4134
|
+
return true;
|
|
4135
|
+
}
|
|
4136
|
+
adopt(link, threadId, fresh = false) {
|
|
4137
|
+
if (typeof threadId !== "string" || !threadId || link !== this.claimedTui || link.tui.readyState !== WebSocket.OPEN || link.up.readyState !== WebSocket.OPEN)
|
|
4138
|
+
return;
|
|
4139
|
+
this.abortActionWaits();
|
|
4140
|
+
this.abandonSteers("thread changed");
|
|
4141
|
+
for (const [id, pending] of this.pending)
|
|
4142
|
+
if (pending.kind === "deliver") {
|
|
4143
|
+
pending.invalidate?.("Codex native thread changed before acceptance was confirmed");
|
|
4144
|
+
this.pending.delete(id);
|
|
4145
|
+
}
|
|
4146
|
+
for (const ids of this.turnDeliveries.values())
|
|
4147
|
+
for (const id of ids)
|
|
4148
|
+
this.delivery({ id, state: "needs_review", reason: "Codex native thread changed" });
|
|
4149
|
+
for (const id of this.unboundDeliveries)
|
|
4150
|
+
this.delivery({ id, state: "needs_review", reason: "Codex native thread changed" });
|
|
4151
|
+
this.turnDeliveries.clear();
|
|
4152
|
+
this.unboundDeliveries.clear();
|
|
4153
|
+
this.tokenTotal = undefined;
|
|
4154
|
+
this.tokenKinds = undefined;
|
|
4155
|
+
this.freshThread = fresh;
|
|
4156
|
+
this.link = link;
|
|
4157
|
+
this.threadId = threadId;
|
|
4158
|
+
this.contextEpoch++;
|
|
4159
|
+
this.deliveryEpoch++;
|
|
4160
|
+
this.retiredTurns.clear();
|
|
4161
|
+
this.unidentifiedCancellation = false;
|
|
4162
|
+
this.activeTurns.clear();
|
|
4163
|
+
this.unboundAccepted = false;
|
|
4164
|
+
this.forgetTurn();
|
|
4165
|
+
this.opts.log?.(`[${this.id}] thread ${threadId}`);
|
|
4166
|
+
this.opts.onThread?.(threadId, fresh);
|
|
4167
|
+
this.setState("idle");
|
|
4168
|
+
this.readUsage();
|
|
4169
|
+
clearInterval(this.usageTimer);
|
|
4170
|
+
this.usageTimer = setInterval(() => this.readUsage(), this.opts.usagePollMs ?? 600000);
|
|
4171
|
+
this.usageTimer.unref?.();
|
|
4172
|
+
}
|
|
4173
|
+
revert(turnId) {
|
|
4174
|
+
const link = this.link;
|
|
4175
|
+
if (this.state !== "idle" || !link || link.up.readyState !== WebSocket.OPEN)
|
|
4176
|
+
return Promise.reject(new Error(`${this.id} is not idle`));
|
|
4177
|
+
const id = this.nextId--;
|
|
4178
|
+
return new Promise((resolve, reject) => {
|
|
4179
|
+
const timer = setTimeout(() => this.pending.delete(id) && reject(new Error("thread/revert got no answer")), 30000);
|
|
4180
|
+
timer.unref?.();
|
|
4181
|
+
this.pending.set(id, { resolve: () => (clearTimeout(timer), resolve()), reject: (e) => (clearTimeout(timer), reject(e)) });
|
|
4182
|
+
link.up.send(JSON.stringify({ method: "thread/revert", id, params: { threadId: this.threadId, beforeTurnId: turnId } }));
|
|
4183
|
+
});
|
|
4184
|
+
}
|
|
4185
|
+
readUsage() {
|
|
4186
|
+
const link = this.link;
|
|
4187
|
+
if (!this.opts.onUsage || !link || link.up.readyState !== WebSocket.OPEN)
|
|
4188
|
+
return;
|
|
4189
|
+
const id = this.nextId--;
|
|
4190
|
+
this.pending.set(id, { resolve: (result) => this.opts.onUsage?.(result?.rateLimits, false), reject: () => {} });
|
|
4191
|
+
link.up.send(JSON.stringify({ method: "account/rateLimits/read", id }));
|
|
4192
|
+
setTimeout(() => this.pending.delete(id), 30000).unref?.();
|
|
4193
|
+
}
|
|
4194
|
+
onNotification(link, method, params) {
|
|
4195
|
+
if (link === this.link && method === "account/rateLimits/updated")
|
|
4196
|
+
return void this.opts.onUsage?.(params.rateLimits, false);
|
|
4197
|
+
if (link === this.link && method === "error" && params.error?.codexErrorInfo === "usageLimitExceeded") {
|
|
4198
|
+
this.opts.onUsage?.({ rateLimitReachedType: "usageLimitExceeded" }, true);
|
|
4124
4199
|
}
|
|
4125
|
-
if (
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4200
|
+
if (link !== this.link || params.threadId !== this.threadId)
|
|
4201
|
+
return;
|
|
4202
|
+
const nativeId = method === "turn/started" || method === "turn/completed" ? params.turn?.id : params.turnId;
|
|
4203
|
+
if (typeof nativeId === "string" && this.retiredTurns.has(nativeId)) {
|
|
4204
|
+
if (method === "turn/completed")
|
|
4205
|
+
this.retiredTurns.delete(nativeId);
|
|
4206
|
+
return;
|
|
4130
4207
|
}
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
const
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
const budgetOption = gate?.budget && once ? { optionId: ACTION_BUDGET_OPTION, name: `Allow ${gate.budget.label} for this session`, kind: "allow_once" } : undefined;
|
|
4140
|
-
if (budgetOption)
|
|
4141
|
-
options = [...options, budgetOption];
|
|
4142
|
-
const tool = typeof named === "string" && /^[\w.:@-]{1,80}$/.test(named) ? named : undefined;
|
|
4143
|
-
const turn = this.turn;
|
|
4144
|
-
const alive = setInterval(() => this.state === "busy" && turn === this.turn && this.touch(), Math.max(10, Math.min(30000, Math.floor(this.watchdogMs / 3))));
|
|
4145
|
-
const controller = new AbortController;
|
|
4146
|
-
this.permissionWaits.add(controller);
|
|
4147
|
-
let picked;
|
|
4148
|
-
try {
|
|
4149
|
-
const cancelled = new Promise((resolve) => controller.signal.addEventListener("abort", () => resolve(undefined), { once: true }));
|
|
4150
|
-
picked = await Promise.race([this.opts.onPermission?.({ peer: this.id, title, options, ...tool ? { tool } : {}, ...kind ? { kind } : {}, ...gate ? { classed: true } : {} }, controller.signal).catch(() => {
|
|
4208
|
+
if (this.unidentifiedCancellation)
|
|
4209
|
+
return;
|
|
4210
|
+
const unknownTurn = () => [...this.activeTurns].some((id) => id.startsWith("unknown:"));
|
|
4211
|
+
if (method === "thread/tokenUsage/updated") {
|
|
4212
|
+
const reader = this.opts.contextReader;
|
|
4213
|
+
this.opts.onContext?.(reader ? reader.read(params.tokenUsage, this.threadId, Date.now()) : codexContext(params.tokenUsage, this.threadId, Date.now()));
|
|
4214
|
+
const total = Number(params.tokenUsage?.total?.totalTokens);
|
|
4215
|
+
if (!Number.isFinite(total))
|
|
4151
4216
|
return;
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4217
|
+
const previous = this.tokenKinds;
|
|
4218
|
+
const kinds = {
|
|
4219
|
+
input: counter(params.tokenUsage?.total?.inputTokens),
|
|
4220
|
+
cachedInput: counter(params.tokenUsage?.total?.cachedInputTokens),
|
|
4221
|
+
output: counter(params.tokenUsage?.total?.outputTokens),
|
|
4222
|
+
reasoning: counter(params.tokenUsage?.total?.reasoningOutputTokens)
|
|
4223
|
+
};
|
|
4224
|
+
const added = this.tokenTotal === undefined ? this.freshThread ? total : 0 : Math.max(0, total - this.tokenTotal);
|
|
4225
|
+
const base = previous ?? (this.freshThread && this.tokenTotal === undefined ? { input: 0, cachedInput: 0, output: 0, reasoning: 0 } : undefined);
|
|
4226
|
+
this.tokenTotal = total;
|
|
4227
|
+
this.tokenKinds = kinds;
|
|
4228
|
+
if (added > 0)
|
|
4229
|
+
this.opts.onTokens?.(added);
|
|
4230
|
+
if (added > 0 && base)
|
|
4231
|
+
this.opts.onTokenBreakdown?.({
|
|
4232
|
+
input: Math.max(0, kinds.input - base.input),
|
|
4233
|
+
cachedInput: Math.max(0, kinds.cachedInput - base.cachedInput),
|
|
4234
|
+
output: Math.max(0, kinds.output - base.output),
|
|
4235
|
+
reasoning: Math.max(0, kinds.reasoning - base.reasoning),
|
|
4236
|
+
total: added
|
|
4237
|
+
});
|
|
4238
|
+
return;
|
|
4156
4239
|
}
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4240
|
+
if (method === "turn/started") {
|
|
4241
|
+
const nativeTurn = params.turn?.id ?? `unknown:${Date.now()}`;
|
|
4242
|
+
this.unboundAccepted = false;
|
|
4243
|
+
this.activeTurns.add(nativeTurn);
|
|
4244
|
+
for (const id of [...this.unboundDeliveries])
|
|
4245
|
+
this.addTurnDelivery(nativeTurn, id);
|
|
4246
|
+
this.lastAnswer = "";
|
|
4247
|
+
this.setState("busy");
|
|
4248
|
+
if (params.turn?.id)
|
|
4249
|
+
this.opts.onTurn?.(params.turn.id);
|
|
4250
|
+
} else if (method === "item/agentMessage/delta") {
|
|
4251
|
+
if (!this.activeTurns.size || typeof params.turnId === "string" && !this.activeTurns.has(params.turnId) && !unknownTurn())
|
|
4252
|
+
return;
|
|
4253
|
+
const buf = this.deltas.get(params.itemId) ?? [];
|
|
4254
|
+
buf.push(params.delta);
|
|
4255
|
+
this.deltas.set(params.itemId, buf);
|
|
4256
|
+
} else if (method === "item/completed" && ["fileChange", "commandExecution", "mcpToolCall", "userMessage"].includes(params.item?.type)) {
|
|
4257
|
+
if (typeof params.turnId === "string" && !this.activeTurns.has(params.turnId) && !unknownTurn())
|
|
4258
|
+
return;
|
|
4161
4259
|
try {
|
|
4162
|
-
|
|
4163
|
-
} catch {
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4260
|
+
this.opts.onItem?.(params.item, typeof params.turnId === "string" && this.activeTurns.has(params.turnId) ? params.turnId : undefined);
|
|
4261
|
+
} catch (error) {
|
|
4262
|
+
this.opts.log?.(`[${this.id}] item handler failed: ${error.message}`);
|
|
4263
|
+
}
|
|
4264
|
+
} else if (method === "item/completed" && params.item?.type === "agentMessage") {
|
|
4265
|
+
if (!this.activeTurns.size || typeof params.turnId === "string" && !this.activeTurns.has(params.turnId) && !unknownTurn())
|
|
4266
|
+
return;
|
|
4267
|
+
const item = params.item;
|
|
4268
|
+
const text = item.text ?? item.content?.map((c) => c.text ?? "").join("") ?? this.deltas.get(item.id)?.join("") ?? "";
|
|
4269
|
+
this.deltas.delete(item.id);
|
|
4270
|
+
if (item.phase !== "commentary" && text.trim())
|
|
4271
|
+
this.lastAnswer = text.trim();
|
|
4272
|
+
} else if (method === "turn/completed") {
|
|
4273
|
+
const nativeTurn = params.turn?.id;
|
|
4274
|
+
this.abortActionWaits(link, typeof nativeTurn === "string" ? nativeTurn : undefined);
|
|
4275
|
+
if (!this.activeTurns.size || typeof nativeTurn === "string" && !this.activeTurns.has(nativeTurn) && ![...this.activeTurns].some((id) => id.startsWith("unknown:")))
|
|
4276
|
+
return;
|
|
4277
|
+
this.activeTurns.delete(nativeTurn);
|
|
4278
|
+
for (const id of this.activeTurns)
|
|
4279
|
+
if (id.startsWith("unknown:"))
|
|
4280
|
+
this.activeTurns.delete(id);
|
|
4281
|
+
if (params.turn?.status === "failed") {
|
|
4282
|
+
this.opts.log?.(`[${this.id}] turn failed: ${params.turn.error?.message ?? "unknown error"}`);
|
|
4283
|
+
}
|
|
4284
|
+
this.endSteers(nativeTurn);
|
|
4285
|
+
if (this.activeTurns.size)
|
|
4286
|
+
return;
|
|
4287
|
+
if (typeof nativeTurn === "string") {
|
|
4288
|
+
for (const id of this.turnDeliveries.get(nativeTurn) ?? [])
|
|
4289
|
+
this.delivery({ id, state: params.turn?.status === "failed" ? "needs_review" : "completed", ...params.turn?.status === "failed" ? { reason: params.turn?.error?.message ?? "Codex turn failed" } : {} });
|
|
4290
|
+
this.turnDeliveries.delete(nativeTurn);
|
|
4291
|
+
}
|
|
4292
|
+
const inReplyTo = this.injected;
|
|
4293
|
+
const to = replyAudience(this.answering);
|
|
4294
|
+
this.injected = undefined;
|
|
4295
|
+
this.answering = [];
|
|
4296
|
+
this.deltas.clear();
|
|
4297
|
+
if (this.lastAnswer)
|
|
4298
|
+
this.onMessage?.(this.lastAnswer, inReplyTo ? { inReplyTo, to } : {});
|
|
4299
|
+
this.lastAnswer = "";
|
|
4300
|
+
this.setState("idle");
|
|
4168
4301
|
}
|
|
4169
|
-
if (turn === this.turn)
|
|
4170
|
-
this.emitActivity("answered", `${String(msg.id)}: ${valid ? options.find((o) => o.optionId === picked)?.kind : "cancelled"}`);
|
|
4171
|
-
this.send({
|
|
4172
|
-
jsonrpc: "2.0",
|
|
4173
|
-
id: msg.id,
|
|
4174
|
-
result: { outcome: valid ? { outcome: "selected", optionId: answer } : { outcome: "cancelled" } }
|
|
4175
|
-
});
|
|
4176
|
-
}
|
|
4177
|
-
request(method, params, timeoutMs) {
|
|
4178
|
-
const id = this.nextId++;
|
|
4179
|
-
return new Promise((resolve, reject) => {
|
|
4180
|
-
const timer = timeoutMs === undefined ? undefined : setTimeout(() => {
|
|
4181
|
-
this.pending.delete(id);
|
|
4182
|
-
reject(new RpcTimeoutError(`${method} did not answer within ${timeoutMs / 1000} s`));
|
|
4183
|
-
}, timeoutMs);
|
|
4184
|
-
timer?.unref();
|
|
4185
|
-
this.pending.set(id, {
|
|
4186
|
-
resolve: (value) => {
|
|
4187
|
-
clearTimeout(timer);
|
|
4188
|
-
resolve(value);
|
|
4189
|
-
},
|
|
4190
|
-
reject: (error) => {
|
|
4191
|
-
clearTimeout(timer);
|
|
4192
|
-
reject(error);
|
|
4193
|
-
}
|
|
4194
|
-
});
|
|
4195
|
-
this.send({ jsonrpc: "2.0", id, method, params });
|
|
4196
|
-
});
|
|
4197
|
-
}
|
|
4198
|
-
notify(method, params) {
|
|
4199
|
-
this.send({ jsonrpc: "2.0", method, params });
|
|
4200
|
-
}
|
|
4201
|
-
send(msg) {
|
|
4202
|
-
if (this.proc?.stdin.writable)
|
|
4203
|
-
this.proc.stdin.write(`${JSON.stringify(msg)}
|
|
4204
|
-
`);
|
|
4205
4302
|
}
|
|
4206
4303
|
};
|
|
4207
4304
|
});
|
|
4208
4305
|
|
|
4209
4306
|
// src/adapters/jcode.ts
|
|
4210
|
-
import { spawn as spawn7, spawnSync as
|
|
4307
|
+
import { spawn as spawn7, spawnSync as spawnSync2 } from "child_process";
|
|
4211
4308
|
import { createConnection } from "net";
|
|
4212
4309
|
import { closeSync as closeSync3, constants as constants2, existsSync as existsSync4, fstatSync as fstatSync3, lstatSync as lstatSync6, mkdtempSync as mkdtempSync3, openSync as openSync3, readSync as readSync2, rmSync as rmSync3 } from "fs";
|
|
4213
4310
|
import { tmpdir as tmpdir3 } from "os";
|
|
@@ -4954,12 +5051,12 @@ var init_deny = __esm(() => {
|
|
|
4954
5051
|
});
|
|
4955
5052
|
|
|
4956
5053
|
// src/local/sandbox.ts
|
|
4957
|
-
import { spawn as spawn8, spawnSync as
|
|
5054
|
+
import { spawn as spawn8, spawnSync as spawnSync3 } from "child_process";
|
|
4958
5055
|
import { existsSync as existsSync6, mkdtempSync as mkdtempSync4, readdirSync, rmSync as rmSync5 } from "fs";
|
|
4959
5056
|
import { homedir as homedir4, tmpdir as tmpdir4 } from "os";
|
|
4960
5057
|
import { dirname as dirname9, join as join11, resolve as resolve9 } from "path";
|
|
4961
5058
|
function developerDir() {
|
|
4962
|
-
const out =
|
|
5059
|
+
const out = spawnSync3("xcode-select", ["-p"], { encoding: "utf8" });
|
|
4963
5060
|
let dir = out.status === 0 ? out.stdout.trim() : "";
|
|
4964
5061
|
try {
|
|
4965
5062
|
dir = dir && realPath(dir);
|
|
@@ -5085,7 +5182,7 @@ function sandboxedExec(argv, opts) {
|
|
|
5085
5182
|
rmSync5(own, { recursive: true, force: true });
|
|
5086
5183
|
} catch {
|
|
5087
5184
|
try {
|
|
5088
|
-
|
|
5185
|
+
spawnSync3("chflags", ["-R", "nouchg", own]);
|
|
5089
5186
|
rmSync5(own, { recursive: true, force: true });
|
|
5090
5187
|
} catch {}
|
|
5091
5188
|
}
|
|
@@ -5215,7 +5312,7 @@ async function runTool(name, rawArgs, ctx) {
|
|
|
5215
5312
|
const approvedTarget = file;
|
|
5216
5313
|
const content = String(a.content ?? "");
|
|
5217
5314
|
const refused = approvalError(await ctx.permit(`write ${a.path} (${content.length} chars):
|
|
5218
|
-
${preview(content)}`, "write", ctx.signal, file, undefined, String(a.path)), "error: the user did not approve this write");
|
|
5315
|
+
${preview(content)}`, "write", ctx.signal, file, undefined, String(a.path), a), "error: the user did not approve this write");
|
|
5219
5316
|
if (refused)
|
|
5220
5317
|
return refused;
|
|
5221
5318
|
if (ctx.signal?.aborted)
|
|
@@ -5236,7 +5333,7 @@ ${preview(content)}`, "write", ctx.signal, file, undefined, String(a.path)), "er
|
|
|
5236
5333
|
return `error: \`old\` must match exactly once, it matched ${count} times`;
|
|
5237
5334
|
const refused = approvalError(await ctx.permit(`edit ${a.path}:
|
|
5238
5335
|
- ${preview(String(a.old), 600)}
|
|
5239
|
-
+ ${preview(String(a.new ?? ""), 600)}`, "edit", ctx.signal, file, undefined, String(a.path)), "error: the user did not approve this edit");
|
|
5336
|
+
+ ${preview(String(a.new ?? ""), 600)}`, "edit", ctx.signal, file, undefined, String(a.path), a), "error: the user did not approve this edit");
|
|
5240
5337
|
if (refused)
|
|
5241
5338
|
return refused;
|
|
5242
5339
|
if (ctx.signal?.aborted)
|
|
@@ -5257,7 +5354,7 @@ ${preview(content)}`, "write", ctx.signal, file, undefined, String(a.path)), "er
|
|
|
5257
5354
|
return "error: empty command";
|
|
5258
5355
|
if (command.length > 4000)
|
|
5259
5356
|
return "error: command longer than 4000 characters; put it in a script file with write, then run that";
|
|
5260
|
-
const refused = approvalError(await ctx.permit(`bash: ${command}`, "bash", ctx.signal), "error: the user did not approve this command");
|
|
5357
|
+
const refused = approvalError(await ctx.permit(`bash: ${command}`, "bash", ctx.signal, undefined, undefined, undefined, a), "error: the user did not approve this command");
|
|
5261
5358
|
if (refused)
|
|
5262
5359
|
return refused;
|
|
5263
5360
|
const res = await sandboxedExec(["/bin/bash", "-c", command], { cwd: ctx.cwd, profile: ctx.sandboxProfile, readOnlyData: !!ctx.readOnly?.length, timeoutMs: (Number(a.timeout_s) || 120) * 1000, ...ctx.sandboxEnv ? { env: ctx.sandboxEnv } : {}, ...ctx.signal ? { signal: ctx.signal } : {} });
|
|
@@ -5276,7 +5373,7 @@ ${preview(content)}`, "write", ctx.signal, file, undefined, String(a.path)), "er
|
|
|
5276
5373
|
if (problem)
|
|
5277
5374
|
return `error: git ${sub}: ${problem}`;
|
|
5278
5375
|
if (GIT_WRITE.has(sub)) {
|
|
5279
|
-
const refused = approvalError(await ctx.permit(`git ${args.join(" ")}`, "git", ctx.signal), "error: the user did not approve this git command");
|
|
5376
|
+
const refused = approvalError(await ctx.permit(`git ${args.join(" ")}`, "git", ctx.signal, undefined, undefined, undefined, a), "error: the user did not approve this git command");
|
|
5280
5377
|
if (refused)
|
|
5281
5378
|
return refused;
|
|
5282
5379
|
}
|
|
@@ -8315,7 +8412,9 @@ var init_task_code_symbols = __esm(() => {
|
|
|
8315
8412
|
});
|
|
8316
8413
|
|
|
8317
8414
|
// src/hub/planning.ts
|
|
8318
|
-
var init_planning = () => {
|
|
8415
|
+
var init_planning = __esm(() => {
|
|
8416
|
+
init_sdlc();
|
|
8417
|
+
});
|
|
8319
8418
|
// src/hub/deliverables.ts
|
|
8320
8419
|
var DELIVERABLE_LIMITS;
|
|
8321
8420
|
var init_deliverables = __esm(() => {
|
|
@@ -9106,7 +9205,7 @@ var init_hub_tools = __esm(() => {
|
|
|
9106
9205
|
tool("hub_check_cancel", "Cancel your task-bound isolated check; only its owner or the person may cancel.", { run: str2 }, ["run"]),
|
|
9107
9206
|
tool("hub_delivery_retry", "Request one redelivery of your own needs_review delivery. An earlier attempt may have effects: inspect those effects before acting. Refuses another peer's row or any lineage already explicitly retried; a repeated uncertain row needs the person. Never completes/discards a delivery or changes task state.", { delivery_id: { type: "string", minLength: 1, maxLength: 256 } }, ["delivery_id"]),
|
|
9108
9207
|
tool("hub_backlog_list", "Read public backlog cards with planning revisions. Configured planners only; PII remains a stub.", {}),
|
|
9109
|
-
tool("hub_backlog_plan", "Plan an unpinned backlog item outside frozen releases with the revision you read and a bounded reason. A person's rank is retained. Actual dates cannot be set.", { id, revision: { type: "integer", minimum: 0 }, reason: { type: "string", maxLength: 1000 }, fields: { type: "object", properties: { rank: { type: ["integer", "null"], minimum: 0 }, priority: { type: ["integer", "null"], minimum: 0, maximum: 3 }, release: { type: ["integer", "null"], minimum: 1 }, plannedStart: { type: ["integer", "null"], minimum: 1 }, due: { type: ["integer", "null"], minimum: 1 }, deps: { type: "array", maxItems: 64, uniqueItems: true, items: { type: "integer", minimum: 1 } }, reserved: { type: ["string", "null"], minLength: 1, maxLength: 32 } }, additionalProperties: false } }, ["id", "revision", "reason"
|
|
9208
|
+
tool("hub_backlog_plan", "Plan an unpinned backlog item outside frozen releases with the revision you read and a bounded reason. Omit fields or give {} for a note-only revision. fields.sdlc accepts only repo/issue identity on unstarted backlog work; relinking clears old spec acceptance. A person's rank is retained. Actual dates cannot be set.", { id, revision: { type: "integer", minimum: 0 }, reason: { type: "string", maxLength: 1000 }, fields: { type: "object", properties: { sdlc: { type: "object", minProperties: 1, properties: { repo: { type: ["string", "null"], maxLength: 400 }, issue: { type: ["integer", "null"], minimum: 1 } }, additionalProperties: false }, rank: { type: ["integer", "null"], minimum: 0 }, priority: { type: ["integer", "null"], minimum: 0, maximum: 3 }, release: { type: ["integer", "null"], minimum: 1 }, plannedStart: { type: ["integer", "null"], minimum: 1 }, due: { type: ["integer", "null"], minimum: 1 }, deps: { type: "array", maxItems: 64, uniqueItems: true, items: { type: "integer", minimum: 1 } }, reserved: { type: ["string", "null"], minLength: 1, maxLength: 32 } }, additionalProperties: false } }, ["id", "revision", "reason"]),
|
|
9110
9209
|
tool("hub_backlog_pull", "Reserve a WIP slot and pull ready unpinned backlog work into To Do at its current revision. Preserves person order/reservations and frozen scope; pending unowned work can be undone with hub_backlog_undo.", { id, revision: { type: "integer", minimum: 0 }, reason: { type: "string", minLength: 1, maxLength: 1000 } }, ["id", "revision", "reason"]),
|
|
9111
9210
|
tool("hub_backlog_split", "Split unstarted backlog work into two to eight staged parts. The original card retains integration/acceptance and depends on the children. Use after for existing prerequisite ids and afterParts for earlier zero-based part indexes. Latest unused unchanged children can be undone with hub_backlog_undo.", { id, revision: { type: "integer", minimum: 0 }, reason: { type: "string", minLength: 1, maxLength: 1000 }, parts: { type: "array", minItems: 2, maxItems: 8, items: { type: "object", properties: { title: { type: "string", minLength: 1, maxLength: 300 }, detail: { type: "string", maxLength: 8000 }, class: str2, refs, plan, after: { type: "array", maxItems: 64, uniqueItems: true, items: { type: "integer", minimum: 1 } }, afterParts: { type: "array", maxItems: 8, uniqueItems: true, items: { type: "integer", minimum: 0 } } }, required: ["title"], additionalProperties: false } } }, ["id", "revision", "reason", "parts"]),
|
|
9112
9211
|
tool("hub_backlog_undo", "Undo your latest planning change at its current revision. Refuses intervening changes and another actor's edit.", { id, revision: { type: "integer", minimum: 1 } }, ["id", "revision"]),
|
|
@@ -9127,9 +9226,9 @@ var init_hub_tools = __esm(() => {
|
|
|
9127
9226
|
tool("hub_checkpoint", "Answer a checkpoint request from the hub (quota or context window pressure): what you were doing, what is half done, what whoever continues must know. Write the same to .agenthub/checkpoint.md first if you can.", { summary: str2, request_id: { type: "string", description: "The request id supplied by a context checkpoint request; required for context checkpoints" } }, ["summary"]),
|
|
9128
9227
|
tool("hub_memory_search", "Search daemon-scoped visible memory, fails first. Query <=200 UTF-8 bytes; default 8, max 20 lines; whole output <=4 KB. Incomplete excerpts are marked. No project argument.", { query: { type: "string", maxLength: 200, description: "At most 200 UTF-8 bytes, enforced by the daemon" }, limit: { type: "integer", minimum: 1, maximum: 20 } }, ["query"]),
|
|
9129
9228
|
tool("hub_memory_show", "Read up to 8 visible memory ids or unique prefixes (6..64 UTF-8 bytes each). Original ids redirect; unavailable ids share one refusal. Whole output <=8 KB.", { ids: { type: "array", minItems: 1, maxItems: 8, items: { type: "string", minLength: 6, maxLength: 64, description: "6 to 64 UTF-8 bytes" } } }, ["ids"]),
|
|
9130
|
-
tool("hub_memory_graph", "Read screened memory neighbourhoods, fails first. Up to 8 starts (<=200 UTF-8 bytes each), 1 or 2 hops; default 60/max 120 nodes, max 240 edges, whole output <=6 KB. No project argument.", { starts: { type: "array", minItems: 1, maxItems: 8, items: { type: "string", minLength: 1, maxLength: 200, description: "At most 200 UTF-8 bytes" } }, hops: { type: "integer", enum: [1, 2] }, limit: { type: "integer", minimum: 1, maximum: 120 } }, ["starts"]),
|
|
9229
|
+
tool("hub_memory_graph", "Read screened memory neighbourhoods, fails first. Symbol starts also include optional current-head one-hop code neighbours as derived SHA-stamped edges; unknown coverage is explicit. Up to 8 starts (<=200 UTF-8 bytes each), 1 or 2 hops; default 60/max 120 nodes, max 240 edges, whole output <=6 KB. No project argument.", { starts: { type: "array", minItems: 1, maxItems: 8, items: { type: "string", minLength: 1, maxLength: 200, description: "At most 200 UTF-8 bytes" } }, hops: { type: "integer", enum: [1, 2] }, limit: { type: "integer", minimum: 1, maximum: 120 } }, ["starts"]),
|
|
9131
9230
|
tool("hub_memory_correct", "Propose a correction to a visible memory entry. This creates a machine-local candidate only; a person accepts or rejects it with amem review. It never edits a note. id is an exact id or unique visible prefix; text and reason must each fit 4000 UTF-8 bytes.", { id: str2, text: str2, reason: str2 }, ["id", "text", "reason"]),
|
|
9132
|
-
tool("hub_remember", "Save a decision, finding, contract or fail to the memory all agents share (the hub's
|
|
9231
|
+
tool("hub_remember", "Save a decision, finding, contract or fail to the memory all agents share (the hub's amem curated store); the other agents also get it with their next message. A fail is an approach you tried that does not work, and why: the most useful note, it stops the others spending their quota on it. Do not retry what a fail note rules out without new evidence. Conclusions worth recalling, not chatter. For kind fail, optional approach, symptom, cause, evidence and retry_when strings compose fixed labeled text; text may be omitted when structured fields are supplied. The complete text must fit 4000 UTF-8 bytes and is refused, never truncated, when oversized. A note on an open task is short-term until the task is approved, when its decisions, contracts and fails become long-term; horizon long keeps it now, scope shared lets the person's other projects find it.", { text: str2, approach: str2, symptom: str2, cause: str2, evidence: str2, retry_when: str2, title: str2, kind: { type: "string", enum: [...NOTE_KINDS] }, task: id, horizon: { type: "string", enum: ["short", "long"] }, scope: { type: "string", enum: ["project", "shared"] } })
|
|
9133
9232
|
];
|
|
9134
9233
|
PLANNER_REVIEW_TOOLS = [
|
|
9135
9234
|
tool("hub_planner_groom", "Read a bounded planner grooming digest of dependencies, repository-scoped overlaps, WIP and blockers. No board change; unavailable evidence stays unknown.", {}),
|
|
@@ -9407,6 +9506,7 @@ var init_local_worker = __esm(() => {
|
|
|
9407
9506
|
this.routePii = !!policy?.pii;
|
|
9408
9507
|
const ctx = this.toolContext(envs, turnSignal, !!policy?.pii, reply);
|
|
9409
9508
|
let usedTools = false;
|
|
9509
|
+
let captureInvocation = 0;
|
|
9410
9510
|
for (let step = 0;step < maxSteps; step++) {
|
|
9411
9511
|
this.elide(msgs);
|
|
9412
9512
|
const res = await this.call(msgs, policy, envs);
|
|
@@ -9455,7 +9555,7 @@ var init_local_worker = __esm(() => {
|
|
|
9455
9555
|
}
|
|
9456
9556
|
this.observeTool(call, output, policy, `${this.sessionId}.${turn}.${step}`);
|
|
9457
9557
|
if (name !== "hub_send" && !TASK_TOOL_NAMES.has(name) && !CONDUCTOR_TOOL_NAMES.has(name))
|
|
9458
|
-
capture?.observe({ tool: call.function.name, args: call.function.arguments, output, id: call.id, paths: touchedPaths(call.function.name, safeParse(call.function.arguments)) });
|
|
9558
|
+
capture?.observe({ tool: call.function.name, args: call.function.arguments, output, id: call.id, sourceId: `${this.sessionId}.${turn}.${step}.${captureInvocation++}`, paths: touchedPaths(call.function.name, safeParse(call.function.arguments)) });
|
|
9459
9559
|
}
|
|
9460
9560
|
} finally {
|
|
9461
9561
|
if (results.length)
|
|
@@ -9471,7 +9571,7 @@ var init_local_worker = __esm(() => {
|
|
|
9471
9571
|
cwd: this.opts.cwd,
|
|
9472
9572
|
deny: this.opts.tools.deny,
|
|
9473
9573
|
readOnly: this.opts.tools.readOnly,
|
|
9474
|
-
permit: async (title, tool, _signal, canonicalTarget, _observe, originalPath) => {
|
|
9574
|
+
permit: async (title, tool, _signal, canonicalTarget, _observe, originalPath, input) => {
|
|
9475
9575
|
if (turnSignal.aborted)
|
|
9476
9576
|
return "aborted";
|
|
9477
9577
|
const generation = this.turn;
|
|
@@ -9500,7 +9600,7 @@ var init_local_worker = __esm(() => {
|
|
|
9500
9600
|
};
|
|
9501
9601
|
const onAbort = () => finish("aborted");
|
|
9502
9602
|
turnSignal.addEventListener("abort", onAbort, { once: true });
|
|
9503
|
-
this.opts.tools.permit(title, tool, turnSignal, canonicalTarget, observe, originalPath).then(finish, (error) => {
|
|
9603
|
+
this.opts.tools.permit(title, tool, turnSignal, canonicalTarget, observe, originalPath, input).then(finish, (error) => {
|
|
9504
9604
|
turnSignal.removeEventListener("abort", onAbort);
|
|
9505
9605
|
reject(error);
|
|
9506
9606
|
});
|
|
@@ -10683,7 +10783,7 @@ var init_native_cli_entry_codex = () => {};
|
|
|
10683
10783
|
|
|
10684
10784
|
// src/adapters/definitions/native-cli-entry-kimi.ts
|
|
10685
10785
|
import { resolve as resolve13 } from "path";
|
|
10686
|
-
import { spawnSync as
|
|
10786
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
10687
10787
|
async function runKimiEntry(ctx) {
|
|
10688
10788
|
const peer = ctx.instance ?? "kimi";
|
|
10689
10789
|
const { args, cwd, stateDir, cliDir, unattendedEnv, projectConfig, connect, fail, takeFlags } = ctx;
|
|
@@ -10750,7 +10850,7 @@ async function runKimiEntry(ctx) {
|
|
|
10750
10850
|
const written = configureKimiMcp(cwd, stateDir, bundleInStatus(snapshot) ?? resolve13(cliDir, "../../plugins/agent-hub/server.js"), "tui");
|
|
10751
10851
|
if (written.tracked)
|
|
10752
10852
|
console.error(".kimi-code/mcp.json is tracked; the agent-hub entry contains machine paths. Review this local change before committing it.");
|
|
10753
|
-
result =
|
|
10853
|
+
result = spawnSync4(launch.cmd, launch.args, { cwd, stdio: "inherit", env: { ...nativeLaunchEnv("kimi", childEnv()), AGENTHUB_STATE_DIR: stateDir, AGENTHUB_PROJECT_DIR: cwd } });
|
|
10754
10854
|
} finally {
|
|
10755
10855
|
if (admission.admissionId)
|
|
10756
10856
|
await hub.request({ t: "kimi_launch_end", admissionId: admission.admissionId }).catch(() => {});
|
|
@@ -11959,7 +12059,7 @@ var init_builtin_metadata = __esm(() => {
|
|
|
11959
12059
|
permission: { grants: { ...hubGrants } },
|
|
11960
12060
|
session: { idField: "sessionId", currentId: sessionId("sessionId"), restoreOnStart: true, launchKind: "pi", launchRecord: true, ...PI_RECOVERY_SESSION, crashPlan: piCrashPlan, unrecordedTerminal: "tui", continuityBookmark: piRecoveryContinuity, continuity: { key: "pi", parse: parsePiContinuity } },
|
|
11961
12061
|
turnEnd: "adapter",
|
|
11962
|
-
budget: { account: "pi", instrumented: true },
|
|
12062
|
+
budget: { account: "pi", instrumented: true, noQuotaReason: "on-prem backends have no provider quota" },
|
|
11963
12063
|
reporting: { tokensNotReported: true },
|
|
11964
12064
|
configuration: { schema: piConfigSchema, routingSchema: piRoutingSchema, settings: PI_SETTINGS, restartReasons: PI_RESTART_REASONS, validateEffective: piEffectiveConfig },
|
|
11965
12065
|
routing: { localAllowedRefusal: "local_allowed = false also excludes pi for this class", longContext: "backend-limit", preferredTier: true, handoffPriority: 0, trace: "backend", backendLabel: "pi" },
|
|
@@ -11982,7 +12082,7 @@ var init_builtin_metadata = __esm(() => {
|
|
|
11982
12082
|
permission: { grants: { ...hubGrants } },
|
|
11983
12083
|
session: { idField: "sessionId", currentId: sessionId("sessionId"), freshOnRestore: true, launchKind: "local", ...LOCAL_RECOVERY_SESSION, crashPlan: localCrashPlan },
|
|
11984
12084
|
turnEnd: "adapter",
|
|
11985
|
-
budget: { account: "local", quotaExempt: true, instrumented: true },
|
|
12085
|
+
budget: { account: "local", quotaExempt: true, instrumented: true, noQuotaReason: "on-prem gateway has no provider quota" },
|
|
11986
12086
|
configuration: { schema: localConfigSchema, routingSchema: localRoutingSchema, restartReasons: LOCAL_RESTART_REASONS },
|
|
11987
12087
|
routing: { localAllowedRefusal: "local_allowed = false for this class", longContext: "skip-owner", preferredTier: true, handoffPriority: 1, trace: "route" },
|
|
11988
12088
|
cli: { command: "local", entry: { lifecycleAdmission: false, execute: runLocalEntry }, usage: [["ahub local [--route <id> | --model <id>]", "start the hub-native worker on the self-hosted models (routing.toml)"]], launchConfigKeys: ["local.deny", "local.bash_network", "local.network_allow", "local.max_steps", "local.read_allow", "routing.local"], startWords: () => ["local"] },
|
|
@@ -12123,7 +12223,7 @@ var init_builtin_metadata = __esm(() => {
|
|
|
12123
12223
|
permission: { modes: {}, grants: { ...hubGrants } },
|
|
12124
12224
|
session: { idField: "sessionId", currentId: sessionId("sessionId"), launchKind: "pi", launchRecord: true },
|
|
12125
12225
|
turnEnd: "adapter",
|
|
12126
|
-
budget: { account: "pi" },
|
|
12226
|
+
budget: { account: "pi", noQuotaReason: "on-prem backends have no provider quota" },
|
|
12127
12227
|
cli: { command: "pi", startWords: (form) => ["pi", "--mode", form] },
|
|
12128
12228
|
display: palette("#c79936")
|
|
12129
12229
|
},
|
|
@@ -12768,19 +12868,57 @@ var init_registry = __esm(() => {
|
|
|
12768
12868
|
}));
|
|
12769
12869
|
});
|
|
12770
12870
|
|
|
12871
|
+
// node_modules/@staix/agent-mem/src/memory/private-spans.ts
|
|
12872
|
+
var init_private_spans = () => {};
|
|
12873
|
+
|
|
12771
12874
|
// node_modules/@staix/agent-mem/src/memory/screen.ts
|
|
12772
12875
|
var init_screen = () => {};
|
|
12773
12876
|
|
|
12774
12877
|
// node_modules/@staix/agent-mem/src/memory/mutation.ts
|
|
12775
12878
|
var init_mutation = () => {};
|
|
12776
12879
|
|
|
12880
|
+
// node_modules/@staix/agent-mem/src/memory/project.ts
|
|
12881
|
+
var init_project2 = __esm(() => {
|
|
12882
|
+
init_platform();
|
|
12883
|
+
});
|
|
12884
|
+
|
|
12885
|
+
// node_modules/@staix/agent-mem/src/memory/policy.ts
|
|
12886
|
+
var MAX_BYTES3, MAX_STRING_BYTES, flags;
|
|
12887
|
+
var init_policy = __esm(() => {
|
|
12888
|
+
init_screen();
|
|
12889
|
+
init_platform();
|
|
12890
|
+
init_project2();
|
|
12891
|
+
MAX_BYTES3 = 256 * 1024;
|
|
12892
|
+
MAX_STRING_BYTES = 16 * 1024;
|
|
12893
|
+
flags = new Set(["private", "pii", "privateturn", "isprivate", "ispii"]);
|
|
12894
|
+
});
|
|
12895
|
+
|
|
12896
|
+
// node_modules/@staix/agent-mem/src/memory/config.ts
|
|
12897
|
+
var MAX_CONFIG_BYTES2;
|
|
12898
|
+
var init_config2 = __esm(() => {
|
|
12899
|
+
init_policy();
|
|
12900
|
+
MAX_CONFIG_BYTES2 = 64 * 1024;
|
|
12901
|
+
});
|
|
12902
|
+
|
|
12777
12903
|
// node_modules/@staix/agent-mem/src/memory/home.ts
|
|
12778
12904
|
var init_home = __esm(() => {
|
|
12779
12905
|
init_platform();
|
|
12906
|
+
init_config2();
|
|
12780
12907
|
});
|
|
12781
12908
|
|
|
12782
|
-
// node_modules/@staix/agent-mem/src/memory/
|
|
12783
|
-
var
|
|
12909
|
+
// node_modules/@staix/agent-mem/src/memory/machine.ts
|
|
12910
|
+
var init_machine = () => {};
|
|
12911
|
+
|
|
12912
|
+
// node_modules/@staix/agent-mem/src/memory/lifecycle-log.ts
|
|
12913
|
+
var MAX_LOG_BYTES;
|
|
12914
|
+
var init_lifecycle_log = __esm(() => {
|
|
12915
|
+
MAX_LOG_BYTES = 8 * 1024 * 1024;
|
|
12916
|
+
});
|
|
12917
|
+
|
|
12918
|
+
// node_modules/@staix/agent-mem/src/memory/aliases.ts
|
|
12919
|
+
var init_aliases = __esm(() => {
|
|
12920
|
+
init_lifecycle_log();
|
|
12921
|
+
});
|
|
12784
12922
|
|
|
12785
12923
|
// node_modules/@staix/agent-mem/src/memory/generation.ts
|
|
12786
12924
|
var init_generation = __esm(() => {
|
|
@@ -12791,16 +12929,41 @@ var init_generation = __esm(() => {
|
|
|
12791
12929
|
// node_modules/@staix/agent-mem/src/memory/shard.ts
|
|
12792
12930
|
var init_shard = __esm(() => {
|
|
12793
12931
|
init_generation();
|
|
12932
|
+
init_aliases();
|
|
12933
|
+
init_aliases();
|
|
12934
|
+
});
|
|
12935
|
+
|
|
12936
|
+
// node_modules/@staix/agent-mem/src/memory/lifecycle.ts
|
|
12937
|
+
var init_lifecycle = __esm(() => {
|
|
12938
|
+
init_aliases();
|
|
12939
|
+
init_lifecycle_log();
|
|
12940
|
+
init_shard();
|
|
12941
|
+
});
|
|
12942
|
+
|
|
12943
|
+
// node_modules/@staix/agent-mem/src/memory/search-fts.ts
|
|
12944
|
+
var init_search_fts = () => {};
|
|
12945
|
+
|
|
12946
|
+
// node_modules/@staix/agent-mem/src/memory/capture-input.ts
|
|
12947
|
+
var init_capture_input = __esm(() => {
|
|
12948
|
+
init_screen();
|
|
12949
|
+
init_platform();
|
|
12950
|
+
init_private_spans();
|
|
12794
12951
|
});
|
|
12795
12952
|
|
|
12796
12953
|
// node_modules/@staix/agent-mem/src/memory/capture.ts
|
|
12954
|
+
var SPOOL_MAX_BYTES;
|
|
12797
12955
|
var init_capture = __esm(() => {
|
|
12798
12956
|
init_screen();
|
|
12799
|
-
|
|
12957
|
+
init_capture_input();
|
|
12958
|
+
init_capture_input();
|
|
12800
12959
|
init_home();
|
|
12801
12960
|
init_shard();
|
|
12802
|
-
|
|
12961
|
+
init_lifecycle();
|
|
12962
|
+
init_machine();
|
|
12963
|
+
init_config2();
|
|
12964
|
+
init_policy();
|
|
12803
12965
|
init_search_fts();
|
|
12966
|
+
SPOOL_MAX_BYTES = 256 * 1024;
|
|
12804
12967
|
});
|
|
12805
12968
|
|
|
12806
12969
|
// node_modules/@staix/agent-mem/src/memory/ops-contract.ts
|
|
@@ -13027,9 +13190,13 @@ var init_reader_lease = __esm(() => {
|
|
|
13027
13190
|
var DEFAULT_TTL_MS, UNSETTLED_MS, DEFAULT_LEASE_MS, MIGRATION_BOOTSTRAP;
|
|
13028
13191
|
var init_store = __esm(() => {
|
|
13029
13192
|
init_platform();
|
|
13193
|
+
init_private_spans();
|
|
13030
13194
|
init_screen();
|
|
13031
13195
|
init_mutation();
|
|
13032
13196
|
init_home();
|
|
13197
|
+
init_machine();
|
|
13198
|
+
init_lifecycle();
|
|
13199
|
+
init_machine();
|
|
13033
13200
|
init_search_fts();
|
|
13034
13201
|
init_shard();
|
|
13035
13202
|
init_capture();
|
|
@@ -13039,10 +13206,97 @@ var init_store = __esm(() => {
|
|
|
13039
13206
|
init_bridge();
|
|
13040
13207
|
init_migrate();
|
|
13041
13208
|
init_reader_lease();
|
|
13042
|
-
DEFAULT_TTL_MS = 14 * 24 * 3600000;
|
|
13043
|
-
UNSETTLED_MS = 180 * 24 * 3600000;
|
|
13044
|
-
DEFAULT_LEASE_MS = 4 * 3600000;
|
|
13045
|
-
MIGRATION_BOOTSTRAP = Symbol("migration index bootstrap");
|
|
13209
|
+
DEFAULT_TTL_MS = 14 * 24 * 3600000;
|
|
13210
|
+
UNSETTLED_MS = 180 * 24 * 3600000;
|
|
13211
|
+
DEFAULT_LEASE_MS = 4 * 3600000;
|
|
13212
|
+
MIGRATION_BOOTSTRAP = Symbol("migration index bootstrap");
|
|
13213
|
+
});
|
|
13214
|
+
|
|
13215
|
+
// node_modules/@staix/agent-mem/src/memory/work-lists.ts
|
|
13216
|
+
var forbidden;
|
|
13217
|
+
var init_work_lists = __esm(() => {
|
|
13218
|
+
init_generation();
|
|
13219
|
+
init_mutation();
|
|
13220
|
+
init_platform();
|
|
13221
|
+
init_screen();
|
|
13222
|
+
init_private_spans();
|
|
13223
|
+
init_shard();
|
|
13224
|
+
init_machine();
|
|
13225
|
+
forbidden = new Set(["__proto__", "constructor", "prototype"]);
|
|
13226
|
+
});
|
|
13227
|
+
|
|
13228
|
+
// node_modules/@staix/agent-mem/src/memory/generation-sync.ts
|
|
13229
|
+
var zero;
|
|
13230
|
+
var init_generation_sync = __esm(() => {
|
|
13231
|
+
init_bridge();
|
|
13232
|
+
init_generation();
|
|
13233
|
+
init_mutation();
|
|
13234
|
+
init_platform();
|
|
13235
|
+
init_prune();
|
|
13236
|
+
init_sync();
|
|
13237
|
+
zero = "0".repeat(40);
|
|
13238
|
+
});
|
|
13239
|
+
|
|
13240
|
+
// node_modules/@staix/agent-mem/src/memory/sync-screen.ts
|
|
13241
|
+
var MAX_BYTES4, GENERATED;
|
|
13242
|
+
var init_sync_screen = __esm(() => {
|
|
13243
|
+
init_config2();
|
|
13244
|
+
init_policy();
|
|
13245
|
+
MAX_BYTES4 = 8 * 1024 * 1024;
|
|
13246
|
+
GENERATED = new Set(["id", "schema", "op", "at", "machine", "actor", "target", "promotedFrom", "supersedes", "expires", "by", "tier", "scope", "kind", "source"]);
|
|
13247
|
+
});
|
|
13248
|
+
|
|
13249
|
+
// node_modules/@staix/agent-mem/src/memory/sync.ts
|
|
13250
|
+
var liveGroups;
|
|
13251
|
+
var init_sync = __esm(() => {
|
|
13252
|
+
init_platform();
|
|
13253
|
+
init_mutation();
|
|
13254
|
+
init_generation_sync();
|
|
13255
|
+
init_sync_screen();
|
|
13256
|
+
liveGroups = new Set;
|
|
13257
|
+
});
|
|
13258
|
+
|
|
13259
|
+
// node_modules/@staix/agent-mem/src/memory/candidate-pending.ts
|
|
13260
|
+
var CANDIDATE_AGE_MS, CANDIDATE_SHOWN_GRACE_MS, CANDIDATE_DUE_MS;
|
|
13261
|
+
var init_candidate_pending = __esm(() => {
|
|
13262
|
+
CANDIDATE_AGE_MS = 30 * 86400000;
|
|
13263
|
+
CANDIDATE_SHOWN_GRACE_MS = 7 * 86400000;
|
|
13264
|
+
CANDIDATE_DUE_MS = 7 * 86400000;
|
|
13265
|
+
});
|
|
13266
|
+
|
|
13267
|
+
// node_modules/@staix/agent-mem/src/memory/path-glob.ts
|
|
13268
|
+
var init_path_glob = __esm(() => {
|
|
13269
|
+
init_project2();
|
|
13270
|
+
});
|
|
13271
|
+
|
|
13272
|
+
// node_modules/@staix/agent-mem/src/memory/repo-scope.ts
|
|
13273
|
+
var REPO_DIR = ".amem", REPO_LOG = "log.jsonl", REPO_ATTRIBUTES_LINE, MAX_LINE_BYTES, MAX_FILE_BYTES, MAX_CONFIG_BYTES3;
|
|
13274
|
+
var init_repo_scope = __esm(() => {
|
|
13275
|
+
init_platform();
|
|
13276
|
+
init_policy();
|
|
13277
|
+
init_project2();
|
|
13278
|
+
init_path_glob();
|
|
13279
|
+
init_shard();
|
|
13280
|
+
REPO_ATTRIBUTES_LINE = `${REPO_DIR}/${REPO_LOG} merge=union`;
|
|
13281
|
+
MAX_LINE_BYTES = 16 * 1024;
|
|
13282
|
+
MAX_FILE_BYTES = 1024 * 1024;
|
|
13283
|
+
MAX_CONFIG_BYTES3 = 1024 * 1024;
|
|
13284
|
+
});
|
|
13285
|
+
|
|
13286
|
+
// node_modules/@staix/agent-mem/src/session-brief.ts
|
|
13287
|
+
var init_session_brief = __esm(() => {
|
|
13288
|
+
init_generation();
|
|
13289
|
+
init_home();
|
|
13290
|
+
init_platform();
|
|
13291
|
+
init_policy();
|
|
13292
|
+
init_config2();
|
|
13293
|
+
init_candidate_pending();
|
|
13294
|
+
init_project2();
|
|
13295
|
+
init_reader_lease();
|
|
13296
|
+
init_shard();
|
|
13297
|
+
init_lifecycle();
|
|
13298
|
+
init_repo_scope();
|
|
13299
|
+
init_work_lists();
|
|
13046
13300
|
});
|
|
13047
13301
|
|
|
13048
13302
|
// node_modules/ajv/dist/compile/codegen/code.js
|
|
@@ -20263,95 +20517,9 @@ init_registry();
|
|
|
20263
20517
|
init_hub_tools();
|
|
20264
20518
|
init_envelope();
|
|
20265
20519
|
|
|
20266
|
-
// src/hub/
|
|
20267
|
-
|
|
20268
|
-
|
|
20269
|
-
init_plan_conformance();
|
|
20270
|
-
|
|
20271
|
-
// src/hub/sdlc-release.ts
|
|
20272
|
-
init_child_process();
|
|
20273
|
-
init_git();
|
|
20274
|
-
|
|
20275
|
-
// src/hub/tasks.ts
|
|
20276
|
-
init_spec_read();
|
|
20277
|
-
init_sdlc_metrics();
|
|
20278
|
-
// package.json
|
|
20279
|
-
var package_default = {
|
|
20280
|
-
name: "@staix/agent-hub",
|
|
20281
|
-
version: "0.15.0",
|
|
20282
|
-
description: "Native multi-agent hub: Claude Code, Codex, Kimi Code, Pi and local inference as peers in one project",
|
|
20283
|
-
license: "MIT",
|
|
20284
|
-
type: "module",
|
|
20285
|
-
bin: {
|
|
20286
|
-
ahub: "src/cli/main.js",
|
|
20287
|
-
"agent-hub": "src/cli/main.js"
|
|
20288
|
-
},
|
|
20289
|
-
scripts: {
|
|
20290
|
-
check: "scripts/check.sh",
|
|
20291
|
-
build: "bun scripts/build.mjs",
|
|
20292
|
-
test: "bun test"
|
|
20293
|
-
},
|
|
20294
|
-
engines: {
|
|
20295
|
-
bun: ">=1.4.2"
|
|
20296
|
-
},
|
|
20297
|
-
dependencies: {
|
|
20298
|
-
"@staix/agent-mem": "0.4.0"
|
|
20299
|
-
},
|
|
20300
|
-
devDependencies: {
|
|
20301
|
-
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
20302
|
-
"@types/bun": "^1.3.0",
|
|
20303
|
-
typescript: "^5.8.0"
|
|
20304
|
-
},
|
|
20305
|
-
files: [
|
|
20306
|
-
".claude-plugin",
|
|
20307
|
-
"src",
|
|
20308
|
-
"src/pet/window.jxa.js",
|
|
20309
|
-
"src/pet/window.html",
|
|
20310
|
-
"plugins",
|
|
20311
|
-
"templates",
|
|
20312
|
-
"scripts/check-docs.mjs",
|
|
20313
|
-
"docs",
|
|
20314
|
-
"README.md",
|
|
20315
|
-
"LICENSE",
|
|
20316
|
-
"CHANGELOG.md",
|
|
20317
|
-
"THIRD_PARTY_NOTICES.md",
|
|
20318
|
-
"LICENSES"
|
|
20319
|
-
],
|
|
20320
|
-
repository: {
|
|
20321
|
-
type: "git",
|
|
20322
|
-
url: "git+https://github.com/STAIxBWLB/agent-hub.git"
|
|
20323
|
-
},
|
|
20324
|
-
homepage: "https://github.com/STAIxBWLB/agent-hub",
|
|
20325
|
-
publishConfig: {
|
|
20326
|
-
access: "public"
|
|
20327
|
-
},
|
|
20328
|
-
exports: {
|
|
20329
|
-
"./adapter": "./src/adapters/public.ts",
|
|
20330
|
-
"./conformance": "./src/adapters/conformance/index.ts"
|
|
20331
|
-
}
|
|
20332
|
-
};
|
|
20333
|
-
|
|
20334
|
-
// src/version.ts
|
|
20335
|
-
var VERSION = package_default.version;
|
|
20336
|
-
|
|
20337
|
-
// src/hub/no-writes.ts
|
|
20338
|
-
var OWNER_TOOL_EFFECTS = new Set(["hub_task_propose", "hub_task_decline", "hub_task_done", "hub_task_cancel", "hub_task_assign", "hub_task_escalate", "hub_review", "hub_checkpoint", "hub_remember", "hub_memory_correct", "hub_check_start", "hub_check_cancel", "hub_sdlc_record", "hub_plan_review", "hub_backlog_plan", "hub_backlog_pull", "hub_backlog_undo", "hub_backlog_split", "hub_backlog_return_untouched", "hub_release_new", "hub_release_configure", "hub_release_assign", "hub_release_cut"]);
|
|
20339
|
-
|
|
20340
|
-
// src/hub/github-import.ts
|
|
20341
|
-
init_sdlc();
|
|
20342
|
-
|
|
20343
|
-
// src/hub/tasks.ts
|
|
20344
|
-
init_planning();
|
|
20345
|
-
|
|
20346
|
-
// src/hub/sdlc-queue.ts
|
|
20347
|
-
init_test_integrity();
|
|
20348
|
-
init_plan_conformance();
|
|
20349
|
-
init_review_findings();
|
|
20350
|
-
init_sdlc();
|
|
20351
|
-
|
|
20352
|
-
// src/hub/tasks.ts
|
|
20353
|
-
init_review_findings();
|
|
20354
|
-
init_roles();
|
|
20520
|
+
// src/hub/memory-symbols.ts
|
|
20521
|
+
init_deny();
|
|
20522
|
+
init_permission_mode();
|
|
20355
20523
|
init_path_policy();
|
|
20356
20524
|
|
|
20357
20525
|
// node_modules/@staix/agent-mem/src/index.ts
|
|
@@ -20366,46 +20534,76 @@ init_shard();
|
|
|
20366
20534
|
init_generation();
|
|
20367
20535
|
init_capture();
|
|
20368
20536
|
init_store();
|
|
20537
|
+
init_lifecycle();
|
|
20369
20538
|
// node_modules/@staix/agent-mem/src/memory/capacity.ts
|
|
20370
20539
|
init_home();
|
|
20371
|
-
|
|
20540
|
+
|
|
20541
|
+
// node_modules/@staix/agent-mem/src/memory/metrics.ts
|
|
20372
20542
|
var METRICS_MAX_BYTES = 64 * 1024;
|
|
20373
|
-
// node_modules/@staix/agent-mem/src/memory/
|
|
20374
|
-
|
|
20375
|
-
|
|
20543
|
+
// node_modules/@staix/agent-mem/src/memory/capacity.ts
|
|
20544
|
+
init_lifecycle();
|
|
20545
|
+
init_capture();
|
|
20376
20546
|
|
|
20377
|
-
// node_modules/@staix/agent-mem/src/
|
|
20378
|
-
|
|
20379
|
-
|
|
20380
|
-
|
|
20381
|
-
|
|
20382
|
-
|
|
20383
|
-
|
|
20384
|
-
// node_modules/@staix/agent-mem/src/memory/import.ts
|
|
20385
|
-
init_platform();
|
|
20547
|
+
// node_modules/@staix/agent-mem/src/index.ts
|
|
20548
|
+
init_lifecycle();
|
|
20549
|
+
|
|
20550
|
+
// node_modules/@staix/agent-mem/src/memory/gc.ts
|
|
20551
|
+
init_capture();
|
|
20552
|
+
init_lifecycle();
|
|
20553
|
+
// node_modules/@staix/agent-mem/src/memory/curated-read.ts
|
|
20386
20554
|
init_store();
|
|
20387
20555
|
init_shard();
|
|
20388
20556
|
|
|
20389
|
-
// node_modules/@staix/agent-mem/src/memory/
|
|
20390
|
-
|
|
20391
|
-
init_mutation();
|
|
20557
|
+
// node_modules/@staix/agent-mem/src/memory/project-export.ts
|
|
20558
|
+
init_lifecycle();
|
|
20392
20559
|
init_platform();
|
|
20560
|
+
init_policy();
|
|
20393
20561
|
init_screen();
|
|
20394
20562
|
init_shard();
|
|
20395
|
-
|
|
20396
|
-
|
|
20397
|
-
|
|
20563
|
+
init_work_lists();
|
|
20564
|
+
// node_modules/@staix/agent-mem/src/memory/cross-project.ts
|
|
20565
|
+
init_search_fts();
|
|
20566
|
+
init_capture();
|
|
20567
|
+
init_lifecycle();
|
|
20568
|
+
|
|
20569
|
+
// node_modules/@staix/agent-mem/src/memory/vault.ts
|
|
20570
|
+
init_generation();
|
|
20571
|
+
init_policy();
|
|
20572
|
+
init_generation();
|
|
20573
|
+
|
|
20574
|
+
// node_modules/@staix/agent-mem/src/index.ts
|
|
20575
|
+
init_sync();
|
|
20576
|
+
|
|
20577
|
+
// node_modules/@staix/agent-mem/src/memory/import.ts
|
|
20398
20578
|
init_platform();
|
|
20579
|
+
init_store();
|
|
20580
|
+
init_shard();
|
|
20581
|
+
init_work_lists();
|
|
20399
20582
|
|
|
20400
20583
|
// node_modules/@staix/agent-mem/src/index.ts
|
|
20584
|
+
init_project2();
|
|
20401
20585
|
init_home();
|
|
20402
20586
|
init_platform();
|
|
20403
20587
|
|
|
20404
20588
|
// node_modules/@staix/agent-mem/src/memory/commands.ts
|
|
20405
20589
|
init_store();
|
|
20590
|
+
init_sync();
|
|
20591
|
+
init_project2();
|
|
20592
|
+
var AMEM_NAMES = { memory: "amem", goal: "amem goal" };
|
|
20593
|
+
var memoryUsage = (name = AMEM_NAMES.memory) => `usage: ${name} [list] [--tier short|long] [--kind <kind>] [--peer <p>] [--machine <id>] [--since <iso>] [--limit N] | search <words> [--history] [--as-of <UTC>] | show <id> [--history] [--as-of <UTC>] | graph <id|node> [--hops 1|2] [--history] [--as-of <UTC>] | promote|demote|forget <id> | supersede <old> <new> | rebuild | sync [--remote <url>] | machine [--label <name>] | import claude-mem [--db <path>] [--limit N] [--restart] | dedupe [--apply] | suggestions | confirm|reject <suggestion id> [--json]`;
|
|
20594
|
+
var MEMORY_USAGE = memoryUsage();
|
|
20595
|
+
var goalUsage = (name = AMEM_NAMES.goal) => `usage: ${name} [list] [--all] | show <id> | new <title> [--milestone <text>]... [--depends <id>]... [--shared] | link <id> <task id> | state <id> open|active|blocked|done|dropped | done <id> | take <id> [--force] [--json]`;
|
|
20596
|
+
var GOAL_USAGE = goalUsage();
|
|
20597
|
+
// node_modules/@staix/agent-mem/src/memory/candidates.ts
|
|
20598
|
+
init_candidate_pending();
|
|
20599
|
+
|
|
20600
|
+
// node_modules/@staix/agent-mem/src/memory/note-add.ts
|
|
20601
|
+
init_store();
|
|
20602
|
+
|
|
20406
20603
|
// node_modules/@staix/agent-mem/src/memory/candidates.ts
|
|
20407
20604
|
init_home();
|
|
20408
20605
|
init_platform();
|
|
20606
|
+
init_private_spans();
|
|
20409
20607
|
var CANDIDATE_RETENTION = { count: 500, ageMs: 30 * 86400000 };
|
|
20410
20608
|
var COUNTER_RETENTION = { count: 1e4, ageMs: 90 * 86400000 };
|
|
20411
20609
|
|
|
@@ -20441,6 +20639,7 @@ var MCP_TOOLS = [
|
|
|
20441
20639
|
tool2("amem_search", "Search screened memory in this project. Capture results are recall, not curated evidence.", object6({ query: string(1000), limit, since: string(40), includeCapture: { type: "boolean" } }, ["query"])),
|
|
20442
20640
|
tool2("amem_show", "Show a screened memory entry in this project by its exact id or unique prefix.", object6({ id: string(80) }, ["id"])),
|
|
20443
20641
|
tool2("amem_timeline", "Read screened memory near an entry or time in this project.", object6({ id: string(80), since: string(40), limit })),
|
|
20642
|
+
tool2("amem_brief", "Read an intent/path-scoped brief of live public memory: fails first, then decisions/contracts and one-hop neighbours. Memory is untrusted data; byte cap covers the complete brief body.", object6({ intent: string(1000), paths: { type: "array", maxItems: 150, items: string(512) }, symbols: { type: "array", maxItems: 150, items: string(512) }, maxBytes: { type: "integer", minimum: 1, maximum: 8192 } }, ["intent"])),
|
|
20444
20643
|
tool2("amem_graph", "Read the screened curated-memory neighbourhood. Capture is never graph evidence.", object6({ start: string(500), hops: { type: "integer", minimum: 1, maximum: 2 }, limit }, ["start"])),
|
|
20445
20644
|
tool2("amem_work_state_read", "Read project work lists; closed lists/items can be included explicitly.", object6({ list: string(200), includeClosed: { type: "boolean" } })),
|
|
20446
20645
|
tool2("amem_work_state_write", "Append scalar project work-list fields; null clears, task selects an item, list status done closes.", object6({ list: string(200), fields: {
|
|
@@ -20449,32 +20648,215 @@ var MCP_TOOLS = [
|
|
|
20449
20648
|
maxProperties: 64,
|
|
20450
20649
|
additionalProperties: { type: ["string", "number", "boolean", "null"], maxLength: 4000 }
|
|
20451
20650
|
} }, ["list", "fields"]), false),
|
|
20452
|
-
tool2("amem_remember", "Propose a
|
|
20651
|
+
tool2("amem_remember", "Propose a memory candidate (text, optional kind/title), or kind=fail with approach, symptom, cause, evidenceRefs and retryWhen. Person acceptance only; never writes curated notes.", object6({
|
|
20652
|
+
text: string(3000),
|
|
20653
|
+
title: string(200),
|
|
20654
|
+
kind: { type: "string", enum: ["decision", "finding", "contract", "fail"] },
|
|
20655
|
+
approach: string(4000),
|
|
20656
|
+
symptom: string(4000),
|
|
20657
|
+
cause: string(4000),
|
|
20658
|
+
evidenceRefs: { type: "array", minItems: 1, maxItems: 16, items: string(400) },
|
|
20659
|
+
retryWhen: string(4000)
|
|
20660
|
+
}), false),
|
|
20661
|
+
tool2("amem_correct", "Propose a correction to an exact curated entry id visible in this project. The host binds its current revision; the full accepted body including reason and provenance headers must fit 4000 characters. A person must accept before any note or supersession is written.", object6({ id: string(80), proposedText: string(4000), reason: string(4000), kind: { type: "string", enum: ["decision", "contract", "fail"] } }, ["id", "proposedText", "reason"]), false),
|
|
20453
20662
|
tool2("amem_vault_candidates", "List screened durable curated notes eligible for a person to move to the vault. Read only.", object6({ limit })),
|
|
20454
20663
|
tool2("amem_capacity", "Read this machine's bounded capture capacity proposal; applying it is person-only.", object6({}))
|
|
20455
20664
|
];
|
|
20665
|
+
// node_modules/@staix/agent-mem/src/memory/plan-guard.ts
|
|
20666
|
+
init_store();
|
|
20667
|
+
init_project2();
|
|
20668
|
+
|
|
20669
|
+
// node_modules/@staix/agent-mem/src/memory/intent-brief.ts
|
|
20670
|
+
init_policy();
|
|
20671
|
+
|
|
20672
|
+
// node_modules/@staix/agent-mem/src/memory/recall-staleness.ts
|
|
20673
|
+
init_platform();
|
|
20674
|
+
init_policy();
|
|
20675
|
+
init_project2();
|
|
20676
|
+
|
|
20456
20677
|
// node_modules/@staix/agent-mem/src/mcp-memory.ts
|
|
20457
20678
|
init_capture();
|
|
20458
20679
|
init_generation();
|
|
20459
20680
|
init_home();
|
|
20460
20681
|
init_platform();
|
|
20682
|
+
init_policy();
|
|
20683
|
+
init_config2();
|
|
20684
|
+
init_project2();
|
|
20685
|
+
init_reader_lease();
|
|
20686
|
+
init_search_fts();
|
|
20687
|
+
init_shard();
|
|
20688
|
+
init_lifecycle();
|
|
20689
|
+
init_repo_scope();
|
|
20690
|
+
init_store();
|
|
20691
|
+
init_work_lists();
|
|
20461
20692
|
|
|
20462
|
-
// node_modules/@staix/agent-mem/src/
|
|
20693
|
+
// node_modules/@staix/agent-mem/src/index.ts
|
|
20694
|
+
init_policy();
|
|
20695
|
+
init_work_lists();
|
|
20696
|
+
|
|
20697
|
+
// node_modules/@staix/agent-mem/src/hook-git.ts
|
|
20698
|
+
init_platform();
|
|
20699
|
+
init_platform();
|
|
20700
|
+
|
|
20701
|
+
// node_modules/@staix/agent-mem/src/hook-spool.ts
|
|
20702
|
+
init_capture_input();
|
|
20703
|
+
init_platform();
|
|
20704
|
+
init_policy();
|
|
20705
|
+
init_config2();
|
|
20706
|
+
init_policy();
|
|
20463
20707
|
init_screen();
|
|
20708
|
+
init_lifecycle();
|
|
20709
|
+
|
|
20710
|
+
// node_modules/@staix/agent-mem/src/hook-contract.ts
|
|
20711
|
+
var HOOK_INPUT_BYTES = 64 * 1024;
|
|
20712
|
+
|
|
20713
|
+
// node_modules/@staix/agent-mem/src/hook-spool.ts
|
|
20714
|
+
var RECORD_BYTES = HOOK_INPUT_BYTES + 8192;
|
|
20715
|
+
var MAX_BYTES5 = 2 * 1024 * 1024;
|
|
20716
|
+
var DRAIN_BYTES = 256 * 1024;
|
|
20717
|
+
var CAPACITY_BYTES = 256 * 1024;
|
|
20718
|
+
var RETIRE_AFTER_MS = 7 * 24 * 3600000;
|
|
20719
|
+
|
|
20720
|
+
// node_modules/@staix/agent-mem/src/hook-project.ts
|
|
20721
|
+
init_platform();
|
|
20722
|
+
init_project2();
|
|
20464
20723
|
init_platform();
|
|
20465
|
-
var MAX_BYTES3 = 256 * 1024;
|
|
20466
|
-
var MAX_STRING_BYTES = 16 * 1024;
|
|
20467
|
-
var flags = new Set(["private", "pii", "privateturn", "isprivate", "ispii"]);
|
|
20468
20724
|
|
|
20469
|
-
// node_modules/@staix/agent-mem/src/
|
|
20470
|
-
|
|
20471
|
-
|
|
20725
|
+
// node_modules/@staix/agent-mem/src/hook-native.ts
|
|
20726
|
+
var SUPERVISOR = [
|
|
20727
|
+
"marker=$1; seconds=$2; shift 2; leader=$$",
|
|
20728
|
+
'owned() { identity=$(/bin/ps -ww -p "$leader" -o pgid=,command=) || return 1; case "$identity" in *"$marker"*) ;; *) return 1 ;; esac; set -f; set -- $identity; [ "$1" = "$leader" ]; }',
|
|
20729
|
+
'reap() { if owned; then kill -KILL "-$leader"; fi; }',
|
|
20730
|
+
"[ -x /bin/sleep ] && [ -x /bin/ps ] && owned || exit 125",
|
|
20731
|
+
"trap reap EXIT HUP INT TERM",
|
|
20732
|
+
'( /bin/sleep "$seconds" & sleeper=$!; kill -0 "$sleeper" 2>/dev/null || { reap; exit 125; }; printf "\\n%s:ARMED\\n" "$marker"; wait "$sleeper"; reap ) & watchdog=$!',
|
|
20733
|
+
'kill -0 "$watchdog" 2>/dev/null || { reap; exit 125; }',
|
|
20734
|
+
'"$@" </dev/null; code=$?; printf "\\n%s:%s\\n" "$marker" "$code"',
|
|
20735
|
+
"read -r ignored || :",
|
|
20736
|
+
'wait "$watchdog"'
|
|
20737
|
+
].join("; ");
|
|
20738
|
+
|
|
20739
|
+
// node_modules/@staix/agent-mem/src/hooks.ts
|
|
20740
|
+
init_aliases();
|
|
20741
|
+
init_home();
|
|
20742
|
+
init_platform();
|
|
20743
|
+
init_policy();
|
|
20744
|
+
init_config2();
|
|
20745
|
+
// node_modules/@staix/agent-mem/src/ingest.ts
|
|
20746
|
+
init_capture();
|
|
20747
|
+
init_home();
|
|
20748
|
+
init_platform();
|
|
20749
|
+
init_policy();
|
|
20750
|
+
init_config2();
|
|
20751
|
+
init_project2();
|
|
20472
20752
|
init_shard();
|
|
20473
|
-
|
|
20753
|
+
var MAX_BYTES6 = 16 * 1024 * 1024;
|
|
20754
|
+
var DEFAULT_BYTES = 4 * 1024 * 1024;
|
|
20755
|
+
var MAX_CURSOR_BYTES = 128 * 1024;
|
|
20756
|
+
var utf8 = new TextDecoder("utf-8", { fatal: true });
|
|
20757
|
+
var MAX_RECORD = 256 * 1024;
|
|
20474
20758
|
|
|
20475
|
-
// node_modules/@staix/agent-mem/src/
|
|
20759
|
+
// node_modules/@staix/agent-mem/src/index.ts
|
|
20760
|
+
init_session_brief();
|
|
20761
|
+
|
|
20762
|
+
// node_modules/@staix/agent-mem/src/memory/decay-review.ts
|
|
20476
20763
|
init_generation();
|
|
20764
|
+
// node_modules/@staix/agent-mem/src/memory/goal-promotion.ts
|
|
20477
20765
|
init_generation();
|
|
20766
|
+
init_lifecycle();
|
|
20767
|
+
init_platform();
|
|
20768
|
+
init_shard();
|
|
20769
|
+
// src/hub/tasks.ts
|
|
20770
|
+
init_task_code_symbols();
|
|
20771
|
+
init_test_integrity();
|
|
20772
|
+
init_plan_conformance();
|
|
20773
|
+
|
|
20774
|
+
// src/hub/sdlc-release.ts
|
|
20775
|
+
init_child_process();
|
|
20776
|
+
init_git();
|
|
20777
|
+
|
|
20778
|
+
// src/hub/tasks.ts
|
|
20779
|
+
init_spec_read();
|
|
20780
|
+
init_sdlc_metrics();
|
|
20781
|
+
// package.json
|
|
20782
|
+
var package_default = {
|
|
20783
|
+
name: "@staix/agent-hub",
|
|
20784
|
+
version: "0.16.0",
|
|
20785
|
+
description: "Native multi-agent hub: Claude Code, Codex, Kimi Code, Pi and local inference as peers in one project",
|
|
20786
|
+
license: "MIT",
|
|
20787
|
+
type: "module",
|
|
20788
|
+
bin: {
|
|
20789
|
+
ahub: "src/cli/main.js",
|
|
20790
|
+
"agent-hub": "src/cli/main.js"
|
|
20791
|
+
},
|
|
20792
|
+
scripts: {
|
|
20793
|
+
check: "scripts/check.sh",
|
|
20794
|
+
build: "bun scripts/build.mjs",
|
|
20795
|
+
test: "bun test"
|
|
20796
|
+
},
|
|
20797
|
+
engines: {
|
|
20798
|
+
bun: ">=1.4.2"
|
|
20799
|
+
},
|
|
20800
|
+
dependencies: {
|
|
20801
|
+
"@staix/agent-mem": "0.7.0"
|
|
20802
|
+
},
|
|
20803
|
+
devDependencies: {
|
|
20804
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
20805
|
+
"@types/bun": "^1.3.0",
|
|
20806
|
+
typescript: "^5.8.0"
|
|
20807
|
+
},
|
|
20808
|
+
files: [
|
|
20809
|
+
".claude-plugin",
|
|
20810
|
+
"src",
|
|
20811
|
+
"src/pet/window.jxa.js",
|
|
20812
|
+
"src/pet/window.html",
|
|
20813
|
+
"plugins",
|
|
20814
|
+
"templates",
|
|
20815
|
+
"scripts/check-docs.mjs",
|
|
20816
|
+
"docs",
|
|
20817
|
+
"README.md",
|
|
20818
|
+
"LICENSE",
|
|
20819
|
+
"CHANGELOG.md",
|
|
20820
|
+
"THIRD_PARTY_NOTICES.md",
|
|
20821
|
+
"LICENSES"
|
|
20822
|
+
],
|
|
20823
|
+
repository: {
|
|
20824
|
+
type: "git",
|
|
20825
|
+
url: "git+https://github.com/STAIxBWLB/agent-hub.git"
|
|
20826
|
+
},
|
|
20827
|
+
homepage: "https://github.com/STAIxBWLB/agent-hub",
|
|
20828
|
+
publishConfig: {
|
|
20829
|
+
access: "public"
|
|
20830
|
+
},
|
|
20831
|
+
exports: {
|
|
20832
|
+
"./adapter": "./src/adapters/public.ts",
|
|
20833
|
+
"./conformance": "./src/adapters/conformance/index.ts"
|
|
20834
|
+
}
|
|
20835
|
+
};
|
|
20836
|
+
|
|
20837
|
+
// src/version.ts
|
|
20838
|
+
var VERSION = package_default.version;
|
|
20839
|
+
|
|
20840
|
+
// src/hub/no-writes.ts
|
|
20841
|
+
var OWNER_TOOL_EFFECTS = new Set(["hub_task_propose", "hub_task_decline", "hub_task_done", "hub_task_cancel", "hub_task_assign", "hub_task_escalate", "hub_review", "hub_checkpoint", "hub_remember", "hub_memory_correct", "hub_check_start", "hub_check_cancel", "hub_sdlc_record", "hub_plan_review", "hub_backlog_plan", "hub_backlog_pull", "hub_backlog_undo", "hub_backlog_split", "hub_backlog_return_untouched", "hub_release_new", "hub_release_configure", "hub_release_assign", "hub_release_cut"]);
|
|
20842
|
+
|
|
20843
|
+
// src/hub/github-import.ts
|
|
20844
|
+
init_sdlc();
|
|
20845
|
+
|
|
20846
|
+
// src/hub/tasks.ts
|
|
20847
|
+
init_planning();
|
|
20848
|
+
|
|
20849
|
+
// src/hub/sdlc-queue.ts
|
|
20850
|
+
init_test_integrity();
|
|
20851
|
+
init_plan_conformance();
|
|
20852
|
+
init_review_findings();
|
|
20853
|
+
init_sdlc();
|
|
20854
|
+
|
|
20855
|
+
// src/hub/tasks.ts
|
|
20856
|
+
init_review_findings();
|
|
20857
|
+
init_roles();
|
|
20858
|
+
init_path_policy();
|
|
20859
|
+
|
|
20478
20860
|
// src/hub/memory-candidates.ts
|
|
20479
20861
|
init_tool_refusal();
|
|
20480
20862
|
|
|
@@ -20528,11 +20910,12 @@ var CONTRADICTION_WINDOW_MS = 7 * 86400000;
|
|
|
20528
20910
|
var QUIET_EVENTS = new Set(["answer", "reviewer changed", "idle sweep", "memory reference started"]);
|
|
20529
20911
|
QUIET_EVENTS.add("ready held");
|
|
20530
20912
|
QUIET_EVENTS.add("plan observed");
|
|
20913
|
+
QUIET_EVENTS.add("planner changed");
|
|
20531
20914
|
var OWNERSHIP_EVENTS = new Set(["assigned", "escalated", "reassigned", "unassigned"]);
|
|
20915
|
+
var GROOM_FYI_WINDOW_MS = 10 * 60000;
|
|
20532
20916
|
|
|
20533
20917
|
// src/hub/conductor.ts
|
|
20534
20918
|
init_tool_refusal();
|
|
20535
|
-
|
|
20536
20919
|
// src/hub/instances.ts
|
|
20537
20920
|
import { types as types4 } from "util";
|
|
20538
20921
|
init_project();
|