@staix/agent-hub 0.16.0 → 0.17.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 +18 -0
- package/README.md +2 -2
- package/docs/agent-notes/adapters.md +18 -5
- package/docs/agent-notes/daemon.md +4 -3
- package/docs/agent-notes/tasks.md +2 -2
- package/docs/agent-notes/tests.md +5 -3
- package/docs/dogfood.md +6 -3
- package/docs/operations.md +45 -18
- package/docs/quickstart.md +6 -2
- package/docs/roles.md +1 -0
- package/docs/sdlc.md +11 -11
- package/docs/security.md +15 -5
- package/docs/smoke.md +51 -3
- package/docs/specs/2026-09-19-agent-hub-design.md +40 -4
- package/docs/specs/2026-09-20-upgrade-recovery-design.md +3 -1
- package/docs/verification/2026-10-25-0.17.0.md +74 -0
- package/docs/verification/2026-10-25-codex-0164-reviewer-tool-call.md +52 -0
- package/docs/verification/2026-10-25-kimi-231-reviewer-compat.md +48 -0
- package/docs/verification/2026-10-25-kimi-usage-ac11.md +48 -0
- package/docs/verification/2026-10-25-qwen-dual-output-hub-e2e.md +23 -0
- package/docs/verification/2026-10-25-qwen-dual-output-t0.md +20 -0
- package/docs/verification/2026-10-25-reviewer-no-verdict-diagnostics.md +35 -0
- package/docs/verified.json +20 -17
- package/package.json +1 -1
- package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-hub/server.js +1022 -160
- package/src/adapters/codex-review-isolation.ts +10 -1
- package/src/adapters/compat.ts +7 -2
- package/src/adapters/conformance/enrollment.ts +18 -4
- package/src/adapters/conformance/factories.ts +4 -1
- package/src/adapters/conformance/kit.ts +11 -0
- package/src/adapters/definitions/native-cli-entry-qwen.ts +32 -4
- package/src/adapters/definitions/native-cli-preview-qwen.ts +9 -1
- package/src/adapters/definitions/native-factories.ts +8 -0
- package/src/adapters/definitions/native-recovery.ts +8 -0
- package/src/adapters/definitions/qwen.ts +7 -4
- package/src/adapters/qwen-dual.ts +578 -0
- package/src/adapters/registry-types.ts +5 -1
- package/src/adapters/registry.ts +9 -1
- package/src/adapters/reviewer-mcp.ts +12 -6
- package/src/adapters/reviewer-print.ts +100 -17
- package/src/cli/upgrade.ts +4 -3
- package/src/hub/daemon.ts +134 -22
- package/src/hub/memory-plan-guard.ts +8 -33
- package/src/hub/tasks.ts +25 -12
- package/src/memory/brief.ts +45 -5
|
@@ -310,7 +310,7 @@ var init_compat = __esm(() => {
|
|
|
310
310
|
},
|
|
311
311
|
"kimi-review": {
|
|
312
312
|
label: "Kimi reviewer (kimi-code)",
|
|
313
|
-
verified: ["2.2.0", "2.3.0"],
|
|
313
|
+
verified: ["2.2.0", "2.3.0", "2.3.1"],
|
|
314
314
|
gate: "isolation",
|
|
315
315
|
probe: ["kimi", "--version"],
|
|
316
316
|
parse: (out) => plain(out.trim()),
|
|
@@ -350,6 +350,15 @@ var init_compat = __esm(() => {
|
|
|
350
350
|
config: "qwen_cmd",
|
|
351
351
|
parse: (out) => plain(out.trim()),
|
|
352
352
|
trustNote: "Native ACP identity and mode ids have dated receipts; 0.26.0 permission callbacks were checked without inference. Another version may change those contracts."
|
|
353
|
+
},
|
|
354
|
+
"qwen-dual": {
|
|
355
|
+
label: "Qwen Code dual-output TUI",
|
|
356
|
+
verified: ["0.26.1"],
|
|
357
|
+
gate: "contract",
|
|
358
|
+
probe: ["qwen", "--version"],
|
|
359
|
+
config: "qwen_cmd",
|
|
360
|
+
parse: (out) => plain(out.trim()),
|
|
361
|
+
trustNote: "The dual-output file contract (submit and confirmation_response input, user/assistant/stream_event/control events, session_start identity) was verified on 0.26.1 only. A hub deny ends the turn without any Qwen event, and no external stop of a running turn exists; another version may change either."
|
|
353
362
|
}
|
|
354
363
|
};
|
|
355
364
|
MAX_BYTES = 64 * 1024;
|
|
@@ -792,6 +801,17 @@ function parseProcessTable(text) {
|
|
|
792
801
|
return m && !m[4].startsWith("Z") ? [{ pid: Number(m[1]), ppid: Number(m[2]), pgid: Number(m[3]), started: m[5], command: m[6] }] : [];
|
|
793
802
|
});
|
|
794
803
|
}
|
|
804
|
+
function processTable() {
|
|
805
|
+
for (let attempt = 0;attempt < 2; attempt++) {
|
|
806
|
+
try {
|
|
807
|
+
const r = Bun.spawnSync(PS, { ...PS_OPTIONS, stdout: "pipe", stderr: "pipe", timeout: 1e4 });
|
|
808
|
+
const rows = r.exitCode === 0 ? own(r.stdout.toString()) : undefined;
|
|
809
|
+
if (rows)
|
|
810
|
+
return rows;
|
|
811
|
+
} catch {}
|
|
812
|
+
}
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
795
815
|
async function readProcessTable() {
|
|
796
816
|
for (let attempt = 0;attempt < 2; attempt++) {
|
|
797
817
|
try {
|
|
@@ -1332,7 +1352,15 @@ var DEFAULT_WATCHDOG_MS = 300000;
|
|
|
1332
1352
|
|
|
1333
1353
|
// src/adapters/reviewer-print.ts
|
|
1334
1354
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1335
|
-
|
|
1355
|
+
function reviewerNoVerdictLine(peer, diagnostic, safeRefusal, withheld = false) {
|
|
1356
|
+
const prefix = `[${peer}] review of task #${diagnostic.task}: no verdict exit=${diagnostic.exitCode ?? "unknown"}; `;
|
|
1357
|
+
if (withheld)
|
|
1358
|
+
return prefix + "diagnostic withheld";
|
|
1359
|
+
const detail = diagnostic.lastTool ? `last hub tool ${diagnostic.lastTool}; ${diagnostic.reviewCalled ? "hub_review called" : "no hub_review call"}` : "no hub_review call";
|
|
1360
|
+
const line = safeRefusal === undefined ? undefined : Array.from(sanitize(safeRefusal).replace(/\s+/g, " ")).filter((char) => !/\p{C}/u.test(char)).slice(0, 240).join("");
|
|
1361
|
+
return prefix + detail + (diagnostic.refusal === undefined && diagnostic.hostRefusal === undefined ? "" : `; refusal: ${line ?? "withheld"}`);
|
|
1362
|
+
}
|
|
1363
|
+
var ReviewerUnavailableError, ReviewerAdmissionError, REVIEWER_FALLBACK_UNCONFIRMED = "reviewer fallback unconfirmed: ", reviewTarget = (env) => {
|
|
1336
1364
|
const task = reviewTask(env);
|
|
1337
1365
|
if (task === undefined || env.private)
|
|
1338
1366
|
return;
|
|
@@ -1356,6 +1384,13 @@ var init_reviewer_print = __esm(() => {
|
|
|
1356
1384
|
this.detail = detail;
|
|
1357
1385
|
}
|
|
1358
1386
|
};
|
|
1387
|
+
ReviewerAdmissionError = class ReviewerAdmissionError extends Error {
|
|
1388
|
+
reason;
|
|
1389
|
+
constructor(reason) {
|
|
1390
|
+
super("review admission changed");
|
|
1391
|
+
this.reason = reason;
|
|
1392
|
+
}
|
|
1393
|
+
};
|
|
1359
1394
|
ReviewerPrintPeer = class ReviewerPrintPeer extends BasePeer {
|
|
1360
1395
|
opts;
|
|
1361
1396
|
hubNative = true;
|
|
@@ -1374,6 +1409,29 @@ var init_reviewer_print = __esm(() => {
|
|
|
1374
1409
|
reviewing;
|
|
1375
1410
|
reviewSeq = 0;
|
|
1376
1411
|
runningBinding;
|
|
1412
|
+
activeTrace;
|
|
1413
|
+
recordToolTrace(task, trace) {
|
|
1414
|
+
if (trace.tool !== "hub_review" && trace.tool !== "hub_task_show")
|
|
1415
|
+
return;
|
|
1416
|
+
const active = this.activeTrace, binding = this.runningBinding;
|
|
1417
|
+
if (!active || active.task !== task || active.generation !== this.generation || this.reviewing !== task || binding?.task !== task || this.opts.bindingCurrent?.(task, binding.binding) === false)
|
|
1418
|
+
return;
|
|
1419
|
+
if (typeof trace.refusal === "string") {
|
|
1420
|
+
if (active.value.hostRefusal === undefined)
|
|
1421
|
+
active.value.hostRefusal = trace.refusal;
|
|
1422
|
+
else if (active.value.hostRefusal !== trace.refusal)
|
|
1423
|
+
active.value.refusalUncertain = true;
|
|
1424
|
+
}
|
|
1425
|
+
this.observeTool(active.value, trace);
|
|
1426
|
+
}
|
|
1427
|
+
observeTool(value, trace) {
|
|
1428
|
+
if (trace.tool !== "hub_review" && trace.tool !== "hub_task_show")
|
|
1429
|
+
return;
|
|
1430
|
+
value.lastTool = trace.tool;
|
|
1431
|
+
value.refusal = typeof trace.refusal === "string" ? trace.refusal : undefined;
|
|
1432
|
+
if (trace.tool === "hub_review")
|
|
1433
|
+
value.reviewCalled = true;
|
|
1434
|
+
}
|
|
1377
1435
|
reviewBinding(task) {
|
|
1378
1436
|
return this.reviewing === task && this.runningBinding?.task === task ? this.runningBinding.binding : undefined;
|
|
1379
1437
|
}
|
|
@@ -1510,6 +1568,17 @@ var init_reviewer_print = __esm(() => {
|
|
|
1510
1568
|
} catch (error) {
|
|
1511
1569
|
if (generation !== this.generation)
|
|
1512
1570
|
return;
|
|
1571
|
+
if (error instanceof ReviewerAdmissionError) {
|
|
1572
|
+
this.opts.log?.(`[${this.id}] review of task #${task} skipped: ${error.reason === "stale_binding" ? "stale binding" : "review policy changed"}`);
|
|
1573
|
+
if (error.reason === "policy_changed") {
|
|
1574
|
+
missing.push(task);
|
|
1575
|
+
const target = batch.targets[index];
|
|
1576
|
+
if (target)
|
|
1577
|
+
reroute.push({ ...target, cause: "no_verdict" });
|
|
1578
|
+
}
|
|
1579
|
+
batch.next = index + 1;
|
|
1580
|
+
continue;
|
|
1581
|
+
}
|
|
1513
1582
|
if (error instanceof PrintChildCleanupError) {
|
|
1514
1583
|
this.opts.log?.(`[${this.id}] review of task #${task}: ${error.message}`);
|
|
1515
1584
|
this.settle("needs_review", `review process group still running: ${error.message}`);
|
|
@@ -1534,6 +1603,15 @@ var init_reviewer_print = __esm(() => {
|
|
|
1534
1603
|
if (!recorded) {
|
|
1535
1604
|
missing.push(task);
|
|
1536
1605
|
const target = batch.targets[index];
|
|
1606
|
+
const diagnostic = { ...target, task, exitCode: result.code, ...result.trace };
|
|
1607
|
+
try {
|
|
1608
|
+
if (this.opts.onNoVerdict)
|
|
1609
|
+
this.opts.onNoVerdict(diagnostic, () => generation === this.generation);
|
|
1610
|
+
else
|
|
1611
|
+
this.opts.log?.(reviewerNoVerdictLine(this.id, diagnostic));
|
|
1612
|
+
} catch {
|
|
1613
|
+
this.opts.log?.(reviewerNoVerdictLine(this.id, { task, exitCode: result.code, reviewCalled: false }, undefined, true));
|
|
1614
|
+
}
|
|
1537
1615
|
if (target)
|
|
1538
1616
|
reroute.push({ ...target, cause: result.quota ? "quota" : "no_verdict" });
|
|
1539
1617
|
if (result.quota) {
|
|
@@ -1543,14 +1621,18 @@ var init_reviewer_print = __esm(() => {
|
|
|
1543
1621
|
}
|
|
1544
1622
|
}
|
|
1545
1623
|
batch.next = index + 1;
|
|
1546
|
-
if (result.code !== 0)
|
|
1624
|
+
if (recorded && result.code !== 0)
|
|
1547
1625
|
this.opts.log?.(`[${this.id}] review of task #${task} exited ${result.code}`);
|
|
1548
1626
|
}
|
|
1549
1627
|
if (generation !== this.generation)
|
|
1550
1628
|
return;
|
|
1551
1629
|
this.clearWatchdog();
|
|
1552
|
-
|
|
1553
|
-
|
|
1630
|
+
const reoffered = await this.reportFailures(reroute, generation);
|
|
1631
|
+
if (generation !== this.generation)
|
|
1632
|
+
return;
|
|
1633
|
+
const unresolved = missing.length > 0 && !reoffered;
|
|
1634
|
+
const normalNoVerdict = !failure && reroute.length > 0 && reroute.every((value) => value.cause === "no_verdict");
|
|
1635
|
+
this.settle(failure?.state ?? (unresolved ? "needs_review" : "completed"), failure?.reason ?? (unresolved ? `${normalNoVerdict ? REVIEWER_FALLBACK_UNCONFIRMED : ""}no verdict recorded for task ${missing.map((id) => `#${id}`).join(", ")}` : undefined));
|
|
1554
1636
|
if (generation === this.generation) {
|
|
1555
1637
|
this.batch = undefined;
|
|
1556
1638
|
this.setState("idle");
|
|
@@ -1558,19 +1640,23 @@ var init_reviewer_print = __esm(() => {
|
|
|
1558
1640
|
}
|
|
1559
1641
|
async reportFailures(failures, generation) {
|
|
1560
1642
|
const seen = new Set;
|
|
1643
|
+
let reoffered = true;
|
|
1561
1644
|
for (const failed of failures) {
|
|
1562
1645
|
if (generation !== this.generation)
|
|
1563
|
-
return;
|
|
1646
|
+
return false;
|
|
1564
1647
|
const identity = JSON.stringify([failed.task, failed.head, failed.flowRevision]);
|
|
1565
1648
|
if (seen.has(identity))
|
|
1566
1649
|
continue;
|
|
1567
1650
|
seen.add(identity);
|
|
1568
1651
|
try {
|
|
1569
|
-
await this.opts.onUnavailable?.(failed, () => generation === this.generation)
|
|
1652
|
+
if (await this.opts.onUnavailable?.(failed, () => generation === this.generation) !== true)
|
|
1653
|
+
reoffered = false;
|
|
1570
1654
|
} catch {
|
|
1655
|
+
reoffered = false;
|
|
1571
1656
|
this.opts.log?.(`[${this.id}] reviewer fallback could not be recorded; inspect the task`);
|
|
1572
1657
|
}
|
|
1573
1658
|
}
|
|
1659
|
+
return reoffered;
|
|
1574
1660
|
}
|
|
1575
1661
|
prepare(sessionId, input, task, mcpConfig) {
|
|
1576
1662
|
const abort = new AbortController;
|
|
@@ -1607,9 +1693,16 @@ var init_reviewer_print = __esm(() => {
|
|
|
1607
1693
|
}).catch(() => {});
|
|
1608
1694
|
return done;
|
|
1609
1695
|
}
|
|
1610
|
-
|
|
1696
|
+
assertAdmission(env, task, binding) {
|
|
1697
|
+
if (this.opts.bindingCurrent?.(task, binding) === false)
|
|
1698
|
+
throw new ReviewerAdmissionError("stale_binding");
|
|
1611
1699
|
if (this.opts.admit?.(env) === false)
|
|
1612
|
-
throw new
|
|
1700
|
+
throw new ReviewerAdmissionError(this.opts.bindingCurrent?.(task, binding) === false ? "stale_binding" : "policy_changed");
|
|
1701
|
+
}
|
|
1702
|
+
async review(env, task, generation) {
|
|
1703
|
+
const revision = Number(env.refs?.reviewRevision);
|
|
1704
|
+
const binding = Object.freeze({ head: env.refs?.commit, flowRevision: Number.isSafeInteger(revision) && revision > 0 ? revision : undefined });
|
|
1705
|
+
this.assertAdmission(env, task, binding);
|
|
1613
1706
|
let facts;
|
|
1614
1707
|
try {
|
|
1615
1708
|
facts = this.opts.facts?.(env);
|
|
@@ -1620,8 +1713,8 @@ var init_reviewer_print = __esm(() => {
|
|
|
1620
1713
|
|
|
1621
1714
|
${facts}` : ""}`;
|
|
1622
1715
|
let launch;
|
|
1623
|
-
const
|
|
1624
|
-
|
|
1716
|
+
const trace = { reviewCalled: false };
|
|
1717
|
+
this.activeTrace = { task, generation, value: trace };
|
|
1625
1718
|
const mcp = this.opts.backend.taskMcp ? this.opts.taskMcp?.(task, () => generation === this.generation && this.reviewToken(task) !== undefined && this.opts.bindingCurrent?.(task, binding) !== false) : undefined;
|
|
1626
1719
|
let mcpClosed = false;
|
|
1627
1720
|
const closeMcp = () => {
|
|
@@ -1638,8 +1731,11 @@ ${facts}` : ""}`;
|
|
|
1638
1731
|
const release = () => releasing ??= Promise.resolve().then(() => launch?.cleanup?.()).then(() => {});
|
|
1639
1732
|
try {
|
|
1640
1733
|
launch = await this.prepare(randomUUID2(), input, task, mcp?.config);
|
|
1641
|
-
if (generation !== this.generation
|
|
1642
|
-
throw new
|
|
1734
|
+
if (generation !== this.generation)
|
|
1735
|
+
throw new ReviewerAdmissionError("stale_binding");
|
|
1736
|
+
this.assertAdmission(env, task, binding);
|
|
1737
|
+
if (launch.current?.() === false)
|
|
1738
|
+
throw new ReviewerUnavailableError("reviewer isolation changed before child spawn", "binary");
|
|
1643
1739
|
this.receipt = launch.isolation;
|
|
1644
1740
|
let quota = false;
|
|
1645
1741
|
const child = new OwnedPrintChild({
|
|
@@ -1666,6 +1762,11 @@ ${facts}` : ""}`;
|
|
|
1666
1762
|
}, 100);
|
|
1667
1763
|
},
|
|
1668
1764
|
onFrame: (event) => {
|
|
1765
|
+
try {
|
|
1766
|
+
const observed = this.opts.backend.toolTrace?.(event);
|
|
1767
|
+
if (observed && generation === this.generation)
|
|
1768
|
+
this.observeTool(trace, observed);
|
|
1769
|
+
} catch {}
|
|
1669
1770
|
const usage = this.opts.backend.usage?.(event);
|
|
1670
1771
|
if (usage)
|
|
1671
1772
|
this.opts.onUsage?.(usage, task);
|
|
@@ -1687,8 +1788,10 @@ ${facts}` : ""}`;
|
|
|
1687
1788
|
const code = await child.done;
|
|
1688
1789
|
if (this.child === child)
|
|
1689
1790
|
this.child = undefined;
|
|
1690
|
-
return { code, quota };
|
|
1791
|
+
return { code, quota, trace };
|
|
1691
1792
|
} finally {
|
|
1793
|
+
if (this.activeTrace?.value === trace)
|
|
1794
|
+
this.activeTrace = undefined;
|
|
1692
1795
|
closeMcp();
|
|
1693
1796
|
if (policyTimer)
|
|
1694
1797
|
clearInterval(policyTimer);
|
|
@@ -1988,7 +2091,15 @@ function ownServer(text) {
|
|
|
1988
2091
|
const env = hub.env === undefined ? {} : object2(hub.env);
|
|
1989
2092
|
if (Object.values(env).some((value) => typeof value !== "string"))
|
|
1990
2093
|
throw new Error("invalid hub environment");
|
|
1991
|
-
return {
|
|
2094
|
+
return {
|
|
2095
|
+
command: hub.command,
|
|
2096
|
+
args: hub.args ?? [],
|
|
2097
|
+
env,
|
|
2098
|
+
enabled: true,
|
|
2099
|
+
required: true,
|
|
2100
|
+
enabled_tools: TOOLS,
|
|
2101
|
+
tools: Object.fromEntries(TOOLS.map((name) => [name, { approval_mode: "approve" }]))
|
|
2102
|
+
};
|
|
1992
2103
|
}
|
|
1993
2104
|
async function prepare(ctx) {
|
|
1994
2105
|
let root;
|
|
@@ -2234,6 +2345,16 @@ var init_codex_review_isolation = __esm(() => {
|
|
|
2234
2345
|
codexReviewerBackend = {
|
|
2235
2346
|
id: "codex",
|
|
2236
2347
|
prepare,
|
|
2348
|
+
toolTrace(event) {
|
|
2349
|
+
const row = event;
|
|
2350
|
+
const item = row?.item;
|
|
2351
|
+
if (row?.type !== "item.completed" || item?.type !== "mcp_tool_call" || item.server !== "agent-hub" || item.tool !== "hub_review" && item.tool !== "hub_task_show")
|
|
2352
|
+
return;
|
|
2353
|
+
const failure = typeof item.error === "string" ? item.error : item.error?.message;
|
|
2354
|
+
const refused = item.result?.isError === true && Array.isArray(item.result.content) ? item.result.content.filter((value) => value?.type === "text" && typeof value.text === "string").map((value) => value.text).join(`
|
|
2355
|
+
`) : undefined;
|
|
2356
|
+
return { tool: item.tool, ...typeof failure === "string" ? { refusal: failure } : typeof refused === "string" ? { refusal: refused } : {} };
|
|
2357
|
+
},
|
|
2237
2358
|
usage(event) {
|
|
2238
2359
|
const row = event;
|
|
2239
2360
|
return row?.type === "turn.completed" && row.usage ? { input_tokens: row.usage.input_tokens, output_tokens: row.usage.output_tokens, cache_read_input_tokens: row.usage.cached_input_tokens } : undefined;
|
|
@@ -4670,12 +4791,696 @@ var init_jcode = __esm(() => {
|
|
|
4670
4791
|
};
|
|
4671
4792
|
});
|
|
4672
4793
|
|
|
4673
|
-
// src/pi/
|
|
4794
|
+
// src/pi/process-signature.ts
|
|
4795
|
+
var init_process_signature = __esm(() => {
|
|
4796
|
+
init_platform();
|
|
4797
|
+
});
|
|
4798
|
+
|
|
4799
|
+
// src/adapters/qwen-dual.ts
|
|
4800
|
+
import { closeSync as closeSync4, constants as constants3, fstatSync as fstatSync4, lstatSync as lstatSync7, mkdirSync as mkdirSync5, openSync as openSync4, readSync as readSync3, realpathSync as realpathSync2, renameSync as renameSync3, writeFileSync as writeFileSync5, writeSync } from "fs";
|
|
4674
4801
|
import { join as join8 } from "path";
|
|
4802
|
+
function qwenDualNamesStateDir(input, stateDir) {
|
|
4803
|
+
const needles = new Set([".agenthub/state", ".agenthub\\state", "qwen-dual", "input.jsonl", stateDir]);
|
|
4804
|
+
try {
|
|
4805
|
+
needles.add(realpathSync2(stateDir));
|
|
4806
|
+
} catch {}
|
|
4807
|
+
const seen = (value, depth) => {
|
|
4808
|
+
if (depth > 8)
|
|
4809
|
+
return true;
|
|
4810
|
+
if (typeof value === "string") {
|
|
4811
|
+
const flat = value.replaceAll("\\", "/");
|
|
4812
|
+
return [...needles].some((n) => n && (value.includes(n) || flat.includes(n.replaceAll("\\", "/"))));
|
|
4813
|
+
}
|
|
4814
|
+
if (Array.isArray(value))
|
|
4815
|
+
return value.some((item) => seen(item, depth + 1));
|
|
4816
|
+
if (value && typeof value === "object")
|
|
4817
|
+
return Object.entries(value).some(([key, item]) => seen(key, depth + 1) || seen(item, depth + 1));
|
|
4818
|
+
return false;
|
|
4819
|
+
};
|
|
4820
|
+
return seen(input, 0);
|
|
4821
|
+
}
|
|
4822
|
+
var SUBMIT_CAP, READ_CAP, ANSWERED_CAP = 256, QWEN_STALLED_NOTE = "stalled: Qwen has said nothing for a while; the hub cannot stop a Qwen turn, press Esc in the Qwen terminal", QWEN_DETACHED_NOTE = "detached: the hub cannot read Qwen's event file; Qwen keeps working in its terminal and the hub reattaches when the file is readable again", DIR = "qwen-dual", QwenDualPeer;
|
|
4823
|
+
var init_qwen_dual = __esm(() => {
|
|
4824
|
+
init_envelope();
|
|
4825
|
+
init_child_process();
|
|
4826
|
+
init_process_signature();
|
|
4827
|
+
init_compat();
|
|
4828
|
+
init_qwen_native();
|
|
4829
|
+
SUBMIT_CAP = 256 * 1024;
|
|
4830
|
+
READ_CAP = 1024 * 1024;
|
|
4831
|
+
QwenDualPeer = class QwenDualPeer extends BasePeer {
|
|
4832
|
+
opts;
|
|
4833
|
+
permissionModeState = "unmanaged";
|
|
4834
|
+
dir;
|
|
4835
|
+
inputFile;
|
|
4836
|
+
eventFile;
|
|
4837
|
+
stateFile;
|
|
4838
|
+
mcpFile;
|
|
4839
|
+
signature;
|
|
4840
|
+
poller;
|
|
4841
|
+
monitor;
|
|
4842
|
+
state_ = { v: 1, offset: 0, generation: 0 };
|
|
4843
|
+
resume = false;
|
|
4844
|
+
_launch;
|
|
4845
|
+
stopping = true;
|
|
4846
|
+
requestedStopReason;
|
|
4847
|
+
detached = false;
|
|
4848
|
+
sessionSeen = false;
|
|
4849
|
+
primed = false;
|
|
4850
|
+
turn;
|
|
4851
|
+
owner;
|
|
4852
|
+
lastAssistant;
|
|
4853
|
+
pending = new Map;
|
|
4854
|
+
answered = new Set;
|
|
4855
|
+
stalled_ = false;
|
|
4856
|
+
exited = false;
|
|
4857
|
+
pollFailures = 0;
|
|
4858
|
+
discarding = false;
|
|
4859
|
+
constructor(id, opts) {
|
|
4860
|
+
super(id, opts.watchdogMs);
|
|
4861
|
+
this.opts = opts;
|
|
4862
|
+
this.dir = join8(opts.stateDir, DIR);
|
|
4863
|
+
this.inputFile = join8(this.dir, "input.jsonl");
|
|
4864
|
+
this.eventFile = join8(this.dir, "events.jsonl");
|
|
4865
|
+
this.stateFile = join8(this.dir, "state.json");
|
|
4866
|
+
this.mcpFile = join8(this.dir, "mcp.json");
|
|
4867
|
+
this.signature = opts.signature ?? processSignature;
|
|
4868
|
+
}
|
|
4869
|
+
get launch() {
|
|
4870
|
+
return this._launch;
|
|
4871
|
+
}
|
|
4872
|
+
get adopted() {
|
|
4873
|
+
return this.adopting;
|
|
4874
|
+
}
|
|
4875
|
+
adopting = false;
|
|
4876
|
+
get stalled() {
|
|
4877
|
+
return this.stalled_;
|
|
4878
|
+
}
|
|
4879
|
+
get statusNotice() {
|
|
4880
|
+
if (this.detached)
|
|
4881
|
+
return { code: "qwen-detached", message: QWEN_DETACHED_NOTE };
|
|
4882
|
+
return this.stalled_ ? { code: "qwen-stalled", message: QWEN_STALLED_NOTE } : undefined;
|
|
4883
|
+
}
|
|
4884
|
+
get isDetached() {
|
|
4885
|
+
return this.detached;
|
|
4886
|
+
}
|
|
4887
|
+
holdsTerminal() {
|
|
4888
|
+
if (this.stopping || this.exited)
|
|
4889
|
+
return false;
|
|
4890
|
+
if (this.state_.ownerPid !== undefined)
|
|
4891
|
+
return this.ownerAlive();
|
|
4892
|
+
return this.sessionSeen;
|
|
4893
|
+
}
|
|
4894
|
+
get sessionId() {
|
|
4895
|
+
return this.state_.sessionId;
|
|
4896
|
+
}
|
|
4897
|
+
get offset() {
|
|
4898
|
+
return this.state_.offset;
|
|
4899
|
+
}
|
|
4900
|
+
recoveryMetadata() {
|
|
4901
|
+
return { ...this.state_.sessionId ? { sessionId: this.state_.sessionId } : {}, launch: { mode: "tui" } };
|
|
4902
|
+
}
|
|
4903
|
+
ensureFile(path, create) {
|
|
4904
|
+
const found = lstatSync7(path, { throwIfNoEntry: false });
|
|
4905
|
+
if (!found) {
|
|
4906
|
+
if (!create)
|
|
4907
|
+
throw new Error("Qwen dual-output file is missing");
|
|
4908
|
+
writeFileSync5(path, "", { mode: 384, flag: "wx" });
|
|
4909
|
+
return;
|
|
4910
|
+
}
|
|
4911
|
+
if (!found.isFile() || found.isSymbolicLink())
|
|
4912
|
+
throw new Error("Qwen dual-output path is not a plain file");
|
|
4913
|
+
if (typeof process.getuid === "function" && found.uid !== process.getuid())
|
|
4914
|
+
throw new Error("Qwen dual-output file is not owned by the person running the hub");
|
|
4915
|
+
if ((found.mode & 63) !== 0)
|
|
4916
|
+
throw new Error("Qwen dual-output file is readable or writable by others");
|
|
4917
|
+
}
|
|
4918
|
+
loadState() {
|
|
4919
|
+
let fd;
|
|
4920
|
+
try {
|
|
4921
|
+
fd = openSync4(this.stateFile, constants3.O_RDONLY | constants3.O_NOFOLLOW | constants3.O_NONBLOCK);
|
|
4922
|
+
const info = fstatSync4(fd);
|
|
4923
|
+
if (!info.isFile() || info.size > 4096)
|
|
4924
|
+
return;
|
|
4925
|
+
const buffer = Buffer.alloc(info.size);
|
|
4926
|
+
readSync3(fd, buffer, 0, info.size, 0);
|
|
4927
|
+
const raw = JSON.parse(buffer.toString("utf8"));
|
|
4928
|
+
if (raw.v !== 1 || !Number.isSafeInteger(raw.offset) || raw.offset < 0 || !Number.isSafeInteger(raw.generation))
|
|
4929
|
+
return;
|
|
4930
|
+
this.state_ = {
|
|
4931
|
+
v: 1,
|
|
4932
|
+
offset: raw.offset,
|
|
4933
|
+
generation: raw.generation,
|
|
4934
|
+
...qwenSessionId(raw.sessionId) ? { sessionId: raw.sessionId } : {},
|
|
4935
|
+
...Number.isSafeInteger(raw.ownerPid) && typeof raw.ownerSignature === "string" ? { ownerPid: raw.ownerPid, ownerSignature: raw.ownerSignature } : {}
|
|
4936
|
+
};
|
|
4937
|
+
} catch {} finally {
|
|
4938
|
+
if (fd !== undefined)
|
|
4939
|
+
try {
|
|
4940
|
+
closeSync4(fd);
|
|
4941
|
+
} catch {}
|
|
4942
|
+
}
|
|
4943
|
+
}
|
|
4944
|
+
saveState() {
|
|
4945
|
+
const temp = `${this.stateFile}.${process.pid}.tmp`;
|
|
4946
|
+
try {
|
|
4947
|
+
writeFileSync5(temp, `${JSON.stringify(this.state_)}
|
|
4948
|
+
`, { mode: 384 });
|
|
4949
|
+
renameSync3(temp, this.stateFile);
|
|
4950
|
+
} catch {
|
|
4951
|
+
this.opts.log?.(`[${this.id}] could not record its Qwen read position`);
|
|
4952
|
+
}
|
|
4953
|
+
}
|
|
4954
|
+
ownerAlive() {
|
|
4955
|
+
const { ownerPid, ownerSignature } = this.state_;
|
|
4956
|
+
if (ownerPid === undefined || ownerSignature === undefined)
|
|
4957
|
+
return false;
|
|
4958
|
+
return this.signature(ownerPid) === ownerSignature;
|
|
4959
|
+
}
|
|
4960
|
+
async start() {
|
|
4961
|
+
mkdirSync5(this.dir, { recursive: true, mode: 448 });
|
|
4962
|
+
const dir = lstatSync7(this.dir);
|
|
4963
|
+
if (!dir.isDirectory() || dir.isSymbolicLink() || typeof process.getuid === "function" && dir.uid !== process.getuid() || (dir.mode & 63) !== 0)
|
|
4964
|
+
throw new Error("Qwen dual-output directory is not private to the person running the hub");
|
|
4965
|
+
this.loadState();
|
|
4966
|
+
for (const path of [this.inputFile, this.eventFile])
|
|
4967
|
+
this.ensureFile(path, true);
|
|
4968
|
+
this.stopping = false;
|
|
4969
|
+
this.exited = false;
|
|
4970
|
+
if (this.ownerAlive()) {
|
|
4971
|
+
this.adopting = true;
|
|
4972
|
+
const probe = openSync4(this.eventFile, constants3.O_RDONLY | constants3.O_NOFOLLOW);
|
|
4973
|
+
let size = 0;
|
|
4974
|
+
try {
|
|
4975
|
+
size = fstatSync4(probe).size;
|
|
4976
|
+
} finally {
|
|
4977
|
+
closeSync4(probe);
|
|
4978
|
+
}
|
|
4979
|
+
if (size < this.state_.offset) {
|
|
4980
|
+
this.state_.offset = 0;
|
|
4981
|
+
this.state_.generation++;
|
|
4982
|
+
}
|
|
4983
|
+
this.sessionSeen = this.state_.sessionId !== undefined;
|
|
4984
|
+
this.primed = true;
|
|
4985
|
+
this.saveState();
|
|
4986
|
+
} else {
|
|
4987
|
+
this.adopting = false;
|
|
4988
|
+
this.resume = this.opts.sessionId !== undefined || this.state_.sessionId !== undefined;
|
|
4989
|
+
const resumeId = this.opts.sessionId ?? this.state_.sessionId;
|
|
4990
|
+
const previous = lstatSync7(this.eventFile);
|
|
4991
|
+
if (previous.size > 0) {
|
|
4992
|
+
try {
|
|
4993
|
+
renameSync3(this.eventFile, join8(this.dir, "events.prev.jsonl"));
|
|
4994
|
+
} catch {}
|
|
4995
|
+
this.ensureFile(this.eventFile, true);
|
|
4996
|
+
}
|
|
4997
|
+
closeSync4(openSync4(this.inputFile, constants3.O_WRONLY | constants3.O_NOFOLLOW | constants3.O_TRUNC));
|
|
4998
|
+
this.state_ = { v: 1, offset: 0, generation: this.state_.generation + 1, ...resumeId ? { sessionId: resumeId } : {} };
|
|
4999
|
+
this.sessionSeen = false;
|
|
5000
|
+
this.saveState();
|
|
5001
|
+
this.writeLaunch(resumeId);
|
|
5002
|
+
}
|
|
5003
|
+
this.poller = setInterval(() => this.poll(), this.opts.pollMs ?? 100);
|
|
5004
|
+
this.poller.unref?.();
|
|
5005
|
+
if (!this.monitor) {
|
|
5006
|
+
this.monitor = setInterval(() => this.checkOwner(), this.opts.ownerMs ?? 1000);
|
|
5007
|
+
this.monitor.unref?.();
|
|
5008
|
+
}
|
|
5009
|
+
this.poll();
|
|
5010
|
+
if (this.adopting && !this.detached && this.sessionSeen)
|
|
5011
|
+
this.setState("idle");
|
|
5012
|
+
}
|
|
5013
|
+
writeLaunch(resumeId) {
|
|
5014
|
+
const [bin, ...rest] = this.opts.cmd.filter((word) => word !== "--acp");
|
|
5015
|
+
if (!bin)
|
|
5016
|
+
throw new Error("qwen_cmd must name an executable");
|
|
5017
|
+
const servers = Object.fromEntries(this.opts.mcpServers.map((server) => [server.name, { command: server.command, args: server.args, env: server.env }]));
|
|
5018
|
+
writeFileSync5(this.mcpFile, JSON.stringify({ mcpServers: servers }), { mode: 384 });
|
|
5019
|
+
const args = [
|
|
5020
|
+
...rest,
|
|
5021
|
+
"--input-file",
|
|
5022
|
+
this.inputFile,
|
|
5023
|
+
"--json-file",
|
|
5024
|
+
this.eventFile,
|
|
5025
|
+
"--mcp-config",
|
|
5026
|
+
this.mcpFile,
|
|
5027
|
+
"--approval-mode",
|
|
5028
|
+
"default",
|
|
5029
|
+
...this.opts.model ? ["--model", this.opts.model] : [],
|
|
5030
|
+
...resumeId ? ["-r", resumeId] : []
|
|
5031
|
+
];
|
|
5032
|
+
this._launch = { cmd: bin, args, env: peerChildEnv(this.id, {}) };
|
|
5033
|
+
}
|
|
5034
|
+
discoverOwner() {
|
|
5035
|
+
if (this.stopping || this.state_.ownerPid !== undefined)
|
|
5036
|
+
return;
|
|
5037
|
+
const rows = (this.opts.table ?? processTable)();
|
|
5038
|
+
if (!rows)
|
|
5039
|
+
return;
|
|
5040
|
+
const flag = `--input-file ${this.inputFile}`;
|
|
5041
|
+
const matches = rows.filter((row) => row.pid !== process.pid && row.command.includes(flag));
|
|
5042
|
+
const pids = new Set(matches.map((row) => row.pid));
|
|
5043
|
+
const roots = matches.filter((row) => !pids.has(row.ppid));
|
|
5044
|
+
if (roots.length !== 1)
|
|
5045
|
+
return;
|
|
5046
|
+
const pid = roots[0].pid, signature = this.signature(pid);
|
|
5047
|
+
if (!signature)
|
|
5048
|
+
return;
|
|
5049
|
+
this.state_.ownerPid = pid;
|
|
5050
|
+
this.state_.ownerSignature = signature;
|
|
5051
|
+
this.saveState();
|
|
5052
|
+
}
|
|
5053
|
+
checkOwner() {
|
|
5054
|
+
if (this.stopping || this.exited)
|
|
5055
|
+
return;
|
|
5056
|
+
if (this.state_.ownerPid === undefined) {
|
|
5057
|
+
if (this.sessionSeen)
|
|
5058
|
+
this.discoverOwner();
|
|
5059
|
+
return;
|
|
5060
|
+
}
|
|
5061
|
+
if (this.ownerAlive())
|
|
5062
|
+
return;
|
|
5063
|
+
if (this.state === "offline" && !this.sessionSeen)
|
|
5064
|
+
return;
|
|
5065
|
+
this.exited = true;
|
|
5066
|
+
this.opts.log?.(`[${this.id}] the Qwen TUI exited`);
|
|
5067
|
+
this.failTurn("the Qwen terminal ended before the turn settled; inspect it before retrying");
|
|
5068
|
+
this.abortApprovals();
|
|
5069
|
+
delete this.state_.ownerPid;
|
|
5070
|
+
delete this.state_.ownerSignature;
|
|
5071
|
+
this.saveState();
|
|
5072
|
+
this.setState("offline");
|
|
5073
|
+
this.opts.onExit?.();
|
|
5074
|
+
}
|
|
5075
|
+
appendInput(record) {
|
|
5076
|
+
const line = `${JSON.stringify(record)}
|
|
5077
|
+
`;
|
|
5078
|
+
if (Buffer.byteLength(line) > SUBMIT_CAP)
|
|
5079
|
+
throw new Error("Qwen submit exceeds its size bound");
|
|
5080
|
+
const fd = openSync4(this.inputFile, constants3.O_WRONLY | constants3.O_APPEND | constants3.O_NOFOLLOW);
|
|
5081
|
+
try {
|
|
5082
|
+
const info = fstatSync4(fd);
|
|
5083
|
+
if (!info.isFile() || (info.mode & 63) !== 0 || typeof process.getuid === "function" && info.uid !== process.getuid())
|
|
5084
|
+
throw new Error("Qwen input file is not private");
|
|
5085
|
+
writeSync(fd, line);
|
|
5086
|
+
} finally {
|
|
5087
|
+
closeSync4(fd);
|
|
5088
|
+
}
|
|
5089
|
+
}
|
|
5090
|
+
async deliver(envs, deliveryId) {
|
|
5091
|
+
if (this.state !== "idle" || this.stopping || this.detached) {
|
|
5092
|
+
if (deliveryId)
|
|
5093
|
+
this.delivery({ id: deliveryId, state: "failed_safe", reason: `${this.id} is ${this.state}` });
|
|
5094
|
+
throw new Error(`${this.id} is ${this.state}`);
|
|
5095
|
+
}
|
|
5096
|
+
let text = this.primed || !this.opts.preamble ? renderDigest(envs, this.primed) : `${this.opts.preamble}
|
|
5097
|
+
|
|
5098
|
+
${renderDigest(envs, false)}`;
|
|
5099
|
+
if (/^\s*[\/!@?]/.test(text))
|
|
5100
|
+
text = `[agent-hub]
|
|
5101
|
+
${text}`;
|
|
5102
|
+
const turn = { envs, ...deliveryId ? { deliveryId } : {}, text, started: false, accepted: false };
|
|
5103
|
+
this.turn = turn;
|
|
5104
|
+
this.owner = undefined;
|
|
5105
|
+
this.lastAssistant = undefined;
|
|
5106
|
+
this.setState("busy");
|
|
5107
|
+
try {
|
|
5108
|
+
this.appendInput({ type: "submit", text });
|
|
5109
|
+
} catch (error) {
|
|
5110
|
+
this.turn = undefined;
|
|
5111
|
+
if (deliveryId)
|
|
5112
|
+
this.delivery({ id: deliveryId, state: "failed_safe", reason: "Qwen input file could not be written" });
|
|
5113
|
+
this.setState("idle");
|
|
5114
|
+
throw error instanceof Error ? error : new Error("Qwen input file could not be written");
|
|
5115
|
+
}
|
|
5116
|
+
this.primed = true;
|
|
5117
|
+
}
|
|
5118
|
+
onWatchdog() {
|
|
5119
|
+
if (this.state !== "busy" || this.stalled_)
|
|
5120
|
+
return;
|
|
5121
|
+
if (this.pending.size > 0) {
|
|
5122
|
+
this.touch();
|
|
5123
|
+
return;
|
|
5124
|
+
}
|
|
5125
|
+
this.stalled_ = true;
|
|
5126
|
+
this.opts.log?.(`[${this.id}] turn stalled; the hub cannot stop it (press Esc in the Qwen terminal)`);
|
|
5127
|
+
this.opts.onStalled?.(true);
|
|
5128
|
+
try {
|
|
5129
|
+
this.opts.onNotice?.(`${this.id}: ${QWEN_STALLED_NOTE}`);
|
|
5130
|
+
} catch {}
|
|
5131
|
+
}
|
|
5132
|
+
alive() {
|
|
5133
|
+
if (this.stalled_) {
|
|
5134
|
+
this.stalled_ = false;
|
|
5135
|
+
this.opts.onStalled?.(false);
|
|
5136
|
+
}
|
|
5137
|
+
if (this.state === "busy")
|
|
5138
|
+
this.touch();
|
|
5139
|
+
}
|
|
5140
|
+
detach(reason) {
|
|
5141
|
+
if (this.detached)
|
|
5142
|
+
return;
|
|
5143
|
+
this.detached = true;
|
|
5144
|
+
this.opts.log?.(`[${this.id}] event file ${reason}: detached`);
|
|
5145
|
+
this.failTurn("the Qwen event file became unreadable before the turn settled; inspect the Qwen terminal before retrying");
|
|
5146
|
+
this.abortApprovals();
|
|
5147
|
+
if (this.state !== "offline")
|
|
5148
|
+
this.setState("offline");
|
|
5149
|
+
}
|
|
5150
|
+
poll() {
|
|
5151
|
+
if (this.stopping)
|
|
5152
|
+
return;
|
|
5153
|
+
let fd;
|
|
5154
|
+
try {
|
|
5155
|
+
fd = openSync4(this.eventFile, constants3.O_RDONLY | constants3.O_NOFOLLOW | constants3.O_NONBLOCK);
|
|
5156
|
+
const info = fstatSync4(fd);
|
|
5157
|
+
if (!info.isFile())
|
|
5158
|
+
throw new Error("not a regular file");
|
|
5159
|
+
if (this.detached) {
|
|
5160
|
+
this.detached = false;
|
|
5161
|
+
this.pollFailures = 0;
|
|
5162
|
+
this.opts.log?.(`[${this.id}] event file readable again: reattached`);
|
|
5163
|
+
if (this.sessionSeen && this.state === "offline" && !this.exited)
|
|
5164
|
+
this.setState("idle");
|
|
5165
|
+
}
|
|
5166
|
+
let offset = this.state_.offset;
|
|
5167
|
+
if (info.size < offset) {
|
|
5168
|
+
offset = 0;
|
|
5169
|
+
this.discarding = false;
|
|
5170
|
+
this.state_.generation++;
|
|
5171
|
+
this.opts.log?.(`[${this.id}] event file was replaced; reading from its start`);
|
|
5172
|
+
}
|
|
5173
|
+
while (offset < info.size) {
|
|
5174
|
+
const want = Math.min(info.size - offset, READ_CAP), buffer = Buffer.alloc(want);
|
|
5175
|
+
const got = readSync3(fd, buffer, 0, want, offset);
|
|
5176
|
+
if (got <= 0)
|
|
5177
|
+
break;
|
|
5178
|
+
if (this.discarding) {
|
|
5179
|
+
const newline = buffer.subarray(0, got).indexOf(10);
|
|
5180
|
+
offset += newline < 0 ? got : newline + 1;
|
|
5181
|
+
this.state_.offset = offset;
|
|
5182
|
+
if (newline >= 0)
|
|
5183
|
+
this.discarding = false;
|
|
5184
|
+
continue;
|
|
5185
|
+
}
|
|
5186
|
+
const end = buffer.subarray(0, got).lastIndexOf(10) + 1;
|
|
5187
|
+
if (end === 0) {
|
|
5188
|
+
if (got < READ_CAP)
|
|
5189
|
+
break;
|
|
5190
|
+
this.opts.log?.(`[${this.id}] dropped an event line over ${READ_CAP} bytes`);
|
|
5191
|
+
this.discarding = true;
|
|
5192
|
+
offset += got;
|
|
5193
|
+
this.state_.offset = offset;
|
|
5194
|
+
continue;
|
|
5195
|
+
}
|
|
5196
|
+
offset += end;
|
|
5197
|
+
this.state_.offset = offset;
|
|
5198
|
+
for (const line of buffer.subarray(0, end).toString("utf8").split(`
|
|
5199
|
+
`))
|
|
5200
|
+
if (line.trim())
|
|
5201
|
+
this.parse(line);
|
|
5202
|
+
if (this.stopping)
|
|
5203
|
+
break;
|
|
5204
|
+
}
|
|
5205
|
+
this.saveState();
|
|
5206
|
+
} catch (error) {
|
|
5207
|
+
this.pollFailures++;
|
|
5208
|
+
if (!this.detached)
|
|
5209
|
+
this.detach(`could not be read (${error.code ?? "error"})`);
|
|
5210
|
+
} finally {
|
|
5211
|
+
if (fd !== undefined)
|
|
5212
|
+
try {
|
|
5213
|
+
closeSync4(fd);
|
|
5214
|
+
} catch {}
|
|
5215
|
+
}
|
|
5216
|
+
}
|
|
5217
|
+
parse(line) {
|
|
5218
|
+
let event;
|
|
5219
|
+
try {
|
|
5220
|
+
event = JSON.parse(line);
|
|
5221
|
+
} catch {
|
|
5222
|
+
this.opts.log?.(`[${this.id}] skipped an unreadable event line`);
|
|
5223
|
+
return;
|
|
5224
|
+
}
|
|
5225
|
+
if (!event || typeof event !== "object" || typeof event.type !== "string")
|
|
5226
|
+
return;
|
|
5227
|
+
try {
|
|
5228
|
+
this.handle(event);
|
|
5229
|
+
} catch (error) {
|
|
5230
|
+
this.opts.log?.(`[${this.id}] event handling failed: ${sanitize(error.message)}`);
|
|
5231
|
+
}
|
|
5232
|
+
}
|
|
5233
|
+
handle(event) {
|
|
5234
|
+
this.alive();
|
|
5235
|
+
switch (event.type) {
|
|
5236
|
+
case "system":
|
|
5237
|
+
return this.onSystem(event);
|
|
5238
|
+
case "user":
|
|
5239
|
+
return this.onUser(event);
|
|
5240
|
+
case "assistant":
|
|
5241
|
+
return this.onAssistant(event);
|
|
5242
|
+
case "stream_event":
|
|
5243
|
+
if (event.event?.type === "message_stop")
|
|
5244
|
+
this.onMessageStop();
|
|
5245
|
+
return;
|
|
5246
|
+
case "result":
|
|
5247
|
+
return this.onResult(event);
|
|
5248
|
+
case "control_request":
|
|
5249
|
+
return this.onControlRequest(event);
|
|
5250
|
+
case "control_response":
|
|
5251
|
+
return this.onControlResponse(event);
|
|
5252
|
+
default:
|
|
5253
|
+
return;
|
|
5254
|
+
}
|
|
5255
|
+
}
|
|
5256
|
+
onSystem(event) {
|
|
5257
|
+
if (event.subtype !== "session_start")
|
|
5258
|
+
return;
|
|
5259
|
+
const data = event.data ?? {};
|
|
5260
|
+
const id = event.session_id ?? data.session_id;
|
|
5261
|
+
if (!qwenSessionId(id))
|
|
5262
|
+
return;
|
|
5263
|
+
const verdict = assessVersion("qwen-dual", data.version);
|
|
5264
|
+
const supported = data.supported_events;
|
|
5265
|
+
if (!verdict.ok || data.protocol_version !== 2 || !Array.isArray(supported) || !["user", "assistant", "stream_event", "control_request", "control_response"].every((name) => supported.includes(name))) {
|
|
5266
|
+
this.opts.log?.(`[${this.id}] the Qwen TUI's dual-output contract is not verified; not attached`);
|
|
5267
|
+
try {
|
|
5268
|
+
this.opts.onNotice?.(`${this.id}: this Qwen's dual-output contract is not verified (${verdict.ok ? "protocol or event set" : verdict.reason}); the hub did not attach it`);
|
|
5269
|
+
} catch {}
|
|
5270
|
+
return;
|
|
5271
|
+
}
|
|
5272
|
+
this.state_.sessionId = id;
|
|
5273
|
+
this.sessionSeen = true;
|
|
5274
|
+
this.saveState();
|
|
5275
|
+
this.discoverOwner();
|
|
5276
|
+
if (!this.stopping && !this.detached && this.state === "offline")
|
|
5277
|
+
this.setState("idle");
|
|
5278
|
+
}
|
|
5279
|
+
promptText(event) {
|
|
5280
|
+
const content = event.message?.content;
|
|
5281
|
+
if (typeof content === "string")
|
|
5282
|
+
return content;
|
|
5283
|
+
if (!Array.isArray(content))
|
|
5284
|
+
return;
|
|
5285
|
+
const blocks = content.filter((block) => block?.type === "text" && typeof block.text === "string");
|
|
5286
|
+
return blocks.length ? blocks.map((block) => block.text).join("") : undefined;
|
|
5287
|
+
}
|
|
5288
|
+
onUser(event) {
|
|
5289
|
+
const text = this.promptText(event);
|
|
5290
|
+
if (text === undefined)
|
|
5291
|
+
return;
|
|
5292
|
+
const turn = this.turn;
|
|
5293
|
+
if (turn && !turn.started && (text.trim() === turn.text.trim() || text.trim().startsWith(turn.text.trim().slice(0, 200)))) {
|
|
5294
|
+
turn.started = true;
|
|
5295
|
+
this.owner = "hub";
|
|
5296
|
+
this.lastAssistant = undefined;
|
|
5297
|
+
if (turn.deliveryId && !turn.accepted) {
|
|
5298
|
+
turn.accepted = true;
|
|
5299
|
+
this.delivery({ id: turn.deliveryId, state: "accepted" });
|
|
5300
|
+
}
|
|
5301
|
+
return;
|
|
5302
|
+
}
|
|
5303
|
+
if (turn?.started)
|
|
5304
|
+
this.failTurn("the Qwen turn ended without a result (it was likely stopped in the terminal); inspect it before retrying", true);
|
|
5305
|
+
this.owner = "personal";
|
|
5306
|
+
this.lastAssistant = undefined;
|
|
5307
|
+
if (this.state === "idle")
|
|
5308
|
+
this.setState("busy");
|
|
5309
|
+
}
|
|
5310
|
+
onAssistant(event) {
|
|
5311
|
+
const content = event.message?.content;
|
|
5312
|
+
const blocks = Array.isArray(content) ? content : [];
|
|
5313
|
+
const text = blocks.filter((block) => block?.type === "text" && typeof block.text === "string").map((block) => block.text).join("");
|
|
5314
|
+
const toolUse = event.message?.stop_reason === "tool_use" || blocks.some((block) => block?.type === "tool_use");
|
|
5315
|
+
this.lastAssistant = { text, toolUse };
|
|
5316
|
+
}
|
|
5317
|
+
onMessageStop() {
|
|
5318
|
+
const last = this.lastAssistant;
|
|
5319
|
+
this.lastAssistant = undefined;
|
|
5320
|
+
if (!last || last.toolUse)
|
|
5321
|
+
return;
|
|
5322
|
+
this.endTurn(last.text);
|
|
5323
|
+
}
|
|
5324
|
+
onResult(event) {
|
|
5325
|
+
const text = typeof event.result === "string" ? event.result : this.lastAssistant?.text ?? "";
|
|
5326
|
+
this.endTurn(text);
|
|
5327
|
+
}
|
|
5328
|
+
endTurn(text) {
|
|
5329
|
+
const turn = this.turn, owner = this.owner;
|
|
5330
|
+
this.owner = undefined;
|
|
5331
|
+
this.lastAssistant = undefined;
|
|
5332
|
+
this.abortApprovals();
|
|
5333
|
+
if (owner === "hub" && turn?.started) {
|
|
5334
|
+
this.turn = undefined;
|
|
5335
|
+
const body = text.trim();
|
|
5336
|
+
if (body)
|
|
5337
|
+
this.onMessage?.(body, { inReplyTo: replyParent(turn.envs), to: replyAudience(turn.envs) });
|
|
5338
|
+
if (turn.deliveryId)
|
|
5339
|
+
this.delivery({ id: turn.deliveryId, state: "completed" });
|
|
5340
|
+
this.stalled_ = false;
|
|
5341
|
+
this.opts.onStalled?.(false);
|
|
5342
|
+
this.setState("idle");
|
|
5343
|
+
return;
|
|
5344
|
+
}
|
|
5345
|
+
if (owner === "personal") {
|
|
5346
|
+
this.stalled_ = false;
|
|
5347
|
+
this.opts.onStalled?.(false);
|
|
5348
|
+
if (!this.turn)
|
|
5349
|
+
this.setState("idle");
|
|
5350
|
+
}
|
|
5351
|
+
}
|
|
5352
|
+
failTurn(reason, notIdle = false, known = false) {
|
|
5353
|
+
const turn = this.turn;
|
|
5354
|
+
if (!turn)
|
|
5355
|
+
return;
|
|
5356
|
+
this.turn = undefined;
|
|
5357
|
+
this.owner = undefined;
|
|
5358
|
+
if (turn.deliveryId) {
|
|
5359
|
+
const receipt = { id: turn.deliveryId, state: known ? "completed" : "needs_review", reason };
|
|
5360
|
+
this.delivery(receipt);
|
|
5361
|
+
}
|
|
5362
|
+
try {
|
|
5363
|
+
this.opts.onNotice?.(`${this.id}: ${reason}`);
|
|
5364
|
+
} catch {}
|
|
5365
|
+
if (this.stalled_) {
|
|
5366
|
+
this.stalled_ = false;
|
|
5367
|
+
this.opts.onStalled?.(false);
|
|
5368
|
+
}
|
|
5369
|
+
if (!notIdle && this.state === "busy")
|
|
5370
|
+
this.setState("idle");
|
|
5371
|
+
}
|
|
5372
|
+
abortApprovals() {
|
|
5373
|
+
for (const { controller } of this.pending.values())
|
|
5374
|
+
controller.abort();
|
|
5375
|
+
this.pending.clear();
|
|
5376
|
+
}
|
|
5377
|
+
onControlRequest(event) {
|
|
5378
|
+
const { request_id: requestId, request } = event;
|
|
5379
|
+
if (typeof requestId !== "string" || !request || request.subtype !== "can_use_tool")
|
|
5380
|
+
return;
|
|
5381
|
+
const turn = this.turn;
|
|
5382
|
+
if (!turn?.started || this.owner !== "hub" || this.stopping || this.state !== "busy")
|
|
5383
|
+
return;
|
|
5384
|
+
const name = typeof request.tool_name === "string" ? request.tool_name : "";
|
|
5385
|
+
const input = request.input ?? {};
|
|
5386
|
+
const hubTool = name !== "" && this.opts.autoApprove?.(name) === true;
|
|
5387
|
+
if (!hubTool && qwenDualNamesStateDir({ input, path: request.blocked_path }, this.opts.guardDir ?? this.opts.stateDir)) {
|
|
5388
|
+
this.opts.log?.(`[${this.id}] approval refused: the call names the hub state dir`);
|
|
5389
|
+
return this.answer(requestId, false, turn, "a Qwen write or command that names the hub state dir was refused: the input file is a prompt channel");
|
|
5390
|
+
}
|
|
5391
|
+
let gate;
|
|
5392
|
+
try {
|
|
5393
|
+
gate = this.state_.sessionId ? this.opts.actionGate?.({ names: [name], input }, this.state_.sessionId) : undefined;
|
|
5394
|
+
} catch {
|
|
5395
|
+
gate = { title: "Action approval: the always-ask check failed; command withheld", denyOnly: true };
|
|
5396
|
+
}
|
|
5397
|
+
if (hubTool && !gate) {
|
|
5398
|
+
this.opts.log?.(`permission auto-approved for ${this.id}: ${name}`);
|
|
5399
|
+
return this.answer(requestId, true, turn);
|
|
5400
|
+
}
|
|
5401
|
+
const full = JSON.stringify(input), cut = full.length > 600;
|
|
5402
|
+
const title = gate ? gate.title : `${sanitize(name || "tool")}: ${cut ? `${full.slice(0, 600)} [cut, ${full.length} chars]` : full}`;
|
|
5403
|
+
const options = [...gate?.denyOnly ? [] : [{ optionId: "allow", name: "Allow", kind: "allow_once" }], { optionId: "deny", name: "Deny", kind: "reject_once" }];
|
|
5404
|
+
const controller = new AbortController;
|
|
5405
|
+
this.pending.set(requestId, { controller, turn });
|
|
5406
|
+
const tool = /^[\w.:@-]{1,80}$/.test(name) ? name : undefined;
|
|
5407
|
+
(async () => {
|
|
5408
|
+
let picked;
|
|
5409
|
+
try {
|
|
5410
|
+
picked = await this.opts.onPermission?.({ peer: this.id, title, options, ...tool ? { tool } : {}, ...gate ? { classed: true } : {} }, controller.signal);
|
|
5411
|
+
} catch {
|
|
5412
|
+
picked = undefined;
|
|
5413
|
+
}
|
|
5414
|
+
const still = this.pending.get(requestId);
|
|
5415
|
+
if (!still || still.controller !== controller || controller.signal.aborted || this.turn !== turn || this.stopping)
|
|
5416
|
+
return;
|
|
5417
|
+
this.pending.delete(requestId);
|
|
5418
|
+
this.answer(requestId, picked === "allow" && options.some((option) => option.optionId === "allow"), turn);
|
|
5419
|
+
})();
|
|
5420
|
+
}
|
|
5421
|
+
answer(requestId, allowed, turn, why) {
|
|
5422
|
+
try {
|
|
5423
|
+
this.appendInput({ type: "confirmation_response", request_id: requestId, allowed });
|
|
5424
|
+
} catch {
|
|
5425
|
+
this.opts.log?.(`[${this.id}] could not write an approval answer`);
|
|
5426
|
+
try {
|
|
5427
|
+
this.opts.onNotice?.(`${this.id}: the hub could not write its approval answer (the input file is unwritable); answer the prompt in the Qwen terminal`);
|
|
5428
|
+
} catch {}
|
|
5429
|
+
return;
|
|
5430
|
+
}
|
|
5431
|
+
this.answered.add(requestId);
|
|
5432
|
+
while (this.answered.size > ANSWERED_CAP)
|
|
5433
|
+
this.answered.delete(this.answered.values().next().value);
|
|
5434
|
+
if (allowed || this.turn !== turn)
|
|
5435
|
+
return;
|
|
5436
|
+
this.owner = undefined;
|
|
5437
|
+
this.abortApprovals();
|
|
5438
|
+
this.failTurn(why ?? "a hub deny cancelled the Qwen turn (Qwen emits nothing for it); the turn ended without an answer", false, true);
|
|
5439
|
+
}
|
|
5440
|
+
onControlResponse(event) {
|
|
5441
|
+
const response = event.response;
|
|
5442
|
+
const id = response?.request_id;
|
|
5443
|
+
if (typeof id !== "string")
|
|
5444
|
+
return;
|
|
5445
|
+
if (response.subtype === "error") {
|
|
5446
|
+
this.opts.log?.(`[${this.id}] Qwen refused an approval answer (stale or unknown request); not retried`);
|
|
5447
|
+
return;
|
|
5448
|
+
}
|
|
5449
|
+
const waiting = this.pending.get(id);
|
|
5450
|
+
if (waiting && !this.answered.has(id)) {
|
|
5451
|
+
this.pending.delete(id);
|
|
5452
|
+
waiting.controller.abort();
|
|
5453
|
+
this.opts.log?.(`[${this.id}] an approval was answered in the Qwen terminal; the hub's was withdrawn`);
|
|
5454
|
+
}
|
|
5455
|
+
}
|
|
5456
|
+
async stop(reason) {
|
|
5457
|
+
if (reason !== undefined)
|
|
5458
|
+
this.requestedStopReason = reason;
|
|
5459
|
+
this.stopping = true;
|
|
5460
|
+
if (this.poller)
|
|
5461
|
+
clearInterval(this.poller);
|
|
5462
|
+
this.poller = undefined;
|
|
5463
|
+
if (this.monitor)
|
|
5464
|
+
clearInterval(this.monitor);
|
|
5465
|
+
this.monitor = undefined;
|
|
5466
|
+
this.abortApprovals();
|
|
5467
|
+
const turn = this.turn;
|
|
5468
|
+
this.turn = undefined;
|
|
5469
|
+
this.owner = undefined;
|
|
5470
|
+
if (turn?.deliveryId)
|
|
5471
|
+
this.delivery({ id: turn.deliveryId, state: "needs_review", reason: reason ?? "Qwen session stopped before settlement" });
|
|
5472
|
+
this.saveState();
|
|
5473
|
+
this.setState("offline");
|
|
5474
|
+
}
|
|
5475
|
+
};
|
|
5476
|
+
});
|
|
5477
|
+
|
|
5478
|
+
// src/pi/launch.ts
|
|
5479
|
+
import { join as join9 } from "path";
|
|
4675
5480
|
function buildPiLaunch(opts, inherited, extension, bridgePort, bridgeToken) {
|
|
4676
5481
|
const adapter = ADAPTERS.find((adapter) => adapter.start.daemonPort === "pi-rpc");
|
|
4677
|
-
const env = { ...peerChildEnv(opts.instance ?? adapter.kind, inherited), ...opts.hubStateDir !== undefined ? { AGENTHUB_STATE_DIR: opts.hubStateDir } : {}, PI_CODING_AGENT_DIR:
|
|
4678
|
-
const args = [...opts.mode === "headless" ? ["--mode", "rpc"] : [], "--provider", "agent-hub-local", "--model", opts.model ?? modelFor(opts.backend, opts.relay.models), "--models", opts.relay.models.map((model) => `agent-hub-local/${model.id}`).join(","), "--no-builtin-tools", "--no-skills", "--no-prompt-templates", "--no-extensions", "--extension", extension, ...opts.preamble ? ["--append-system-prompt", opts.preamble] : [], "--session-dir",
|
|
5482
|
+
const env = { ...peerChildEnv(opts.instance ?? adapter.kind, inherited), ...opts.hubStateDir !== undefined ? { AGENTHUB_STATE_DIR: opts.hubStateDir } : {}, PI_CODING_AGENT_DIR: join9(opts.stateDir, "pi"), AGENTHUB_PI_BRIDGE_URL: `http://127.0.0.1:${bridgePort}`, AGENTHUB_PI_BRIDGE_TOKEN: bridgeToken, AGENTHUB_PI_OWNER_TOKEN: bridgeToken, AGENTHUB_PI_RELAY_URL: opts.relay.url, AGENTHUB_PI_RELAY_TOKEN: opts.relay.token, AGENTHUB_PI_MODELS: JSON.stringify(opts.relay.models), AGENTHUB_PI_TOOLS: JSON.stringify(opts.tools), AGENTHUB_PI_MAX_STEPS: String(opts.maxSteps ?? 30) };
|
|
5483
|
+
const args = [...opts.mode === "headless" ? ["--mode", "rpc"] : [], "--provider", "agent-hub-local", "--model", opts.model ?? modelFor(opts.backend, opts.relay.models), "--models", opts.relay.models.map((model) => `agent-hub-local/${model.id}`).join(","), "--no-builtin-tools", "--no-skills", "--no-prompt-templates", "--no-extensions", "--extension", extension, ...opts.preamble ? ["--append-system-prompt", opts.preamble] : [], "--session-dir", join9(opts.stateDir, "pi-sessions")];
|
|
4679
5484
|
if (opts.sessionFile)
|
|
4680
5485
|
args.push("--session", opts.sessionFile);
|
|
4681
5486
|
else if (opts.sessionId)
|
|
@@ -4699,21 +5504,16 @@ var init_launch = __esm(() => {
|
|
|
4699
5504
|
init_child_process();
|
|
4700
5505
|
});
|
|
4701
5506
|
|
|
4702
|
-
// src/pi/process-signature.ts
|
|
4703
|
-
var init_process_signature = __esm(() => {
|
|
4704
|
-
init_platform();
|
|
4705
|
-
});
|
|
4706
|
-
|
|
4707
5507
|
// src/hub/recovery-store.ts
|
|
4708
|
-
import { chmodSync, existsSync as existsSync5, mkdirSync as
|
|
5508
|
+
import { chmodSync, existsSync as existsSync5, mkdirSync as mkdirSync6, readFileSync as readFileSync5, renameSync as renameSync4, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
4709
5509
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
4710
5510
|
function atomicPrivateJSON(file, value) {
|
|
4711
5511
|
const temporary = `${file}.${randomUUID3()}.tmp`;
|
|
4712
5512
|
try {
|
|
4713
|
-
|
|
5513
|
+
writeFileSync6(temporary, `${JSON.stringify(value, null, 2)}
|
|
4714
5514
|
`, { mode: 384, flag: "wx" });
|
|
4715
5515
|
chmodSync(temporary, 384);
|
|
4716
|
-
|
|
5516
|
+
renameSync4(temporary, file);
|
|
4717
5517
|
} finally {
|
|
4718
5518
|
rmSync4(temporary, { force: true });
|
|
4719
5519
|
}
|
|
@@ -4724,8 +5524,8 @@ var init_recovery_store = __esm(() => {
|
|
|
4724
5524
|
});
|
|
4725
5525
|
|
|
4726
5526
|
// src/pi/session.ts
|
|
4727
|
-
import { readFileSync as readFileSync6, openSync as
|
|
4728
|
-
import { join as
|
|
5527
|
+
import { readFileSync as readFileSync6, openSync as openSync5, closeSync as closeSync5, fsyncSync, mkdirSync as mkdirSync7 } from "fs";
|
|
5528
|
+
import { join as join10, relative as relative2, resolve as resolve7, dirname as dirname6 } from "path";
|
|
4729
5529
|
function sessionReadFailure(error) {
|
|
4730
5530
|
if (error.code === "ENOENT")
|
|
4731
5531
|
throw new PiSessionUnresumable("missing", error.message, { cause: error });
|
|
@@ -4735,7 +5535,7 @@ function validatePiSession(stateDir, cwd, sessionFile, sessionId) {
|
|
|
4735
5535
|
let file, sessions;
|
|
4736
5536
|
try {
|
|
4737
5537
|
file = realPath(sessionFile);
|
|
4738
|
-
sessions = realPath(
|
|
5538
|
+
sessions = realPath(join10(stateDir, "pi-sessions"));
|
|
4739
5539
|
} catch (error) {
|
|
4740
5540
|
return sessionReadFailure(error);
|
|
4741
5541
|
}
|
|
@@ -4793,11 +5593,11 @@ var init_permission_mode = __esm(() => {
|
|
|
4793
5593
|
});
|
|
4794
5594
|
|
|
4795
5595
|
// src/hub/path-policy.ts
|
|
4796
|
-
import { accessSync, constants as
|
|
4797
|
-
import { dirname as dirname7, isAbsolute as isAbsolute5, join as
|
|
5596
|
+
import { accessSync, constants as constants4, lstatSync as lstatSync8, opendirSync, readlinkSync, statSync as statSync2 } from "fs";
|
|
5597
|
+
import { dirname as dirname7, isAbsolute as isAbsolute5, join as join11, relative as relative3, resolve as resolve8, sep as sep2 } from "path";
|
|
4798
5598
|
function existing(path) {
|
|
4799
5599
|
try {
|
|
4800
|
-
|
|
5600
|
+
lstatSync8(path);
|
|
4801
5601
|
return true;
|
|
4802
5602
|
} catch (error) {
|
|
4803
5603
|
if (error.code === "ENOENT")
|
|
@@ -4813,7 +5613,7 @@ function policyRealPath(path) {
|
|
|
4813
5613
|
throw new Error("path cannot be resolved");
|
|
4814
5614
|
ancestor = parent;
|
|
4815
5615
|
}
|
|
4816
|
-
return
|
|
5616
|
+
return join11(realPath(ancestor), relative3(ancestor, path));
|
|
4817
5617
|
}
|
|
4818
5618
|
function policyTargets(cwd, paths) {
|
|
4819
5619
|
const root = realPath(cwd);
|
|
@@ -4841,14 +5641,14 @@ function directAliasTarget(path) {
|
|
|
4841
5641
|
return;
|
|
4842
5642
|
ancestor = parent;
|
|
4843
5643
|
}
|
|
4844
|
-
if (!
|
|
5644
|
+
if (!lstatSync8(ancestor).isSymbolicLink())
|
|
4845
5645
|
return;
|
|
4846
5646
|
target = resolve8(dirname7(ancestor), readlinkSync(ancestor), relative3(ancestor, target));
|
|
4847
5647
|
}
|
|
4848
5648
|
return;
|
|
4849
5649
|
}
|
|
4850
5650
|
function aliasScope(dir, lexical, actual, includeParents, budget) {
|
|
4851
|
-
accessSync(dir,
|
|
5651
|
+
accessSync(dir, constants4.R_OK);
|
|
4852
5652
|
const handle = opendirSync(dir);
|
|
4853
5653
|
let matched = false;
|
|
4854
5654
|
try {
|
|
@@ -4860,7 +5660,7 @@ function aliasScope(dir, lexical, actual, includeParents, budget) {
|
|
|
4860
5660
|
budget.entries++;
|
|
4861
5661
|
if (!entry.isSymbolicLink())
|
|
4862
5662
|
continue;
|
|
4863
|
-
const target = directAliasTarget(
|
|
5663
|
+
const target = directAliasTarget(join11(dir, entry.name));
|
|
4864
5664
|
if (target !== undefined && (beneath(lexical, target) || beneath(actual, target) || includeParents && (beneath(target, lexical) || beneath(target, actual))))
|
|
4865
5665
|
matched = true;
|
|
4866
5666
|
}
|
|
@@ -4956,7 +5756,7 @@ function globScope(root, lexical, actual, path, includeParents, budget, roots, a
|
|
|
4956
5756
|
if (++budget.entries > 4096)
|
|
4957
5757
|
throw new Error("policy glob discovery unavailable");
|
|
4958
5758
|
const next = advance(pattern, states, entry.name);
|
|
4959
|
-
if (next.size && visit(
|
|
5759
|
+
if (next.size && visit(join11(dir, entry.name), next, depth + 1, chain))
|
|
4960
5760
|
return true;
|
|
4961
5761
|
}
|
|
4962
5762
|
} finally {
|
|
@@ -5054,7 +5854,7 @@ var init_deny = __esm(() => {
|
|
|
5054
5854
|
import { spawn as spawn8, spawnSync as spawnSync3 } from "child_process";
|
|
5055
5855
|
import { existsSync as existsSync6, mkdtempSync as mkdtempSync4, readdirSync, rmSync as rmSync5 } from "fs";
|
|
5056
5856
|
import { homedir as homedir4, tmpdir as tmpdir4 } from "os";
|
|
5057
|
-
import { dirname as dirname9, join as
|
|
5857
|
+
import { dirname as dirname9, join as join12, resolve as resolve9 } from "path";
|
|
5058
5858
|
function developerDir() {
|
|
5059
5859
|
const out = spawnSync3("xcode-select", ["-p"], { encoding: "utf8" });
|
|
5060
5860
|
let dir = out.status === 0 ? out.stdout.trim() : "";
|
|
@@ -5075,7 +5875,7 @@ function moduleGitDirs(root) {
|
|
|
5075
5875
|
const out = memoryGitSync(["-C", root, "rev-parse", "--git-common-dir"], { encoding: "utf8" });
|
|
5076
5876
|
if (out.status !== 0)
|
|
5077
5877
|
return;
|
|
5078
|
-
const modulesRoot =
|
|
5878
|
+
const modulesRoot = join12(resolve9(root, out.stdout.trim()), "modules");
|
|
5079
5879
|
const dirs = [];
|
|
5080
5880
|
const walk = (dir, depth) => {
|
|
5081
5881
|
if (depth > 8)
|
|
@@ -5089,14 +5889,14 @@ function moduleGitDirs(root) {
|
|
|
5089
5889
|
const names = new Set(entries.map((e) => e.name));
|
|
5090
5890
|
if (names.has("HEAD") && names.has("config")) {
|
|
5091
5891
|
dirs.push(dir);
|
|
5092
|
-
const nested =
|
|
5892
|
+
const nested = join12(dir, "modules");
|
|
5093
5893
|
if (names.has("modules"))
|
|
5094
5894
|
walk(nested, depth + 1);
|
|
5095
5895
|
return;
|
|
5096
5896
|
}
|
|
5097
5897
|
for (const e of entries)
|
|
5098
5898
|
if (e.isDirectory())
|
|
5099
|
-
walk(
|
|
5899
|
+
walk(join12(dir, e.name), depth + 1);
|
|
5100
5900
|
};
|
|
5101
5901
|
walk(modulesRoot, 0);
|
|
5102
5902
|
const entries = new Set([modulesRoot]);
|
|
@@ -5107,13 +5907,13 @@ function moduleGitDirs(root) {
|
|
|
5107
5907
|
}
|
|
5108
5908
|
function profile(cwd, network, readAllow = [], deny = [], readOnly = []) {
|
|
5109
5909
|
const home = homedir4();
|
|
5110
|
-
const inHome = (p) => p.startsWith("~/") ?
|
|
5910
|
+
const inHome = (p) => p.startsWith("~/") ? join12(home, p.slice(2)) : p;
|
|
5111
5911
|
const root = realPath(cwd);
|
|
5112
5912
|
const gitDirs = externalGitDirs(root);
|
|
5113
5913
|
const moduleGit = moduleGitDirs(root);
|
|
5114
5914
|
const creds = [".ssh", ".aws", ".gnupg", ".config/gh", ".config/gcloud", ".kube", ".docker", ".netrc", ".npmrc", ".omniroute", ".claude", ".codex", ".kimi-code", "Library/Keychains"];
|
|
5115
5915
|
const dev = developerDir();
|
|
5116
|
-
const readable = [root, ...HOME_READABLE.map((p) =>
|
|
5916
|
+
const readable = [root, ...HOME_READABLE.map((p) => join12(home, p)), ...readAllow.map(inHome), ...gitDirs, ...dev ? [dev] : []];
|
|
5117
5917
|
const subpaths = (paths) => paths.map((p) => `(subpath ${q(p)})`).join(" ");
|
|
5118
5918
|
const globals = (names) => names.map((n) => `(global-name ${q(n)})`).join(" ");
|
|
5119
5919
|
const proxy = typeof network === "object" ? `(allow network-outbound (remote ip ${q(`localhost:${network.proxyPort}`)}))` : undefined;
|
|
@@ -5131,14 +5931,14 @@ function profile(cwd, network, readAllow = [], deny = [], readOnly = []) {
|
|
|
5131
5931
|
'(allow file-ioctl (regex #"^/dev/"))',
|
|
5132
5932
|
...proxy ? [proxy] : network ? ["(allow network*)"] : []
|
|
5133
5933
|
];
|
|
5134
|
-
const places = `${creds.map((c) => `(subpath ${q(
|
|
5934
|
+
const places = `${creds.map((c) => `(subpath ${q(join12(home, c))})`).join(" ")} ${denyRegexes(root, deny, false).join(" ")}`;
|
|
5135
5935
|
return [
|
|
5136
5936
|
"(version 1)",
|
|
5137
5937
|
...start,
|
|
5138
5938
|
"(deny file-write*)",
|
|
5139
5939
|
...readOnly.length ? ['(allow file-write* (literal "/dev/null"))'] : [`(allow file-write* (subpath ${q(root)}) (regex #"^/dev/") ${gitDirs.map((d) => `(subpath ${q(d)})`).join(" ")})`],
|
|
5140
5940
|
`(deny file-write* ${hubWriteRegexes(root, gitDirs, moduleGit).join(" ")})`,
|
|
5141
|
-
`(deny file-read* file-write* ${creds.map((c) => `(subpath ${q(
|
|
5941
|
+
`(deny file-read* file-write* ${creds.map((c) => `(subpath ${q(join12(home, c))})`).join(" ")})`,
|
|
5142
5942
|
`(deny file-read* file-write* ${denyRegexes(root, deny).join(" ")})`,
|
|
5143
5943
|
...network ? [`(allow file-read* ${CA_BUNDLES.map((p) => `(literal ${q(p)})`).join(" ")} (regex ${q("/certifi/cacert\\.pem$")}))`, `(deny file-read* file-write* ${places})`] : []
|
|
5144
5944
|
].join(`
|
|
@@ -5152,7 +5952,7 @@ function sandboxedExec(argv, opts) {
|
|
|
5152
5952
|
if (opts.signal?.aborted)
|
|
5153
5953
|
return Promise.resolve({ code: null, output: "error: command cancelled before execution" });
|
|
5154
5954
|
return new Promise((resolve) => {
|
|
5155
|
-
const own = realPath(mkdtempSync4(
|
|
5955
|
+
const own = realPath(mkdtempSync4(join12(tmpdir4(), "ahub-cmd-")));
|
|
5156
5956
|
const sandbox = `${opts.profile}
|
|
5157
5957
|
(allow file-read* ${opts.readOnlyData ? "" : "file-write* "}process-exec (subpath ${q(own)}))`;
|
|
5158
5958
|
const env = { PATH: process.env.PATH ?? "/usr/bin:/bin", HOME: homedir4(), LANG: process.env.LANG ?? "en_US.UTF-8", TERM: "dumb", ...opts.env, TMPDIR: `${own}/` };
|
|
@@ -5227,8 +6027,8 @@ var init_sandbox = __esm(() => {
|
|
|
5227
6027
|
});
|
|
5228
6028
|
|
|
5229
6029
|
// src/local/tools.ts
|
|
5230
|
-
import { lstatSync as
|
|
5231
|
-
import { dirname as dirname10, join as
|
|
6030
|
+
import { lstatSync as lstatSync9, mkdirSync as mkdirSync8, readFileSync as readFileSync7, statSync as statSync3, writeFileSync as writeFileSync7 } from "fs";
|
|
6031
|
+
import { dirname as dirname10, join as join13, relative as relative4, resolve as resolve10 } from "path";
|
|
5232
6032
|
function guardPath(ctx, path, mode) {
|
|
5233
6033
|
const root = realPath(ctx.cwd);
|
|
5234
6034
|
const abs = resolve10(root, path);
|
|
@@ -5241,7 +6041,7 @@ function guardPath(ctx, path, mode) {
|
|
|
5241
6041
|
} catch {
|
|
5242
6042
|
throw new Error(`${path} goes through a dangling symlink`);
|
|
5243
6043
|
}
|
|
5244
|
-
const real =
|
|
6044
|
+
const real = join13(realExisting, relative4(existing, abs));
|
|
5245
6045
|
const rel = relative4(root, real);
|
|
5246
6046
|
if (rel.startsWith("..") || resolve10(root, rel) !== real)
|
|
5247
6047
|
throw new Error(`${path} is outside the project directory`);
|
|
@@ -5249,7 +6049,7 @@ function guardPath(ctx, path, mode) {
|
|
|
5249
6049
|
throw new Error(`${path} is on the secrets denylist`);
|
|
5250
6050
|
if (mode === "write" && isHubWritePath(rel))
|
|
5251
6051
|
throw new Error(`${path} is not writable by the local worker`);
|
|
5252
|
-
if (mode === "write" && ctx.readOnly?.length && (
|
|
6052
|
+
if (mode === "write" && ctx.readOnly?.length && (lstatSync9(real, { throwIfNoEntry: false })?.nlink ?? 1) > 1)
|
|
5253
6053
|
throw new Error("hardlinked write target cannot be verified with data.readonly");
|
|
5254
6054
|
if (mode === "write" && matchesPolicy(root, path, ctx.readOnly ?? []))
|
|
5255
6055
|
throw new Error("target is protected by data.readonly");
|
|
@@ -5320,8 +6120,8 @@ ${preview(content)}`, "write", ctx.signal, file, undefined, String(a.path), a),
|
|
|
5320
6120
|
file = guardPath(ctx, String(a.path), "write");
|
|
5321
6121
|
if (file !== approvedTarget)
|
|
5322
6122
|
return "error: path target changed during approval; inspect before retrying";
|
|
5323
|
-
|
|
5324
|
-
|
|
6123
|
+
mkdirSync8(dirname10(file), { recursive: true });
|
|
6124
|
+
writeFileSync7(file, content);
|
|
5325
6125
|
return `wrote ${a.path}`;
|
|
5326
6126
|
}
|
|
5327
6127
|
case "edit": {
|
|
@@ -5345,7 +6145,7 @@ ${preview(content)}`, "write", ctx.signal, file, undefined, String(a.path), a),
|
|
|
5345
6145
|
count = text.split(String(a.old)).length - 1;
|
|
5346
6146
|
if (count !== 1)
|
|
5347
6147
|
return `error: file changed during approval; \`old\` must match exactly once, it matched ${count} times`;
|
|
5348
|
-
|
|
6148
|
+
writeFileSync7(file, text.replace(String(a.old), () => String(a.new ?? "")));
|
|
5349
6149
|
return `edited ${a.path}`;
|
|
5350
6150
|
}
|
|
5351
6151
|
case "bash": {
|
|
@@ -5395,7 +6195,7 @@ ${preview(content)}`, "write", ctx.signal, file, undefined, String(a.path), a),
|
|
|
5395
6195
|
}
|
|
5396
6196
|
var APPROVAL_EXPIRED_TEXT = "error: approval expired: no person answered; do not retry this call; hand the task off or stop", ApprovalWaitStop, approvalError = (picked, denied) => picked === true ? undefined : picked === "expired" ? APPROVAL_EXPIRED_TEXT : picked === "aborted" ? "error: approval withdrawn because the turn ended; no operation was executed" : denied, lexists = (p) => {
|
|
5397
6197
|
try {
|
|
5398
|
-
|
|
6198
|
+
lstatSync9(p);
|
|
5399
6199
|
return true;
|
|
5400
6200
|
} catch {
|
|
5401
6201
|
return false;
|
|
@@ -5449,8 +6249,8 @@ var PI_CEILING_KIND = "tool-step-ceiling", PI_CEILING_UNIT = "tool-step";
|
|
|
5449
6249
|
|
|
5450
6250
|
// src/adapters/pi.ts
|
|
5451
6251
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
5452
|
-
import { existsSync as existsSync7, mkdirSync as
|
|
5453
|
-
import { join as
|
|
6252
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync9 } from "fs";
|
|
6253
|
+
import { join as join14, resolve as resolve11 } from "path";
|
|
5454
6254
|
import { spawn as spawn9 } from "child_process";
|
|
5455
6255
|
function ownerStillAlive(pid, signature) {
|
|
5456
6256
|
const current = processSignature(pid);
|
|
@@ -5816,9 +6616,9 @@ var init_pi = __esm(() => {
|
|
|
5816
6616
|
this.approvalAnswerEpoch = undefined;
|
|
5817
6617
|
this.approvalStopReason = "";
|
|
5818
6618
|
this.approvalAbortSent = false;
|
|
5819
|
-
|
|
5820
|
-
const sessions =
|
|
5821
|
-
|
|
6619
|
+
mkdirSync9(this.opts.stateDir, { recursive: true });
|
|
6620
|
+
const sessions = join14(this.opts.stateDir, "pi-sessions");
|
|
6621
|
+
mkdirSync9(sessions, { recursive: true, mode: 448 });
|
|
5822
6622
|
if (this.opts.sessionFile) {
|
|
5823
6623
|
try {
|
|
5824
6624
|
validatePiSession(this.opts.stateDir, this.opts.cwd, this.opts.sessionFile, this.opts.sessionId);
|
|
@@ -5875,7 +6675,7 @@ var init_pi = __esm(() => {
|
|
|
5875
6675
|
return new Response("not found", { status: 404 });
|
|
5876
6676
|
} });
|
|
5877
6677
|
this.server = bridge;
|
|
5878
|
-
const extension = resolve11(
|
|
6678
|
+
const extension = resolve11(join14(import.meta.dir, "../pi/extension.ts"));
|
|
5879
6679
|
const inherited = {};
|
|
5880
6680
|
for (const key of ["PATH", "HOME", "USER", "SHELL", "TMPDIR", "TERM", "TERM_PROGRAM", "LANG", "LC_ALL", "LC_CTYPE", "NO_COLOR", "CODEX_HOME"])
|
|
5881
6681
|
if (process.env[key])
|
|
@@ -8804,8 +9604,8 @@ var init_shipped_roles = __esm(() => {
|
|
|
8804
9604
|
|
|
8805
9605
|
// src/hub/roles.ts
|
|
8806
9606
|
import { createHash as createHash5 } from "crypto";
|
|
8807
|
-
import { constants as
|
|
8808
|
-
import { join as
|
|
9607
|
+
import { constants as constants5, closeSync as closeSync6, fstatSync as fstatSync5, lstatSync as lstatSync10, openSync as openSync6, readSync as readSync4, readdirSync as readdirSync2 } from "fs";
|
|
9608
|
+
import { join as join15 } from "path";
|
|
8809
9609
|
function parseRole(text, stem, layer) {
|
|
8810
9610
|
if (Buffer.byteLength(text) > 8192 || text.includes("\r") || !ID.test(stem))
|
|
8811
9611
|
throw new Error("invalid role size, encoding or file id");
|
|
@@ -8852,24 +9652,24 @@ function parseRole(text, stem, layer) {
|
|
|
8852
9652
|
return { id: stem, name: header.name, group: header.group, summary: header.summary, source: header.source, base: list("base", BASES2), classes: list("classes", CLASSES2), body, review, layer, hash: createHash5("sha256").update(text).digest("hex") };
|
|
8853
9653
|
}
|
|
8854
9654
|
function projectRole(file) {
|
|
8855
|
-
const fd =
|
|
9655
|
+
const fd = openSync6(file, constants5.O_RDONLY | constants5.O_NOFOLLOW | constants5.O_NONBLOCK);
|
|
8856
9656
|
try {
|
|
8857
|
-
const before =
|
|
9657
|
+
const before = fstatSync5(fd);
|
|
8858
9658
|
if (!before.isFile() || before.nlink !== 1 || before.size > 8192)
|
|
8859
9659
|
throw new Error("role must be a bounded regular file");
|
|
8860
9660
|
const bytes = Buffer.alloc(8193);
|
|
8861
9661
|
let length = 0, n = 0;
|
|
8862
|
-
while ((n =
|
|
9662
|
+
while ((n = readSync4(fd, bytes, length, bytes.length - length, null)) > 0) {
|
|
8863
9663
|
length += n;
|
|
8864
9664
|
if (length > 8192)
|
|
8865
9665
|
throw new Error("role exceeds file cap");
|
|
8866
9666
|
}
|
|
8867
|
-
const after =
|
|
9667
|
+
const after = fstatSync5(fd);
|
|
8868
9668
|
if (before.size !== after.size || before.mtimeMs !== after.mtimeMs || length !== after.size)
|
|
8869
9669
|
throw new Error("role changed during read");
|
|
8870
9670
|
return bytes.subarray(0, length).toString("utf8");
|
|
8871
9671
|
} finally {
|
|
8872
|
-
|
|
9672
|
+
closeSync6(fd);
|
|
8873
9673
|
}
|
|
8874
9674
|
}
|
|
8875
9675
|
function loadRoleCatalog(cwd) {
|
|
@@ -8884,9 +9684,9 @@ function loadRoleCatalog(cwd) {
|
|
|
8884
9684
|
if (!cwd)
|
|
8885
9685
|
return { roles, errors };
|
|
8886
9686
|
try {
|
|
8887
|
-
const config =
|
|
9687
|
+
const config = join15(cwd, ".agenthub"), dir = join15(config, "roles");
|
|
8888
9688
|
for (const path of [config, dir]) {
|
|
8889
|
-
const stat =
|
|
9689
|
+
const stat = lstatSync10(path);
|
|
8890
9690
|
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
8891
9691
|
throw new Error("unsafe role directory");
|
|
8892
9692
|
}
|
|
@@ -8896,7 +9696,7 @@ function loadRoleCatalog(cwd) {
|
|
|
8896
9696
|
for (const file of files) {
|
|
8897
9697
|
const id = file.slice(0, -3);
|
|
8898
9698
|
try {
|
|
8899
|
-
roles.set(id, parseRole(projectRole(
|
|
9699
|
+
roles.set(id, parseRole(projectRole(join15(dir, file)), id, "project"));
|
|
8900
9700
|
} catch {
|
|
8901
9701
|
errors.push({ id: ID.test(id) ? id : "invalid-file-id", reason: "invalid or unsafe project role" });
|
|
8902
9702
|
}
|
|
@@ -9289,7 +10089,7 @@ var init_config = __esm(() => {
|
|
|
9289
10089
|
});
|
|
9290
10090
|
|
|
9291
10091
|
// src/hub/routing.ts
|
|
9292
|
-
import { join as
|
|
10092
|
+
import { join as join16, resolve as resolve12 } from "path";
|
|
9293
10093
|
function effectiveLocalRoute(defaultRoute, classRoute, classFixedModel) {
|
|
9294
10094
|
return classRoute ?? (classFixedModel === undefined ? defaultRoute : undefined);
|
|
9295
10095
|
}
|
|
@@ -9300,7 +10100,7 @@ var init_routing = __esm(() => {
|
|
|
9300
10100
|
init_spec_read();
|
|
9301
10101
|
init_config();
|
|
9302
10102
|
init_stage();
|
|
9303
|
-
TEMPLATE =
|
|
10103
|
+
TEMPLATE = join16(import.meta.dir, "..", "..", "templates", "routing.toml");
|
|
9304
10104
|
cache = new Map;
|
|
9305
10105
|
});
|
|
9306
10106
|
|
|
@@ -9851,7 +10651,7 @@ var init_launch2 = __esm(() => {
|
|
|
9851
10651
|
|
|
9852
10652
|
// src/adapters/claude-task-sessions.ts
|
|
9853
10653
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
9854
|
-
import { mkdirSync as
|
|
10654
|
+
import { mkdirSync as mkdirSync10, readFileSync as readFileSync8 } from "fs";
|
|
9855
10655
|
import { dirname as dirname11 } from "path";
|
|
9856
10656
|
function readState(file) {
|
|
9857
10657
|
let text;
|
|
@@ -9889,7 +10689,7 @@ class ClaudeTaskSessions {
|
|
|
9889
10689
|
}
|
|
9890
10690
|
write(sessions) {
|
|
9891
10691
|
const next = { version: 1, sessions };
|
|
9892
|
-
|
|
10692
|
+
mkdirSync10(dirname11(this.file), { recursive: true, mode: 448 });
|
|
9893
10693
|
atomicPrivateJSON(this.file, next);
|
|
9894
10694
|
this.state = next;
|
|
9895
10695
|
}
|
|
@@ -9934,9 +10734,9 @@ var init_claude_task_sessions = __esm(() => {
|
|
|
9934
10734
|
|
|
9935
10735
|
// src/adapters/claude-implementer.ts
|
|
9936
10736
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
9937
|
-
import { lstatSync as
|
|
10737
|
+
import { lstatSync as lstatSync11, mkdtempSync as mkdtempSync5, rmSync as rmSync6, writeFileSync as writeFileSync8 } from "fs";
|
|
9938
10738
|
import { tmpdir as tmpdir5 } from "os";
|
|
9939
|
-
import { join as
|
|
10739
|
+
import { join as join17 } from "path";
|
|
9940
10740
|
function claudeImplementerArgs(turn, mcp, settings, prompt) {
|
|
9941
10741
|
return [
|
|
9942
10742
|
"-p",
|
|
@@ -9968,10 +10768,10 @@ function claudeImplementerArgs(turn, mcp, settings, prompt) {
|
|
|
9968
10768
|
async function proveClaudeImplementer(peer, command) {
|
|
9969
10769
|
const binary = Bun.which(command[0], { PATH: process.env.PATH }) ?? command[0];
|
|
9970
10770
|
const fingerprint = () => {
|
|
9971
|
-
const file = realPath(binary), stat =
|
|
10771
|
+
const file = realPath(binary), stat = lstatSync11(file);
|
|
9972
10772
|
return [file, stat.dev, stat.ino, stat.size, stat.mtimeMs, stat.ctimeMs].join(":");
|
|
9973
10773
|
};
|
|
9974
|
-
const binding = fingerprint(), home = mkdtempSync5(
|
|
10774
|
+
const binding = fingerprint(), home = mkdtempSync5(join17(tmpdir5(), "ahub-claude-proof-"));
|
|
9975
10775
|
let version = "", timedOut = false;
|
|
9976
10776
|
const child = new OwnedPrintChild({
|
|
9977
10777
|
peer,
|
|
@@ -9980,7 +10780,7 @@ async function proveClaudeImplementer(peer, command) {
|
|
|
9980
10780
|
cwd: home,
|
|
9981
10781
|
input: "",
|
|
9982
10782
|
envMode: "replace",
|
|
9983
|
-
env: { PATH: process.env.PATH, HOME: home, CLAUDE_CONFIG_DIR:
|
|
10783
|
+
env: { PATH: process.env.PATH, HOME: home, CLAUDE_CONFIG_DIR: join17(home, ".claude") },
|
|
9984
10784
|
maxFrameBytes: 4096,
|
|
9985
10785
|
maxOutputBytes: 4096,
|
|
9986
10786
|
onLine: (line) => {
|
|
@@ -10051,7 +10851,7 @@ var init_claude_implementer = __esm(() => {
|
|
|
10051
10851
|
if (options.mode !== undefined && options.mode !== "ask" && options.mode !== "ask-when-needed")
|
|
10052
10852
|
throw new Error("Claude implementer refuses unsupported permission mode");
|
|
10053
10853
|
this.mode = options.mode ?? "ask";
|
|
10054
|
-
this.sessions = new ClaudeTaskSessions(
|
|
10854
|
+
this.sessions = new ClaudeTaskSessions(join17(options.stateDir, claudeStateFile("claude-task-sessions", id)));
|
|
10055
10855
|
}
|
|
10056
10856
|
get permissionModeState() {
|
|
10057
10857
|
return this.mode;
|
|
@@ -10197,9 +10997,9 @@ var init_claude_implementer = __esm(() => {
|
|
|
10197
10997
|
this.options.notify?.(`${this.id}: task #${group.task} session fresh; recorded transcript is missing`);
|
|
10198
10998
|
}
|
|
10199
10999
|
}
|
|
10200
|
-
const launchId = randomUUID8(), directory = mkdtempSync5(
|
|
11000
|
+
const launchId = randomUUID8(), directory = mkdtempSync5(join17(this.options.stateDir, "claude-child-")), settings = join17(directory, "settings.json");
|
|
10201
11001
|
this.cleanup = () => rmSync6(directory, { recursive: true, force: true });
|
|
10202
|
-
|
|
11002
|
+
writeFileSync8(settings, sessionSettings(undefined, { script: this.options.factsHook, stateDir: this.options.stateDir, purpose: "permission" }), { mode: 384, flag: "wx" });
|
|
10203
11003
|
const env = {
|
|
10204
11004
|
AGENTHUB_MODE: "tools",
|
|
10205
11005
|
AGENTHUB_PEER_ID: this.id,
|
|
@@ -10333,6 +11133,14 @@ function createJcode(ctx) {
|
|
|
10333
11133
|
init_jcode();
|
|
10334
11134
|
return new JcodePeer(ctx.daemon.jcode);
|
|
10335
11135
|
}
|
|
11136
|
+
function createQwenDual(ctx) {
|
|
11137
|
+
if (!("qwenDual" in ctx.daemon))
|
|
11138
|
+
throw new Error("Qwen TUI requires its dual-output transport options");
|
|
11139
|
+
if (ctx.permission === "read-only")
|
|
11140
|
+
throw new Error("Qwen has no verified read-only permission mapping");
|
|
11141
|
+
init_qwen_dual();
|
|
11142
|
+
return new QwenDualPeer(ctx.instance, ctx.daemon.qwenDual);
|
|
11143
|
+
}
|
|
10336
11144
|
function createPi(ctx) {
|
|
10337
11145
|
if (!("pi" in ctx.daemon))
|
|
10338
11146
|
throw new Error("Pi requires its Pi transport options");
|
|
@@ -10384,6 +11192,7 @@ var init_native_factories = __esm(() => {
|
|
|
10384
11192
|
DAEMON_PORTS = {
|
|
10385
11193
|
"codex-appserver": (options) => ({ codex: options }),
|
|
10386
11194
|
acp: (options) => ({ acp: options }),
|
|
11195
|
+
"qwen-dual": (options) => ({ qwenDual: options }),
|
|
10387
11196
|
"owned-acp": (options) => ({ jcode: options }),
|
|
10388
11197
|
"pi-rpc": (options) => ({ pi: options }),
|
|
10389
11198
|
"local-worker": (options) => ({ local: options }),
|
|
@@ -10455,9 +11264,9 @@ var init_reviewer_variants = __esm(() => {
|
|
|
10455
11264
|
});
|
|
10456
11265
|
|
|
10457
11266
|
// src/adapters/agy/hooks.ts
|
|
10458
|
-
import { closeSync as
|
|
11267
|
+
import { closeSync as closeSync7, constants as constants6, fstatSync as fstatSync6, lstatSync as lstatSync12, openSync as openSync7, readSync as readSync5 } from "fs";
|
|
10459
11268
|
import { homedir as homedir5 } from "os";
|
|
10460
|
-
import { isAbsolute as isAbsolute6, join as
|
|
11269
|
+
import { isAbsolute as isAbsolute6, join as join18, relative as relative5 } from "path";
|
|
10461
11270
|
import { createHash as createHash6 } from "crypto";
|
|
10462
11271
|
function decodeAgyHook(event, value) {
|
|
10463
11272
|
if (!EVENTS2.includes(event) || !record3(value) || typeof value.conversationId !== "string" || !UUID2.test(value.conversationId) || typeof value.transcriptPath !== "string" || !isAbsolute6(value.transcriptPath) || value.transcriptPath.length > 4096 || !Array.isArray(value.workspacePaths) || !value.workspacePaths.length || value.workspacePaths.length > 32 || !value.workspacePaths.every((path) => typeof path === "string" && path.length <= 4096 && isAbsolute6(path)))
|
|
@@ -10495,19 +11304,19 @@ function readAgyTranscript(session, transcript) {
|
|
|
10495
11304
|
if (!UUID2.test(session))
|
|
10496
11305
|
return empty;
|
|
10497
11306
|
const home = process.env.HOME ?? homedir5();
|
|
10498
|
-
const candidates = ["antigravity-cli", "antigravity", "antigravity-ide"].map((surface) =>
|
|
11307
|
+
const candidates = ["antigravity-cli", "antigravity", "antigravity-ide"].map((surface) => join18(home, ".gemini", surface, "brain", session, ".system_generated/logs/transcript_full.jsonl"));
|
|
10499
11308
|
if (!candidates.includes(transcript))
|
|
10500
11309
|
return empty;
|
|
10501
11310
|
let fd;
|
|
10502
11311
|
try {
|
|
10503
11312
|
let cursor = home;
|
|
10504
11313
|
for (const name of relative5(home, transcript).split("/")) {
|
|
10505
|
-
cursor =
|
|
10506
|
-
if (
|
|
11314
|
+
cursor = join18(cursor, name);
|
|
11315
|
+
if (lstatSync12(cursor).isSymbolicLink())
|
|
10507
11316
|
return empty;
|
|
10508
11317
|
}
|
|
10509
|
-
fd =
|
|
10510
|
-
const stat =
|
|
11318
|
+
fd = openSync7(transcript, constants6.O_RDONLY | constants6.O_NOFOLLOW);
|
|
11319
|
+
const stat = fstatSync6(fd);
|
|
10511
11320
|
if (!stat.isFile() || stat.nlink !== 1)
|
|
10512
11321
|
return empty;
|
|
10513
11322
|
const cap = 4 * 1024 * 1024;
|
|
@@ -10516,7 +11325,7 @@ function readAgyTranscript(session, transcript) {
|
|
|
10516
11325
|
const buffer = Buffer.alloc(stat.size - from);
|
|
10517
11326
|
let bytes = 0;
|
|
10518
11327
|
while (bytes < buffer.length) {
|
|
10519
|
-
const count =
|
|
11328
|
+
const count = readSync5(fd, buffer, bytes, buffer.length - bytes, from + bytes);
|
|
10520
11329
|
if (!count)
|
|
10521
11330
|
break;
|
|
10522
11331
|
bytes += count;
|
|
@@ -10549,7 +11358,7 @@ function readAgyTranscript(session, transcript) {
|
|
|
10549
11358
|
return empty;
|
|
10550
11359
|
} finally {
|
|
10551
11360
|
if (fd !== undefined)
|
|
10552
|
-
|
|
11361
|
+
closeSync7(fd);
|
|
10553
11362
|
}
|
|
10554
11363
|
}
|
|
10555
11364
|
var EVENTS2, UUID2, record3 = (value) => !!value && typeof value === "object" && !Array.isArray(value), AGY_FACTS_HOOK;
|
|
@@ -10628,7 +11437,7 @@ function builtinCliEntries(definitions) {
|
|
|
10628
11437
|
}
|
|
10629
11438
|
// src/adapters/definitions/native-cli-entry-claude.ts
|
|
10630
11439
|
import { readFileSync as readFileSync9 } from "fs";
|
|
10631
|
-
import { join as
|
|
11440
|
+
import { join as join19 } from "path";
|
|
10632
11441
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
10633
11442
|
async function runClaudeEntry(ctx) {
|
|
10634
11443
|
const { args, cwd, stateDir, cliDir, unattendedEnv, projectConfig, fail } = ctx;
|
|
@@ -10679,17 +11488,17 @@ async function runClaudeEntry(ctx) {
|
|
|
10679
11488
|
}
|
|
10680
11489
|
}
|
|
10681
11490
|
let original;
|
|
10682
|
-
for (const file of [
|
|
11491
|
+
for (const file of [join19(executionCwd, ".claude", "settings.local.json"), join19(executionCwd, ".claude", "settings.json"), join19(process.env.HOME ?? "", ".claude", "settings.json")]) {
|
|
10683
11492
|
try {
|
|
10684
11493
|
original ??= JSON.parse(readFileSync9(file, "utf8")).statusLine;
|
|
10685
11494
|
} catch {}
|
|
10686
11495
|
}
|
|
10687
|
-
const facts = claudeObservationHooks(projectConfig(), { script:
|
|
11496
|
+
const facts = claudeObservationHooks(projectConfig(), { script: join19(cliDir, "facts-hook.ts"), stateDir });
|
|
10688
11497
|
let previousLaunch;
|
|
10689
11498
|
try {
|
|
10690
|
-
previousLaunch = JSON.parse(readFileSync9(
|
|
11499
|
+
previousLaunch = JSON.parse(readFileSync9(join19(stateDir, claudeStateFile("claude-launch", peer)), "utf8"));
|
|
10691
11500
|
} catch {}
|
|
10692
|
-
const launch = buildLaunch("claude", args, { unattended: unattendedEnv, statusLine: { script:
|
|
11501
|
+
const launch = buildLaunch("claude", args, { unattended: unattendedEnv, statusLine: { script: join19(cliDir, "statusline-tee.ts"), stateDir, ...original ? { original } : {} }, ...facts ? { facts } : {} });
|
|
10693
11502
|
let result;
|
|
10694
11503
|
try {
|
|
10695
11504
|
let launchRecord;
|
|
@@ -11174,7 +11983,7 @@ var init_native_cli_entry_jcode = () => {};
|
|
|
11174
11983
|
|
|
11175
11984
|
// src/adapters/definitions/native-cli-preview-claude.ts
|
|
11176
11985
|
import { readFileSync as readFileSync10 } from "fs";
|
|
11177
|
-
import { join as
|
|
11986
|
+
import { join as join20 } from "path";
|
|
11178
11987
|
function previewClaude(ctx) {
|
|
11179
11988
|
const { args, cwd, stateDir, cliDir, unattended, config } = ctx;
|
|
11180
11989
|
const { buildLaunch, claudeObservationHooks } = ctx.helpers;
|
|
@@ -11182,13 +11991,13 @@ function previewClaude(ctx) {
|
|
|
11182
11991
|
let envNames = state.envNames;
|
|
11183
11992
|
let launch;
|
|
11184
11993
|
let original;
|
|
11185
|
-
for (const file of [
|
|
11994
|
+
for (const file of [join20(cwd, ".claude/settings.local.json"), join20(cwd, ".claude/settings.json"), join20(process.env.HOME ?? "", ".claude/settings.json")]) {
|
|
11186
11995
|
try {
|
|
11187
11996
|
original ??= JSON.parse(readFileSync10(file, "utf8")).statusLine;
|
|
11188
11997
|
} catch {}
|
|
11189
11998
|
}
|
|
11190
|
-
const facts = claudeObservationHooks(config, { script:
|
|
11191
|
-
launch = buildLaunch("claude", args, { preview: true, unattended, statusLine: { script:
|
|
11999
|
+
const facts = claudeObservationHooks(config, { script: join20(cliDir, "facts-hook.ts"), stateDir });
|
|
12000
|
+
launch = buildLaunch("claude", args, { preview: true, unattended, statusLine: { script: join20(cliDir, "statusline-tee.ts"), stateDir, ...original ? { original: { command: original.command ? REDACTED : "", ...typeof original.refreshInterval === "number" && Number.isFinite(original.refreshInterval) ? { refreshInterval: original.refreshInterval } : {}, ...typeof original.padding === "number" && Number.isFinite(original.padding) ? { padding: original.padding } : {} } } : {} }, ...facts ? { facts } : {} });
|
|
11192
12001
|
const safeArgs = args.map((arg, i) => {
|
|
11193
12002
|
if (args[i - 1] === "--settings")
|
|
11194
12003
|
return "{}";
|
|
@@ -11199,7 +12008,7 @@ function previewClaude(ctx) {
|
|
|
11199
12008
|
return publicArg(arg);
|
|
11200
12009
|
});
|
|
11201
12010
|
const publicLaunch = buildLaunch("claude", safeArgs, { preview: true, unattended, statusLine: {
|
|
11202
|
-
script:
|
|
12011
|
+
script: join20(cliDir, "statusline-tee.ts"),
|
|
11203
12012
|
stateDir,
|
|
11204
12013
|
...original ? { original: { command: original.command ? REDACTED : "", ...typeof original.refreshInterval === "number" && Number.isFinite(original.refreshInterval) ? { refreshInterval: original.refreshInterval } : {}, ...typeof original.padding === "number" && Number.isFinite(original.padding) ? { padding: original.padding } : {} } } : {}
|
|
11205
12014
|
}, facts });
|
|
@@ -11232,7 +12041,7 @@ function previewCodex(ctx) {
|
|
|
11232
12041
|
var init_native_cli_preview_codex = () => {};
|
|
11233
12042
|
|
|
11234
12043
|
// src/adapters/definitions/native-cli-preview-kimi.ts
|
|
11235
|
-
import { join as
|
|
12044
|
+
import { join as join21 } from "path";
|
|
11236
12045
|
function previewKimi(ctx) {
|
|
11237
12046
|
const { args, cwd, stateDir, cliDir, config } = ctx;
|
|
11238
12047
|
const { childEnv, startModeOf, buildKimiLaunch, buildKimiTuiLaunch, nativeLaunchEnv, kimiMcpPlan } = ctx.helpers;
|
|
@@ -11255,14 +12064,14 @@ function previewKimi(ctx) {
|
|
|
11255
12064
|
launch = { ...native, cmd: native.cmd === "kimi" ? "kimi" : REDACTED, args: native.args.map((arg) => arg === "--model" || arg === REDACTED ? arg : REDACTED) };
|
|
11256
12065
|
envNames = Object.keys(nativeLaunchEnv("kimi", childEnv())).concat(["AGENTHUB_STATE_DIR", "AGENTHUB_PROJECT_DIR"]);
|
|
11257
12066
|
}
|
|
11258
|
-
const { file, action, changed, tracked } = kimiMcpPlan(cwd, stateDir,
|
|
12067
|
+
const { file, action, changed, tracked } = kimiMcpPlan(cwd, stateDir, join21(cliDir, "../../plugins/agent-hub/server.js"), mode);
|
|
11259
12068
|
kimiMetadata = { mode, mcp: { file, action, changed, tracked } };
|
|
11260
12069
|
return finishPreview(ctx, launch, envNames, unresolved, kimiMetadata);
|
|
11261
12070
|
}
|
|
11262
12071
|
var init_native_cli_preview_kimi = () => {};
|
|
11263
12072
|
|
|
11264
12073
|
// src/adapters/definitions/native-cli-preview-pi.ts
|
|
11265
|
-
import { join as
|
|
12074
|
+
import { join as join22, resolve as resolve15 } from "path";
|
|
11266
12075
|
function previewPi(ctx) {
|
|
11267
12076
|
const { args, stateDir, cliDir, config } = ctx;
|
|
11268
12077
|
const { relayModelIds, buildPiLaunch, startModeOf } = ctx.helpers;
|
|
@@ -11297,7 +12106,7 @@ function previewPi(ctx) {
|
|
|
11297
12106
|
tools: [],
|
|
11298
12107
|
preamble: REDACTED,
|
|
11299
12108
|
maxSteps: config.pi.max_steps
|
|
11300
|
-
}, {}, resolve15(
|
|
12109
|
+
}, {}, resolve15(join22(cliDir, "../pi/extension.ts")), UNRESOLVED, UNRESOLVED);
|
|
11301
12110
|
launch = pi;
|
|
11302
12111
|
envNames = Object.keys(pi.env).concat(["PATH", "HOME", "USER", "SHELL", "TMPDIR", "TERM", "TERM_PROGRAM", "LANG", "LC_ALL", "LC_CTYPE", "NO_COLOR", "CODEX_HOME"].filter((key) => process.env[key] !== undefined));
|
|
11303
12112
|
if (mode === "tui")
|
|
@@ -11382,8 +12191,8 @@ var init_native_config_policy = __esm(() => {
|
|
|
11382
12191
|
});
|
|
11383
12192
|
|
|
11384
12193
|
// src/adapters/definitions/native-policy-aux.ts
|
|
11385
|
-
import { closeSync as
|
|
11386
|
-
import { isAbsolute as isAbsolute7, join as
|
|
12194
|
+
import { closeSync as closeSync8, constants as constants7, fstatSync as fstatSync7, openSync as openSync8, readdirSync as readdirSync3, readFileSync as readFileSync11 } from "fs";
|
|
12195
|
+
import { isAbsolute as isAbsolute7, join as join23, normalize } from "path";
|
|
11387
12196
|
function claudeCrashPlan(_meta) {
|
|
11388
12197
|
return { how: "claude: the Claude Code plugin reconnects by itself while that session is still open" };
|
|
11389
12198
|
}
|
|
@@ -11428,13 +12237,13 @@ function localCrashPlan(meta) {
|
|
|
11428
12237
|
}
|
|
11429
12238
|
function claudePlugins(root, cwd) {
|
|
11430
12239
|
const enabled = new Map;
|
|
11431
|
-
for (const file of [
|
|
12240
|
+
for (const file of [join23(root, "settings.json"), ...cwd ? [join23(cwd, ".claude", "settings.json"), join23(cwd, ".claude", "settings.local.json")] : []]) {
|
|
11432
12241
|
const map = json(file)?.enabledPlugins;
|
|
11433
12242
|
if (map && typeof map === "object")
|
|
11434
12243
|
for (const [key, on] of Object.entries(map))
|
|
11435
12244
|
enabled.set(key, on === true);
|
|
11436
12245
|
}
|
|
11437
|
-
const installed = json(
|
|
12246
|
+
const installed = json(join23(root, "plugins", "installed_plugins.json"))?.plugins ?? {};
|
|
11438
12247
|
const out = [];
|
|
11439
12248
|
for (const [key, on] of enabled) {
|
|
11440
12249
|
if (!on)
|
|
@@ -11448,7 +12257,7 @@ function claudePlugins(root, cwd) {
|
|
|
11448
12257
|
return out;
|
|
11449
12258
|
}
|
|
11450
12259
|
function codexPlugins(root) {
|
|
11451
|
-
const config = text(
|
|
12260
|
+
const config = text(join23(root, "config.toml"));
|
|
11452
12261
|
if (config === undefined)
|
|
11453
12262
|
return [];
|
|
11454
12263
|
const found = new Map, disabled = new Set;
|
|
@@ -11467,7 +12276,7 @@ function codexPlugins(root) {
|
|
|
11467
12276
|
continue;
|
|
11468
12277
|
let versions = [];
|
|
11469
12278
|
try {
|
|
11470
|
-
versions = readdirSync3(
|
|
12279
|
+
versions = readdirSync3(join23(root, "plugins", "cache", market, plugin));
|
|
11471
12280
|
} catch {}
|
|
11472
12281
|
out.push({ name, version: versions.length === 1 ? version2(versions[0]) : null });
|
|
11473
12282
|
}
|
|
@@ -11487,8 +12296,8 @@ function absoluteContinuityPath(value) {
|
|
|
11487
12296
|
var str3 = (value) => typeof value === "string" && value ? value : undefined, PLUGINS, CLAUDE_ONLY, text = (file) => {
|
|
11488
12297
|
let fd;
|
|
11489
12298
|
try {
|
|
11490
|
-
fd =
|
|
11491
|
-
const st =
|
|
12299
|
+
fd = openSync8(file, constants7.O_RDONLY | constants7.O_NONBLOCK);
|
|
12300
|
+
const st = fstatSync7(fd);
|
|
11492
12301
|
if (!st.isFile() || st.size > 1024 * 1024)
|
|
11493
12302
|
return;
|
|
11494
12303
|
return readFileSync11(fd, "utf8");
|
|
@@ -11496,7 +12305,7 @@ var str3 = (value) => typeof value === "string" && value ? value : undefined, PL
|
|
|
11496
12305
|
return;
|
|
11497
12306
|
} finally {
|
|
11498
12307
|
if (fd !== undefined)
|
|
11499
|
-
|
|
12308
|
+
closeSync8(fd);
|
|
11500
12309
|
}
|
|
11501
12310
|
}, json = (file) => {
|
|
11502
12311
|
try {
|
|
@@ -11505,7 +12314,7 @@ var str3 = (value) => typeof value === "string" && value ? value : undefined, PL
|
|
|
11505
12314
|
} catch {
|
|
11506
12315
|
return;
|
|
11507
12316
|
}
|
|
11508
|
-
}, version2 = (v) => typeof v === "string" && /^[0-9A-Za-z.+_-]{1,40}$/.test(v) ? v : null, claudePluginInventory = (ctx) => claudePlugins(ctx.env.CLAUDE_CONFIG_DIR?.trim() ||
|
|
12317
|
+
}, version2 = (v) => typeof v === "string" && /^[0-9A-Za-z.+_-]{1,40}$/.test(v) ? v : null, claudePluginInventory = (ctx) => claudePlugins(ctx.env.CLAUDE_CONFIG_DIR?.trim() || join23(ctx.home, ".claude"), ctx.cwd), codexPluginInventory = (ctx) => codexPlugins(ctx.env.CODEX_HOME?.trim() || join23(ctx.home, ".codex")), codexToolSemantic = (name, _command) => name.toLowerCase() === "filechange" ? "edit" : undefined;
|
|
11509
12318
|
var init_native_policy_aux = __esm(() => {
|
|
11510
12319
|
PLUGINS = new Map([["claude-mem", "claude-mem"], ["ponytail", "ponytail"], ["open-code-review", "open-code-review"]]);
|
|
11511
12320
|
CLAUDE_ONLY = new Map([["codex@openai-codex", "codex-plugin"]]);
|
|
@@ -11548,9 +12357,9 @@ var init_native_cli_launch_common = __esm(() => {
|
|
|
11548
12357
|
});
|
|
11549
12358
|
|
|
11550
12359
|
// src/adapters/definitions/native-cli-claude-launch.ts
|
|
11551
|
-
import { mkdirSync as
|
|
12360
|
+
import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync9, renameSync as renameSync5, unlinkSync as unlinkSync3, readFileSync as readFileSync12 } from "fs";
|
|
11552
12361
|
import { randomUUID as randomUUID10 } from "crypto";
|
|
11553
|
-
import { resolve as resolve16, join as
|
|
12362
|
+
import { resolve as resolve16, join as join24 } from "path";
|
|
11554
12363
|
function claudeChannel(userArgs) {
|
|
11555
12364
|
const bundle = resolve16(import.meta.dir, "../../../plugins/agent-hub/server.js");
|
|
11556
12365
|
for (let i = 0;i < userArgs.length; i++) {
|
|
@@ -11612,11 +12421,11 @@ function claudeNativeLaunch(ctx) {
|
|
|
11612
12421
|
const stateDir = ctx.facts?.stateDir ?? ctx.statusLine?.stateDir;
|
|
11613
12422
|
if (!stateDir)
|
|
11614
12423
|
throw new Error("Claude session settings require the hub state directory");
|
|
11615
|
-
const file =
|
|
12424
|
+
const file = join24(stateDir, `claude-settings-${randomUUID10()}.json`);
|
|
11616
12425
|
try {
|
|
11617
|
-
|
|
11618
|
-
|
|
11619
|
-
|
|
12426
|
+
mkdirSync11(stateDir, { recursive: true, mode: 448 });
|
|
12427
|
+
writeFileSync9(`${file}.tmp`, settingsArgs[1], { mode: 384, flag: "wx" });
|
|
12428
|
+
renameSync5(`${file}.tmp`, file);
|
|
11620
12429
|
} catch {
|
|
11621
12430
|
try {
|
|
11622
12431
|
unlinkSync3(`${file}.tmp`);
|
|
@@ -11669,7 +12478,7 @@ var init_native_cli_codex_launch = __esm(() => {
|
|
|
11669
12478
|
});
|
|
11670
12479
|
|
|
11671
12480
|
// src/hub/events.ts
|
|
11672
|
-
import { appendFileSync, closeSync as
|
|
12481
|
+
import { appendFileSync, closeSync as closeSync9, existsSync as existsSync8, fstatSync as fstatSync8, openSync as openSync9, readFileSync as readFileSync13, readSync as readSync6 } from "fs";
|
|
11673
12482
|
function readEvents(file, since = 0) {
|
|
11674
12483
|
if (!existsSync8(file))
|
|
11675
12484
|
return [];
|
|
@@ -11691,13 +12500,13 @@ var init_events = () => {};
|
|
|
11691
12500
|
// src/cli/recovery-session-file.ts
|
|
11692
12501
|
import { statSync as statSync4 } from "fs";
|
|
11693
12502
|
import { homedir as homedir6 } from "os";
|
|
11694
|
-
import { join as
|
|
12503
|
+
import { join as join25 } from "path";
|
|
11695
12504
|
function checkedSessionPath(binding, kind) {
|
|
11696
12505
|
if (kind === "rollout")
|
|
11697
|
-
return
|
|
11698
|
-
const config = binding.launch.env.CLAUDE_CONFIG_DIR ??
|
|
12506
|
+
return join25(binding.launch.env.CODEX_HOME ?? join25(homedir6(), ".codex"), "sessions");
|
|
12507
|
+
const config = binding.launch.env.CLAUDE_CONFIG_DIR ?? join25(homedir6(), ".claude");
|
|
11699
12508
|
const slug = binding.projectRoot.replace(/[^a-zA-Z0-9]/g, "-");
|
|
11700
|
-
return
|
|
12509
|
+
return join25(config, "projects", slug, `${binding.sessionId}.jsonl`);
|
|
11701
12510
|
}
|
|
11702
12511
|
function recordedFileState(file) {
|
|
11703
12512
|
try {
|
|
@@ -11710,7 +12519,7 @@ var init_recovery_session_file = () => {};
|
|
|
11710
12519
|
|
|
11711
12520
|
// src/adapters/definitions/native-recovery.ts
|
|
11712
12521
|
import { readdirSync as readdirSync4 } from "fs";
|
|
11713
|
-
import { basename as basename5, join as
|
|
12522
|
+
import { basename as basename5, join as join26 } from "path";
|
|
11714
12523
|
var rolloutState = (binding) => {
|
|
11715
12524
|
try {
|
|
11716
12525
|
return readdirSync4(checkedSessionPath(binding, "rollout"), { recursive: true }).some((name) => String(name).endsWith(".jsonl") && basename5(String(name)).includes(binding.sessionId));
|
|
@@ -11734,7 +12543,7 @@ var init_native_recovery = __esm(() => {
|
|
|
11734
12543
|
if (phase !== "plan")
|
|
11735
12544
|
return false;
|
|
11736
12545
|
let current, started = false, turned = false;
|
|
11737
|
-
for (const e of readEvents(
|
|
12546
|
+
for (const e of readEvents(join26(stateDir, "events.jsonl"))) {
|
|
11738
12547
|
if (e.type === "native_thread" && e.peer === binding.peer) {
|
|
11739
12548
|
current = e.thread;
|
|
11740
12549
|
if (e.thread === binding.sessionId && e.fresh)
|
|
@@ -11798,7 +12607,12 @@ var init_native_recovery = __esm(() => {
|
|
|
11798
12607
|
};
|
|
11799
12608
|
QWEN_RECOVERY_SESSION = {
|
|
11800
12609
|
legacyTargetSupport: { file: "src/hub/peer-catalog.ts", exportName: "OPTIONAL_PEERS", refusal: "target does not support Qwen; end Qwen and make a new plan, or choose a supporting target; runtimes unchanged" },
|
|
11801
|
-
restoreArgs: (peer) => ({ ...startupArgs(peer), ...peer.sessionId ? { "session-id": peer.sessionId } : {} })
|
|
12610
|
+
restoreArgs: (peer) => ({ ...startupArgs(peer), ...peer.sessionId ? { "session-id": peer.sessionId } : {} }),
|
|
12611
|
+
recoveryRefusal: (peer, phase = "plan") => {
|
|
12612
|
+
if (peer.args?.mode !== "tui")
|
|
12613
|
+
return;
|
|
12614
|
+
return phase === "restore" ? "Qwen's own terminal cannot be restored as a headless worker; end it in that terminal and make a new recovery plan" : "qwen: its own terminal has no automatic restore; end it in that terminal, then make a new recovery plan (restart it with ahub qwen --mode tui, which resumes its session)";
|
|
12615
|
+
}
|
|
11802
12616
|
};
|
|
11803
12617
|
});
|
|
11804
12618
|
|
|
@@ -12271,9 +13085,15 @@ var init_builtin_metadata = __esm(() => {
|
|
|
12271
13085
|
});
|
|
12272
13086
|
|
|
12273
13087
|
// src/adapters/definitions/native-cli-entry-qwen.ts
|
|
13088
|
+
function verifiedLaunch(value) {
|
|
13089
|
+
const launch = value;
|
|
13090
|
+
if (!launch || typeof launch.cmd !== "string" || !launch.cmd || !Array.isArray(launch.args) || !launch.args.every((word) => typeof word === "string") || !launch.env || typeof launch.env !== "object" || Array.isArray(launch.env))
|
|
13091
|
+
return;
|
|
13092
|
+
return { cmd: launch.cmd, args: launch.args, env: launch.env };
|
|
13093
|
+
}
|
|
12274
13094
|
async function runQwenEntry(ctx) {
|
|
12275
13095
|
const peer = ctx.instance ?? "qwen";
|
|
12276
|
-
const { args, cwd, stateDir, unattendedEnv, connect, fail, takeFlags } = ctx;
|
|
13096
|
+
const { args, cwd, stateDir, unattendedEnv, connect, fail, takeFlags, execWithEnv } = ctx;
|
|
12277
13097
|
const { launcherPreview } = ctx.helpers;
|
|
12278
13098
|
if (args.includes("--print-command") || args.includes("--dry-run")) {
|
|
12279
13099
|
try {
|
|
@@ -12284,28 +13104,55 @@ async function runQwenEntry(ctx) {
|
|
|
12284
13104
|
}
|
|
12285
13105
|
const { one, rest } = takeFlags(args, ["--mode", "--model", "--session-id"], []);
|
|
12286
13106
|
const adapter = ctx.definition;
|
|
13107
|
+
const usage = "usage: ahub qwen [--mode headless|tui] [--model <alias>] [--session-id <id>]";
|
|
12287
13108
|
if (one["--session-id"] !== undefined && !adapter.session.crashIdentity?.({ sessionId: one["--session-id"] }).sessionId)
|
|
12288
13109
|
fail("invalid Qwen session identity");
|
|
12289
|
-
if (rest.length || one["--mode"] !== undefined && !
|
|
12290
|
-
fail(
|
|
13110
|
+
if (rest.length || one["--mode"] !== undefined && !["headless", "tui"].includes(one["--mode"]))
|
|
13111
|
+
fail(usage);
|
|
13112
|
+
const mode = one["--mode"] === "tui" ? "tui" : "headless";
|
|
13113
|
+
if (mode === "tui" && ctx.instance)
|
|
13114
|
+
fail("extra Qwen instances require headless mode");
|
|
13115
|
+
if (mode === "tui" && !(process.stdin.isTTY && process.stdout.isTTY))
|
|
13116
|
+
fail("Qwen's TUI needs a terminal: run ahub qwen --mode tui in one, or use --mode headless");
|
|
12291
13117
|
const hub = await connect();
|
|
13118
|
+
let launch;
|
|
12292
13119
|
try {
|
|
12293
13120
|
const snapshot = (await hub.request({ t: "status" })).status;
|
|
12294
13121
|
const warning = retainedLaunchWarning(snapshot, peer, ctx.definition);
|
|
12295
13122
|
if (warning)
|
|
12296
13123
|
console.error(warning);
|
|
12297
|
-
const reply = await hub.request({ t: "start", peer, args: { mode
|
|
13124
|
+
const reply = await hub.request({ t: "start", peer, args: { mode, ...one["--model"] ? { model: one["--model"] } : {}, ...one["--session-id"] ? { sessionId: one["--session-id"] } : {} }, operationId: process.env.AGENTHUB_RECOVERY_OPERATION }, 35000);
|
|
12298
13125
|
if (!reply.ok)
|
|
12299
13126
|
fail(reply.error);
|
|
12300
|
-
|
|
13127
|
+
if (mode === "tui") {
|
|
13128
|
+
if (reply.already)
|
|
13129
|
+
return console.log("qwen is already attached (its TUI is running); use that terminal");
|
|
13130
|
+
if (reply.adopted)
|
|
13131
|
+
return console.log("qwen: the hub read on from its saved position; the Qwen terminal it was driving is still running");
|
|
13132
|
+
launch = reply.launch;
|
|
13133
|
+
} else
|
|
13134
|
+
console.log(reply.already ? `${peer} is already attached` : `${peer} attached (headless); use ahub say @${peer} and the console for approvals`);
|
|
12301
13135
|
} finally {
|
|
12302
13136
|
hub.close();
|
|
12303
13137
|
}
|
|
13138
|
+
if (mode !== "tui")
|
|
13139
|
+
return;
|
|
13140
|
+
const verified = verifiedLaunch(launch);
|
|
13141
|
+
if (!verified)
|
|
13142
|
+
return fail("Qwen TUI launch metadata was not verified");
|
|
13143
|
+
const person = Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.startsWith("AGENTHUB_") || name === "AGENTHUB_HOME"));
|
|
13144
|
+
const env = { ...peerChildEnv(peer, person), ...verified.env };
|
|
13145
|
+
delete env.AGENTHUB_RECOVERY_OPERATION;
|
|
13146
|
+
delete env.AGENTHUB_UNATTENDED;
|
|
13147
|
+
console.error("Qwen runs in this terminal, driven by the hub through its dual-output files. Limits: a hub deny ends the turn without an answer, and a stalled turn is stopped only by pressing Esc here.");
|
|
13148
|
+
return execWithEnv(verified.cmd, verified.args, env);
|
|
12304
13149
|
}
|
|
12305
|
-
var init_native_cli_entry_qwen = () => {
|
|
13150
|
+
var init_native_cli_entry_qwen = __esm(() => {
|
|
13151
|
+
init_child_process();
|
|
13152
|
+
});
|
|
12306
13153
|
|
|
12307
13154
|
// src/adapters/definitions/native-cli-preview-qwen.ts
|
|
12308
|
-
import { join as
|
|
13155
|
+
import { join as join27 } from "path";
|
|
12309
13156
|
function previewQwen(ctx) {
|
|
12310
13157
|
const { tool, args, cwd, stateDir, cliDir, config } = ctx;
|
|
12311
13158
|
const state = previewState(args, ctx.baseEnvNames), { unresolved, value } = state;
|
|
@@ -12313,8 +13160,8 @@ function previewQwen(ctx) {
|
|
|
12313
13160
|
let launch;
|
|
12314
13161
|
const mode = value("--mode") ?? "headless", model = value("--model");
|
|
12315
13162
|
const adapter = ctx.definition;
|
|
12316
|
-
if (!
|
|
12317
|
-
throw new Error("Qwen
|
|
13163
|
+
if (!["headless", "tui"].includes(mode))
|
|
13164
|
+
throw new Error("unknown Qwen mode");
|
|
12318
13165
|
for (let i = 0;i < args.length; i += 2)
|
|
12319
13166
|
if (!["--model", "--mode", "--session-id"].includes(args[i]) || !args[i + 1])
|
|
12320
13167
|
throw new Error("unknown Qwen option");
|
|
@@ -12323,12 +13170,19 @@ function previewQwen(ctx) {
|
|
|
12323
13170
|
throw new Error("invalid Qwen session identity");
|
|
12324
13171
|
if (sessionId)
|
|
12325
13172
|
unresolved.push({ field: "sessionId", reason: "requested through ACP session/load at start; preview does not load or expose it" });
|
|
13173
|
+
if (mode === "tui") {
|
|
13174
|
+
const [bin = "qwen", ...prefix] = nativeCommand(config.qwen_cmd, ["qwen"]).filter((word) => word !== "--acp");
|
|
13175
|
+
launch = { cmd: bin, args: [...prefix, "--input-file", "<state dir>/qwen/qwen-dual/input.jsonl", "--json-file", "<state dir>/qwen/qwen-dual/events.jsonl", "--mcp-config", "<state dir>/qwen/qwen-dual/mcp.json", "--approval-mode", "default", ...model ? ["--model", REDACTED] : [], ...sessionId ? ["-r", REDACTED] : []] };
|
|
13176
|
+
unresolved.push({ field: "files", reason: "the daemon prepares the input and event files (0600) when the start is admitted; preview creates nothing" });
|
|
13177
|
+
unresolved.push({ field: "nativeIdentity", reason: `Qwen Code (${describe("qwen-dual")}) is checked before the start and again from its session_start event; preview starts nothing` });
|
|
13178
|
+
return finishPreview(ctx, launch, envNames, unresolved, undefined);
|
|
13179
|
+
}
|
|
12326
13180
|
launch = adapter.start.launch({
|
|
12327
13181
|
instance: tool,
|
|
12328
13182
|
form: "headless",
|
|
12329
13183
|
projectRoot: cwd,
|
|
12330
13184
|
stateDir,
|
|
12331
|
-
entrypoint:
|
|
13185
|
+
entrypoint: join27(cliDir, "main.js"),
|
|
12332
13186
|
userArgs: [],
|
|
12333
13187
|
unattended: false,
|
|
12334
13188
|
env: {},
|
|
@@ -12353,6 +13207,7 @@ var init_qwen = __esm(() => {
|
|
|
12353
13207
|
init_native_recovery();
|
|
12354
13208
|
init_acp();
|
|
12355
13209
|
init_qwen_native();
|
|
13210
|
+
init_native_factories();
|
|
12356
13211
|
QWEN_DEFINITION = {
|
|
12357
13212
|
api: 1,
|
|
12358
13213
|
kind: "qwen",
|
|
@@ -12371,10 +13226,11 @@ var init_qwen = __esm(() => {
|
|
|
12371
13226
|
conductorStartable: true,
|
|
12372
13227
|
hubSwitches: [],
|
|
12373
13228
|
ownedFlags: [],
|
|
12374
|
-
preflight: (request) => request.form !== undefined && request.form !== "headless"
|
|
13229
|
+
preflight: (request) => request.form !== undefined && request.form !== "headless" && request.form !== "tui" ? "unknown Qwen mode; usage: ahub qwen [--mode headless|tui]" : undefined,
|
|
13230
|
+
createTui: createQwenDual,
|
|
12375
13231
|
launch: (context) => {
|
|
12376
13232
|
if (context.form !== "headless")
|
|
12377
|
-
throw new Error("Qwen TUI is
|
|
13233
|
+
throw new Error("the Qwen TUI is launched from the files the daemon prepares; use ahub qwen --mode tui");
|
|
12378
13234
|
const launch = buildQwenLaunch(context.nativeCommand ?? ["qwen", "--acp"], context.model);
|
|
12379
13235
|
const argv = [launch.cmd, ...launch.args];
|
|
12380
13236
|
return { ...launch, metadata: { packageEntrypoint: context.entrypoint, argv, command: argv.map(shellQuote3).join(" "), env: {} } };
|
|
@@ -12414,7 +13270,7 @@ var init_qwen = __esm(() => {
|
|
|
12414
13270
|
turnEnd: "adapter",
|
|
12415
13271
|
budget: { account: "qwen" },
|
|
12416
13272
|
capabilities: { watch: true },
|
|
12417
|
-
cli: { command: "qwen", entry: { lifecycleAdmission: true, execute: runQwenEntry }, preview: previewQwen, usage: [["ahub qwen [--mode headless] [--model <alias>] [--session-id <id>] [--print-command]", `start optional Qwen Code ACP (${describe("qwen")}; roster.qwen, default off)`]], launchConfigKeys: ["qwen_cmd"], startWords: () => ["qwen"] },
|
|
13273
|
+
cli: { command: "qwen", entry: { lifecycleAdmission: true, execute: runQwenEntry }, preview: previewQwen, usage: [["ahub qwen [--mode headless|tui] [--model <alias>] [--session-id <id>] [--print-command]", `start optional Qwen Code ACP (${describe("qwen")}; roster.qwen, default off); --mode tui runs Qwen's own TUI in this terminal, driven through its dual-output files (${describe("qwen-dual")}); a hub deny ends the turn without an answer and a stalled turn is stopped with Esc in the Qwen terminal`]], launchConfigKeys: ["qwen_cmd"], startWords: () => ["qwen"] },
|
|
12418
13274
|
display: QWEN_DISPLAY,
|
|
12419
13275
|
hooks: { recallPlatform: "qwen" }
|
|
12420
13276
|
};
|
|
@@ -12611,6 +13467,9 @@ function immutable(value, seen = new WeakSet) {
|
|
|
12611
13467
|
immutable(member, seen);
|
|
12612
13468
|
return Object.freeze(value);
|
|
12613
13469
|
}
|
|
13470
|
+
function isFixedBuiltinDefinition(definition) {
|
|
13471
|
+
return definition !== undefined && fixedBuiltinDefinitions.has(definition);
|
|
13472
|
+
}
|
|
12614
13473
|
function deriveAdapterLists(definitions) {
|
|
12615
13474
|
const ordered = (field, eligible = (_definition) => true) => definitions.map((definition, index) => ({ definition, index })).filter(({ definition }) => eligible(definition)).sort((left, right) => {
|
|
12616
13475
|
const a = left.definition.projection?.[field], b = right.definition.projection?.[field];
|
|
@@ -12780,7 +13639,7 @@ function assertDefinitionCoherence(definition) {
|
|
|
12780
13639
|
fail("reviewer", "unavailable reviewer must remain metadata-only");
|
|
12781
13640
|
}
|
|
12782
13641
|
function assertNativeCapabilityScope(definition) {
|
|
12783
|
-
if (
|
|
13642
|
+
if (isFixedBuiltinDefinition(definition))
|
|
12784
13643
|
return;
|
|
12785
13644
|
const declared = (block, key) => block !== undefined && (key in block);
|
|
12786
13645
|
const restricted = [
|
|
@@ -12788,7 +13647,10 @@ function assertNativeCapabilityScope(definition) {
|
|
|
12788
13647
|
["attach.admission", declared(definition.attach, "admission")],
|
|
12789
13648
|
["budget.resetSoftTokensOnResume", declared(definition.budget, "resetSoftTokensOnResume")],
|
|
12790
13649
|
["session.crashIdentity", declared(definition.session, "crashIdentity")],
|
|
13650
|
+
["session.nativeRevert", declared(definition.session, "nativeRevert")],
|
|
12791
13651
|
["start.daemonPort pi-rpc", definition.start.daemonPort === "pi-rpc"],
|
|
13652
|
+
["start.daemonPort qwen-dual", definition.start.daemonPort === "qwen-dual"],
|
|
13653
|
+
["start.createTui", declared(definition.start, "createTui")],
|
|
12792
13654
|
["routing", definition.routing !== undefined],
|
|
12793
13655
|
["projection", definition.projection !== undefined],
|
|
12794
13656
|
["instances.nativeHome", declared(definition.instances, "nativeHome")],
|
|
@@ -20192,7 +21054,7 @@ init_project();
|
|
|
20192
21054
|
// src/hub/instance-config.ts
|
|
20193
21055
|
init_registry();
|
|
20194
21056
|
import { readFileSync as readFileSync15 } from "fs";
|
|
20195
|
-
import { join as
|
|
21057
|
+
import { join as join30 } from "path";
|
|
20196
21058
|
|
|
20197
21059
|
// src/adapters/external-metadata.ts
|
|
20198
21060
|
import { types as types3 } from "util";
|
|
@@ -20269,8 +21131,8 @@ init_registry();
|
|
|
20269
21131
|
// src/hub/config-trust.ts
|
|
20270
21132
|
init_child_process();
|
|
20271
21133
|
init_git();
|
|
20272
|
-
import { lstatSync as
|
|
20273
|
-
import { dirname as dirname12, join as
|
|
21134
|
+
import { lstatSync as lstatSync13, statSync as statSync5 } from "fs";
|
|
21135
|
+
import { dirname as dirname12, join as join28 } from "path";
|
|
20274
21136
|
var MACHINE_LOCAL = [
|
|
20275
21137
|
"kimi_cmd",
|
|
20276
21138
|
"permission_modes",
|
|
@@ -20302,7 +21164,7 @@ function configTracked(cwd, name) {
|
|
|
20302
21164
|
return;
|
|
20303
21165
|
let opened;
|
|
20304
21166
|
try {
|
|
20305
|
-
opened = statSync5(
|
|
21167
|
+
opened = statSync5(join28(cwd, ".agenthub", name));
|
|
20306
21168
|
} catch {}
|
|
20307
21169
|
return r.stdout.split("\x00").filter(Boolean).some((row) => {
|
|
20308
21170
|
const path = row.slice(row.indexOf("\t") + 1);
|
|
@@ -20311,7 +21173,7 @@ function configTracked(cwd, name) {
|
|
|
20311
21173
|
if (!opened)
|
|
20312
21174
|
return path === `.agenthub/${name}`;
|
|
20313
21175
|
try {
|
|
20314
|
-
const s = statSync5(
|
|
21176
|
+
const s = statSync5(join28(cwd, path));
|
|
20315
21177
|
return !!opened && s.dev === opened.dev && s.ino === opened.ino;
|
|
20316
21178
|
} catch {
|
|
20317
21179
|
return false;
|
|
@@ -20368,7 +21230,7 @@ function readAdapterModuleDeclarations(cwd, layers) {
|
|
|
20368
21230
|
// src/hub/control-client.ts
|
|
20369
21231
|
init_project();
|
|
20370
21232
|
import { readFileSync as readFileSync14 } from "fs";
|
|
20371
|
-
import { join as
|
|
21233
|
+
import { join as join29, resolve as resolve17 } from "path";
|
|
20372
21234
|
function stateDirFor(cwd) {
|
|
20373
21235
|
return projectContext(cwd).stateDir;
|
|
20374
21236
|
}
|
|
@@ -20376,8 +21238,8 @@ var PROTOCOL = 16;
|
|
|
20376
21238
|
var RECOVERY_SOURCE_PROTOCOLS = [9, 10, 11, 12, 13, 14, 15, PROTOCOL];
|
|
20377
21239
|
function readControl(stateDir) {
|
|
20378
21240
|
try {
|
|
20379
|
-
const status = JSON.parse(readFileSync14(
|
|
20380
|
-
const token = readFileSync14(
|
|
21241
|
+
const status = JSON.parse(readFileSync14(join29(stateDir, "status.json"), "utf8"));
|
|
21242
|
+
const token = readFileSync14(join29(stateDir, "control-token"), "utf8").trim();
|
|
20381
21243
|
if (!Number.isInteger(status.controlPort) || status.controlPort < 1 || status.controlPort > 65535 || !token)
|
|
20382
21244
|
return;
|
|
20383
21245
|
const startModes = status.startModes && typeof status.startModes === "object" && !Array.isArray(status.startModes) ? Object.fromEntries(Object.entries(status.startModes).filter((entry) => entry[1] === "tui" || entry[1] === "headless")) : undefined;
|
|
@@ -20781,7 +21643,7 @@ init_sdlc_metrics();
|
|
|
20781
21643
|
// package.json
|
|
20782
21644
|
var package_default = {
|
|
20783
21645
|
name: "@staix/agent-hub",
|
|
20784
|
-
version: "0.
|
|
21646
|
+
version: "0.17.0",
|
|
20785
21647
|
description: "Native multi-agent hub: Claude Code, Codex, Kimi Code, Pi and local inference as peers in one project",
|
|
20786
21648
|
license: "MIT",
|
|
20787
21649
|
type: "module",
|
|
@@ -20870,13 +21732,13 @@ init_workflow();
|
|
|
20870
21732
|
init_deliverables();
|
|
20871
21733
|
|
|
20872
21734
|
// src/hub/deliverable-files.ts
|
|
20873
|
-
import { constants as
|
|
21735
|
+
import { constants as constants8, lstatSync as lstatSync14 } from "fs";
|
|
20874
21736
|
init_tools();
|
|
20875
21737
|
init_permission_mode();
|
|
20876
21738
|
init_project();
|
|
20877
21739
|
init_deliverables();
|
|
20878
21740
|
var CHUNK = 1024 * 1024;
|
|
20879
|
-
var NOFOLLOW =
|
|
21741
|
+
var NOFOLLOW = constants8.O_NOFOLLOW ?? 0;
|
|
20880
21742
|
|
|
20881
21743
|
// src/hub/tasks.ts
|
|
20882
21744
|
init_board();
|
|
@@ -21108,7 +21970,7 @@ var INSTANCE_CONFIG_FILES = ["config.json", "config.local.json"];
|
|
|
21108
21970
|
function readInstanceConfigLayers(cwd) {
|
|
21109
21971
|
return INSTANCE_CONFIG_FILES.flatMap((name) => {
|
|
21110
21972
|
try {
|
|
21111
|
-
return [{ name, raw: JSON.parse(readFileSync15(
|
|
21973
|
+
return [{ name, raw: JSON.parse(readFileSync15(join30(cwd, ".agenthub", name), "utf8")) }];
|
|
21112
21974
|
} catch (error) {
|
|
21113
21975
|
if (error.code === "ENOENT")
|
|
21114
21976
|
return [];
|
|
@@ -29462,7 +30324,7 @@ class StdioServerTransport {
|
|
|
29462
30324
|
|
|
29463
30325
|
// src/adapters/claude-channel.ts
|
|
29464
30326
|
import { readFileSync as readFileSync16 } from "fs";
|
|
29465
|
-
import { join as
|
|
30327
|
+
import { join as join31 } from "path";
|
|
29466
30328
|
init_hub_tools();
|
|
29467
30329
|
init_envelope();
|
|
29468
30330
|
|
|
@@ -29504,7 +30366,7 @@ var channel = !toolsMode && process.env.AGENTHUB_CHANNEL === "1";
|
|
|
29504
30366
|
function roles2() {
|
|
29505
30367
|
const read = (name) => {
|
|
29506
30368
|
try {
|
|
29507
|
-
return JSON.parse(readFileSync16(
|
|
30369
|
+
return JSON.parse(readFileSync16(join31(projectRoot2, ".agenthub", name), "utf8")).roles ?? {};
|
|
29508
30370
|
} catch {
|
|
29509
30371
|
return {};
|
|
29510
30372
|
}
|
|
@@ -29523,7 +30385,7 @@ function sessionProfile() {
|
|
|
29523
30385
|
for (const name of ["config.json", "config.local.json"]) {
|
|
29524
30386
|
let data;
|
|
29525
30387
|
try {
|
|
29526
|
-
data = JSON.parse(readFileSync16(
|
|
30388
|
+
data = JSON.parse(readFileSync16(join31(projectRoot2, ".agenthub", name), "utf8"));
|
|
29527
30389
|
} catch (error) {
|
|
29528
30390
|
if (error.code === "ENOENT")
|
|
29529
30391
|
continue;
|
|
@@ -29550,7 +30412,7 @@ function sessionRoles() {
|
|
|
29550
30412
|
const read = (name) => {
|
|
29551
30413
|
let raw;
|
|
29552
30414
|
try {
|
|
29553
|
-
raw = JSON.parse(readFileSync16(
|
|
30415
|
+
raw = JSON.parse(readFileSync16(join31(projectRoot2, ".agenthub", name), "utf8")).catalog_roles;
|
|
29554
30416
|
} catch {
|
|
29555
30417
|
return {};
|
|
29556
30418
|
}
|
|
@@ -29580,7 +30442,7 @@ var HELD = `another session is attached to the hub as "${peerId}"; this one is s
|
|
|
29580
30442
|
var INBOX_CAP = 200;
|
|
29581
30443
|
function peerHeld() {
|
|
29582
30444
|
try {
|
|
29583
|
-
const status = JSON.parse(readFileSync16(
|
|
30445
|
+
const status = JSON.parse(readFileSync16(join31(stateDir, "status.json"), "utf8"));
|
|
29584
30446
|
const peer = status.peers?.[peerId];
|
|
29585
30447
|
if (channel && peer?.toolsOnly && !peer.claiming)
|
|
29586
30448
|
return false;
|