@staix/agent-hub 0.16.0 → 0.18.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 +34 -0
- package/README.md +2 -2
- package/docs/agent-notes/README.md +5 -0
- package/docs/agent-notes/adapters.md +20 -5
- package/docs/agent-notes/benchmarks.md +1 -0
- package/docs/agent-notes/daemon.md +5 -3
- package/docs/agent-notes/local-worker.md +1 -0
- package/docs/agent-notes/tasks.md +9 -3
- package/docs/agent-notes/tests.md +6 -3
- package/docs/dogfood.md +21 -4
- package/docs/memory.md +1 -1
- package/docs/operations.md +150 -34
- package/docs/profiles.md +36 -0
- package/docs/quickstart.md +14 -4
- package/docs/roles.md +1 -0
- package/docs/sdlc.md +39 -13
- package/docs/security.md +41 -9
- package/docs/smoke.md +51 -3
- package/docs/specs/2026-09-19-agent-hub-design.md +56 -10
- 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-0.18.0.md +76 -0
- package/docs/verification/2026-10-25-codex-0164-reviewer-tool-call.md +52 -0
- package/docs/verification/2026-10-25-codex-review-live-verdict.md +35 -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-remote-docker-transport.md +46 -0
- package/docs/verification/2026-10-25-reviewer-no-verdict-diagnostics.md +35 -0
- package/docs/verification/profiles/YYYY-MM-DD-general.md +46 -0
- package/docs/verification/profiles/YYYY-MM-DD-research.md +61 -0
- package/docs/verification/profiles/YYYY-MM-DD-writing.md +46 -0
- package/docs/verified.json +28 -19
- package/package.json +2 -2
- package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-hub/server.js +1115 -192
- 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/builtin-metadata.ts +1 -1
- 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/local-worker.ts +5 -3
- 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/console-state.ts +9 -2
- package/src/cli/help.ts +29 -28
- package/src/cli/identity.ts +87 -6
- package/src/cli/main.ts +190 -69
- package/src/cli/output.ts +7 -2
- package/src/cli/sdlc-output.ts +242 -0
- package/src/cli/task-output.ts +212 -0
- package/src/cli/upgrade-output.ts +57 -0
- package/src/cli/upgrade.ts +4 -3
- package/src/hub/board.ts +17 -0
- package/src/hub/check-runner.ts +252 -22
- package/src/hub/conductor.ts +5 -0
- package/src/hub/config-apply.ts +16 -7
- package/src/hub/config-edit-host.ts +1 -1
- package/src/hub/config-tier.ts +1 -1
- package/src/hub/daemon.ts +487 -94
- package/src/hub/dogfood-profile.ts +248 -0
- package/src/hub/dogfood-read.ts +77 -14
- package/src/hub/dogfood.ts +5 -1
- package/src/hub/events.ts +1 -1
- package/src/hub/host-executable.ts +2 -1
- package/src/hub/hub-tools.ts +2 -2
- package/src/hub/memory-plan-guard.ts +8 -33
- package/src/hub/operational-authority.ts +60 -0
- package/src/hub/operational-view.ts +141 -0
- package/src/hub/orphan-process.ts +69 -0
- package/src/hub/permission-mode.ts +1 -1
- package/src/hub/read-only-shell-proof.ts +80 -0
- package/src/hub/read-only-shell.ts +162 -0
- package/src/hub/reviewer-pool-config.ts +3 -2
- package/src/hub/reviewer-pool.ts +3 -0
- package/src/hub/routing.ts +23 -6
- package/src/hub/sdlc-merge.ts +299 -0
- package/src/hub/sdlc-mirror.ts +129 -0
- package/src/hub/sdlc.ts +58 -12
- package/src/hub/settings.ts +11 -5
- package/src/hub/snapshots.ts +60 -0
- package/src/hub/task-repository.ts +31 -5
- package/src/hub/tasks.ts +266 -28
- package/src/local/tools.ts +17 -4
- package/src/memory/brief.ts +45 -5
- package/src/ui/index.html +2 -1
|
@@ -82,7 +82,7 @@ function memoryGitEnv(env = process.env, options = {}) {
|
|
|
82
82
|
}
|
|
83
83
|
function memoryGitSync(args, options) {
|
|
84
84
|
const argv = memoryGitArgv(args);
|
|
85
|
-
return childProcess().spawnSync(argv[0], argv.slice(1), { ...options, env: memoryGitEnv(options.env ?? process.env) });
|
|
85
|
+
return childProcess().spawnSync(argv[0], argv.slice(1), { ...ambientGitTimeout !== undefined ? { timeout: ambientGitTimeout, killSignal: "SIGKILL" } : {}, ...options, env: memoryGitEnv(options.env ?? process.env) });
|
|
86
86
|
}
|
|
87
87
|
function processSignature(pid) {
|
|
88
88
|
try {
|
|
@@ -118,7 +118,7 @@ var require2, processes, childProcess = () => processes ??= require2("node:child
|
|
|
118
118
|
const parent = dirname(absolute);
|
|
119
119
|
return parent === absolute ? absolute : join(canonical(parent), basename(absolute));
|
|
120
120
|
}
|
|
121
|
-
}, DENY_SEGMENTS, DENY_NAMES, hasSegment = (rel, seg) => `/${rel}/`.includes(`/${seg}/`), SAFE_CONFIG, CREDENTIAL_KEYS, memoryGitArgv = (args, opts = {}) => ["git", "--no-pager", ...SAFE_CONFIG.filter((c) => !opts.credentials || !CREDENTIAL_KEYS.has(c)).flatMap((c) => ["-c", c]), ...args];
|
|
121
|
+
}, DENY_SEGMENTS, DENY_NAMES, hasSegment = (rel, seg) => `/${rel}/`.includes(`/${seg}/`), SAFE_CONFIG, CREDENTIAL_KEYS, memoryGitArgv = (args, opts = {}) => ["git", "--no-pager", ...SAFE_CONFIG.filter((c) => !opts.credentials || !CREDENTIAL_KEYS.has(c)).flatMap((c) => ["-c", c]), ...args], ambientGitTimeout;
|
|
122
122
|
var init_platform = __esm(() => {
|
|
123
123
|
require2 = createRequire(import.meta.url);
|
|
124
124
|
DENY_SEGMENTS = [".maru/secrets", ".agenthub/state", ".agenthub/archive"];
|
|
@@ -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": {
|
|
@@ -5354,9 +6154,26 @@ ${preview(content)}`, "write", ctx.signal, file, undefined, String(a.path), a),
|
|
|
5354
6154
|
return "error: empty command";
|
|
5355
6155
|
if (command.length > 4000)
|
|
5356
6156
|
return "error: command longer than 4000 characters; put it in a script file with write, then run that";
|
|
5357
|
-
const
|
|
5358
|
-
|
|
5359
|
-
|
|
6157
|
+
const input = Object.freeze({ ...a, command });
|
|
6158
|
+
for (let attempt = 0;; attempt++) {
|
|
6159
|
+
const refused = approvalError(await ctx.permit(`bash: ${command}`, "bash", ctx.signal, undefined, undefined, undefined, input, attempt ? { forceAsk: true } : undefined), "error: the user did not approve this command");
|
|
6160
|
+
if (refused)
|
|
6161
|
+
return refused;
|
|
6162
|
+
if (ctx.signal?.aborted)
|
|
6163
|
+
return "error: turn cancelled before shell execution";
|
|
6164
|
+
let check = "valid";
|
|
6165
|
+
try {
|
|
6166
|
+
check = ctx.shellExecutionCheck?.(input) ?? "valid";
|
|
6167
|
+
} catch {
|
|
6168
|
+
check = "ask";
|
|
6169
|
+
}
|
|
6170
|
+
if (check === "aborted" || ctx.signal?.aborted)
|
|
6171
|
+
return "error: turn cancelled before shell execution";
|
|
6172
|
+
if (check === "valid")
|
|
6173
|
+
break;
|
|
6174
|
+
if (attempt)
|
|
6175
|
+
return "error: read-only shell proof remained unstable; no command was executed";
|
|
6176
|
+
}
|
|
5360
6177
|
const res = await sandboxedExec(["/bin/bash", "-c", command], { cwd: ctx.cwd, profile: ctx.sandboxProfile, readOnlyData: !!ctx.readOnly?.length, timeoutMs: (Number(a.timeout_s) || 120) * 1000, ...ctx.sandboxEnv ? { env: ctx.sandboxEnv } : {}, ...ctx.signal ? { signal: ctx.signal } : {} });
|
|
5361
6178
|
try {
|
|
5362
6179
|
ctx.onProcessExit?.(res.code);
|
|
@@ -5395,7 +6212,7 @@ ${preview(content)}`, "write", ctx.signal, file, undefined, String(a.path), a),
|
|
|
5395
6212
|
}
|
|
5396
6213
|
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
6214
|
try {
|
|
5398
|
-
|
|
6215
|
+
lstatSync9(p);
|
|
5399
6216
|
return true;
|
|
5400
6217
|
} catch {
|
|
5401
6218
|
return false;
|
|
@@ -5420,7 +6237,7 @@ var init_tools = __esm(() => {
|
|
|
5420
6237
|
fn("read", "Read a text file inside the project. Returns numbered lines.", { path: str, offset: { type: "integer", minimum: 1 }, limit: { type: "integer", minimum: 1 } }, ["path"]),
|
|
5421
6238
|
fn("write", "Create or overwrite a file inside the project. Needs the user's approval.", { path: str, content: str }, ["path", "content"]),
|
|
5422
6239
|
fn("edit", "Replace one exact, unique occurrence of `old` with `new` in a file. Needs the user's approval.", { path: str, old: str, new: str }, ["path", "old", "new"]),
|
|
5423
|
-
fn("bash", "Run a shell command in the project directory, sandboxed: no writes outside the project, no credential reads, no network unless the hub enables it.
|
|
6240
|
+
fn("bash", "Run a shell command in the project directory, sandboxed: no writes outside the project, no credential reads, no network unless the hub enables it. Follows hub permission policy: proven bounded read-only commands may run automatically in ask-when-needed; other commands ask.", { command: str, timeout_s: { type: "integer", minimum: 1, maximum: 600 } }, ["command"]),
|
|
5424
6241
|
fn("git", "Run git in the project. status, diff, log, show, ls-files, blame, rev-parse run freely; add, commit, restore, checkout, switch, stash, mv, rm, branch, merge, rebase, cherry-pick, revert, tag need approval; everything else (push, fetch, config, remote ...) is refused.", { args: { type: "array", items: str, minItems: 1 } }, ["args"]),
|
|
5425
6242
|
fn("hub_send", "Send a message to the other agents now, before your turn ends. Conclusions only. Your final answer is shared anyway; use this for something that cannot wait.", { text: str, to: { type: "array", items: str } }, ["text"])
|
|
5426
6243
|
];
|
|
@@ -5449,8 +6266,8 @@ var PI_CEILING_KIND = "tool-step-ceiling", PI_CEILING_UNIT = "tool-step";
|
|
|
5449
6266
|
|
|
5450
6267
|
// src/adapters/pi.ts
|
|
5451
6268
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
5452
|
-
import { existsSync as existsSync7, mkdirSync as
|
|
5453
|
-
import { join as
|
|
6269
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync9 } from "fs";
|
|
6270
|
+
import { join as join14, resolve as resolve11 } from "path";
|
|
5454
6271
|
import { spawn as spawn9 } from "child_process";
|
|
5455
6272
|
function ownerStillAlive(pid, signature) {
|
|
5456
6273
|
const current = processSignature(pid);
|
|
@@ -5816,9 +6633,9 @@ var init_pi = __esm(() => {
|
|
|
5816
6633
|
this.approvalAnswerEpoch = undefined;
|
|
5817
6634
|
this.approvalStopReason = "";
|
|
5818
6635
|
this.approvalAbortSent = false;
|
|
5819
|
-
|
|
5820
|
-
const sessions =
|
|
5821
|
-
|
|
6636
|
+
mkdirSync9(this.opts.stateDir, { recursive: true });
|
|
6637
|
+
const sessions = join14(this.opts.stateDir, "pi-sessions");
|
|
6638
|
+
mkdirSync9(sessions, { recursive: true, mode: 448 });
|
|
5822
6639
|
if (this.opts.sessionFile) {
|
|
5823
6640
|
try {
|
|
5824
6641
|
validatePiSession(this.opts.stateDir, this.opts.cwd, this.opts.sessionFile, this.opts.sessionId);
|
|
@@ -5875,7 +6692,7 @@ var init_pi = __esm(() => {
|
|
|
5875
6692
|
return new Response("not found", { status: 404 });
|
|
5876
6693
|
} });
|
|
5877
6694
|
this.server = bridge;
|
|
5878
|
-
const extension = resolve11(
|
|
6695
|
+
const extension = resolve11(join14(import.meta.dir, "../pi/extension.ts"));
|
|
5879
6696
|
const inherited = {};
|
|
5880
6697
|
for (const key of ["PATH", "HOME", "USER", "SHELL", "TMPDIR", "TERM", "TERM_PROGRAM", "LANG", "LC_ALL", "LC_CTYPE", "NO_COLOR", "CODEX_HOME"])
|
|
5881
6698
|
if (process.env[key])
|
|
@@ -8804,8 +9621,8 @@ var init_shipped_roles = __esm(() => {
|
|
|
8804
9621
|
|
|
8805
9622
|
// src/hub/roles.ts
|
|
8806
9623
|
import { createHash as createHash5 } from "crypto";
|
|
8807
|
-
import { constants as
|
|
8808
|
-
import { join as
|
|
9624
|
+
import { constants as constants5, closeSync as closeSync6, fstatSync as fstatSync5, lstatSync as lstatSync10, openSync as openSync6, readSync as readSync4, readdirSync as readdirSync2 } from "fs";
|
|
9625
|
+
import { join as join15 } from "path";
|
|
8809
9626
|
function parseRole(text, stem, layer) {
|
|
8810
9627
|
if (Buffer.byteLength(text) > 8192 || text.includes("\r") || !ID.test(stem))
|
|
8811
9628
|
throw new Error("invalid role size, encoding or file id");
|
|
@@ -8852,24 +9669,24 @@ function parseRole(text, stem, layer) {
|
|
|
8852
9669
|
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
9670
|
}
|
|
8854
9671
|
function projectRole(file) {
|
|
8855
|
-
const fd =
|
|
9672
|
+
const fd = openSync6(file, constants5.O_RDONLY | constants5.O_NOFOLLOW | constants5.O_NONBLOCK);
|
|
8856
9673
|
try {
|
|
8857
|
-
const before =
|
|
9674
|
+
const before = fstatSync5(fd);
|
|
8858
9675
|
if (!before.isFile() || before.nlink !== 1 || before.size > 8192)
|
|
8859
9676
|
throw new Error("role must be a bounded regular file");
|
|
8860
9677
|
const bytes = Buffer.alloc(8193);
|
|
8861
9678
|
let length = 0, n = 0;
|
|
8862
|
-
while ((n =
|
|
9679
|
+
while ((n = readSync4(fd, bytes, length, bytes.length - length, null)) > 0) {
|
|
8863
9680
|
length += n;
|
|
8864
9681
|
if (length > 8192)
|
|
8865
9682
|
throw new Error("role exceeds file cap");
|
|
8866
9683
|
}
|
|
8867
|
-
const after =
|
|
9684
|
+
const after = fstatSync5(fd);
|
|
8868
9685
|
if (before.size !== after.size || before.mtimeMs !== after.mtimeMs || length !== after.size)
|
|
8869
9686
|
throw new Error("role changed during read");
|
|
8870
9687
|
return bytes.subarray(0, length).toString("utf8");
|
|
8871
9688
|
} finally {
|
|
8872
|
-
|
|
9689
|
+
closeSync6(fd);
|
|
8873
9690
|
}
|
|
8874
9691
|
}
|
|
8875
9692
|
function loadRoleCatalog(cwd) {
|
|
@@ -8884,9 +9701,9 @@ function loadRoleCatalog(cwd) {
|
|
|
8884
9701
|
if (!cwd)
|
|
8885
9702
|
return { roles, errors };
|
|
8886
9703
|
try {
|
|
8887
|
-
const config =
|
|
9704
|
+
const config = join15(cwd, ".agenthub"), dir = join15(config, "roles");
|
|
8888
9705
|
for (const path of [config, dir]) {
|
|
8889
|
-
const stat =
|
|
9706
|
+
const stat = lstatSync10(path);
|
|
8890
9707
|
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
8891
9708
|
throw new Error("unsafe role directory");
|
|
8892
9709
|
}
|
|
@@ -8896,7 +9713,7 @@ function loadRoleCatalog(cwd) {
|
|
|
8896
9713
|
for (const file of files) {
|
|
8897
9714
|
const id = file.slice(0, -3);
|
|
8898
9715
|
try {
|
|
8899
|
-
roles.set(id, parseRole(projectRole(
|
|
9716
|
+
roles.set(id, parseRole(projectRole(join15(dir, file)), id, "project"));
|
|
8900
9717
|
} catch {
|
|
8901
9718
|
errors.push({ id: ID.test(id) ? id : "invalid-file-id", reason: "invalid or unsafe project role" });
|
|
8902
9719
|
}
|
|
@@ -9217,7 +10034,7 @@ var init_hub_tools = __esm(() => {
|
|
|
9217
10034
|
tool("hub_release_list", "Read public release scope and planned/actual dates for phased planning. Configured planners only.", {}),
|
|
9218
10035
|
tool("hub_task_propose", "Put a piece of work on the shared task board. The hub assigns an owner by class (routing.toml) unless you name one. Classes: plan, implement, bulk_edit, test, review, summarize, triage. Name the class when you know it; without one the hub tries to pick it. Name yourself as owner to claim work nobody assigned you: it starts in progress. Put the paths the work touches in refs, set refs.repo for work in another repository, and when you claim work, a plan; the hub says when they overlap another open task.", { title: str2, class: { type: "string", enum: ["plan", "implement", "bulk_edit", "test", "review", "summarize", "triage"] }, detail: str2, role: { type: "string", description: "catalog role id for this task class; guidance only, no routing or authority change" }, refs, plan, owner: { type: "string", description: "peer id; yours to claim the work, omit to let the hub route it. With after, the owner is reserved: offered the task first once it is ready" }, after: { type: "array", items: { type: "integer" }, description: "ids of tasks that must be approved first; until then this one waits, offered to nobody, and cannot be claimed" }, backlog: { type: "boolean", description: "stage unoffered work; owner is only a reservation, even when naming yourself" }, urgent: { type: "boolean", description: "hand it over even when its owner is paused for quota only briefly" }, goal: { type: "string", description: "id of this project's long-term task (ahub goal list) the work is part of" } }, ["title"]),
|
|
9219
10036
|
tool("hub_task_accept", "Take a task that was assigned to you, with your plan for it. Set refs.repo for work in another repository; acceptance may correct only that repository metadata.", { id, plan: acceptPlan, refs: { ...refs, properties: { repo: refs.properties.repo } } }, ["id"]),
|
|
9220
|
-
tool("hub_task_cancel", "Cancel
|
|
10037
|
+
tool("hub_task_cancel", "Cancel proposed/backlog work you proposed as its configured planner, including work an owner accepted and then declined while it has no execution, branch, commit or PR evidence; work with any such evidence is person-only. Keeps history and does not stop a native turn or delete files.", { id, reason: str2, superseded_by: str2, revision: { type: "integer", minimum: 0 } }, ["id", "reason"]),
|
|
9221
10038
|
tool("hub_task_decline", "Pass on a task assigned to you; the hub offers it to the next peer. A plain decline is a lasting refusal: you are never offered this task again. Pass capacity: true when you only have no room right now: you are skipped for this move only, and a later reroute or planner pull may offer it to you again.", { id, reason: str2, capacity: { type: "boolean" } }, ["id"]),
|
|
9222
10039
|
tool("hub_task_done", "Mark your task finished. It goes to its reviewer with your summary and refs; no duplicate hub_send chat is needed. When the project configures a check for its class, the hub runs it first and the result comes as a task message: a failed check keeps the task with you.", { id, summary: { type: "string", description: "what changed, why, and the check you ran with its result" }, refs, deliverables: deliverablesSchema }, ["id", "summary"]),
|
|
9223
10040
|
tool("hub_sdlc_record", "Record artifact ids or claimed local checks on your task. This grants no authority or external-check evidence. Only its owner/reviewer or the person may record; only its reviewer/person may name a reviewed head or phase checklist. Phase evidence refs are content hashes, not external verification.", { id, receipt: { type: "object", properties: { deliverables: deliverablesSchema, stage: { type: "string", enum: ["spec", "plan", "implement", "review", "verify", "merge", "release"] }, repo: str2, issue: id, pr: id, localHead: str2, localResult: { type: "string", enum: ["passed", "failed", "unknown"] }, reviewedHead: str2, phase: { type: "object", properties: { id: str2, head: str2, tasks: { type: "array", maxItems: 64, items: { type: "object", properties: { id, after: { type: "array", maxItems: 16, items: id } }, required: ["id", "after"], additionalProperties: false } }, criteria: { type: "array", maxItems: 32, items: { type: "object", properties: { id: str2, required: { type: "boolean" }, result: { type: "string", enum: ["verified", "failed", "unverified"] }, refs: { type: "array", maxItems: 8, items: str2 } }, required: ["id", "required", "result", "refs"], additionalProperties: false } } }, required: ["id", "tasks", "criteria"], additionalProperties: false } }, additionalProperties: false } }, ["id", "receipt"]),
|
|
@@ -9241,7 +10058,7 @@ var init_hub_tools = __esm(() => {
|
|
|
9241
10058
|
TASK_TOOL_NAMES = new Set(TASK_TOOLS.map((t) => t.name));
|
|
9242
10059
|
CONDUCTOR_TOOLS = [
|
|
9243
10060
|
tool("hub_status", "Inspect public team state, holds, quota windows, task counts and pending approval peer/tool/age. Only the configured conductor may use this tool.", {}),
|
|
9244
|
-
tool("hub_task_show", "Read a task's public view and history, its done summary and check result included. PII tasks remain stubs. Only the conductor and the task's current owner and reviewer may use this tool.", { id }, ["id"]),
|
|
10061
|
+
tool("hub_task_show", "Read a task's public view and history, its done summary and check result included. PII tasks remain stubs. Only the conductor, the configured planner and the task's current owner and reviewer may use this tool.", { id }, ["id"]),
|
|
9245
10062
|
tool("hub_task_assign", "Name an owner (default role) or reviewer. Only the conductor may name a reviewer through this tool. A proposer may redirect an unaccepted proposed task's owner; a task that still waits reserves that owner. Pull-only peers receive work when named while attached or when they claim it; a peer that cannot take the task is refused with the reason, and the task is unchanged. With an explicit capabilities list, the conductor needs assign capability for either role, the proposer only to give ownership to another peer.", { id, peer: str2, role: { type: "string", enum: ["owner", "reviewer"] } }, ["id", "peer"]),
|
|
9246
10063
|
tool("hub_task_escalate", "Escalate a task through the normal task flow, as the conductor. Requires assign capability when explicitly listed.", { id }, ["id"]),
|
|
9247
10064
|
tool("hub_peer_start", "Start local, kimi, qwen, jcode, pi, claude or codex in the start mode a person set for it; you cannot choose the mode. A headless peer starts at once. A TUI peer (claude, and pi and codex by default) opens in a terminal with a fixed command; when the hub cannot open a terminal the call fails with the command for the person to run.", { peer: { type: "string", enum: CONDUCTOR_START_PEERS } }, ["peer"]),
|
|
@@ -9289,7 +10106,7 @@ var init_config = __esm(() => {
|
|
|
9289
10106
|
});
|
|
9290
10107
|
|
|
9291
10108
|
// src/hub/routing.ts
|
|
9292
|
-
import { join as
|
|
10109
|
+
import { join as join16, resolve as resolve12 } from "path";
|
|
9293
10110
|
function effectiveLocalRoute(defaultRoute, classRoute, classFixedModel) {
|
|
9294
10111
|
return classRoute ?? (classFixedModel === undefined ? defaultRoute : undefined);
|
|
9295
10112
|
}
|
|
@@ -9300,7 +10117,7 @@ var init_routing = __esm(() => {
|
|
|
9300
10117
|
init_spec_read();
|
|
9301
10118
|
init_config();
|
|
9302
10119
|
init_stage();
|
|
9303
|
-
TEMPLATE =
|
|
10120
|
+
TEMPLATE = join16(import.meta.dir, "..", "..", "templates", "routing.toml");
|
|
9304
10121
|
cache = new Map;
|
|
9305
10122
|
});
|
|
9306
10123
|
|
|
@@ -9567,11 +10384,12 @@ var init_local_worker = __esm(() => {
|
|
|
9567
10384
|
return `(stopped after ${maxSteps} steps) ${progress.last}`.trim();
|
|
9568
10385
|
}
|
|
9569
10386
|
toolContext(envs, turnSignal, pii, reply) {
|
|
10387
|
+
const executionGeneration = this.turn;
|
|
9570
10388
|
return {
|
|
9571
10389
|
cwd: this.opts.cwd,
|
|
9572
10390
|
deny: this.opts.tools.deny,
|
|
9573
10391
|
readOnly: this.opts.tools.readOnly,
|
|
9574
|
-
permit: async (title, tool, _signal, canonicalTarget, _observe, originalPath, input) => {
|
|
10392
|
+
permit: async (title, tool, _signal, canonicalTarget, _observe, originalPath, input, policy) => {
|
|
9575
10393
|
if (turnSignal.aborted)
|
|
9576
10394
|
return "aborted";
|
|
9577
10395
|
const generation = this.turn;
|
|
@@ -9600,7 +10418,7 @@ var init_local_worker = __esm(() => {
|
|
|
9600
10418
|
};
|
|
9601
10419
|
const onAbort = () => finish("aborted");
|
|
9602
10420
|
turnSignal.addEventListener("abort", onAbort, { once: true });
|
|
9603
|
-
this.opts.tools.permit(title, tool, turnSignal, canonicalTarget, observe, originalPath, input).then(finish, (error) => {
|
|
10421
|
+
this.opts.tools.permit(title, tool, turnSignal, canonicalTarget, observe, originalPath, input, policy).then(finish, (error) => {
|
|
9604
10422
|
turnSignal.removeEventListener("abort", onAbort);
|
|
9605
10423
|
reject(error);
|
|
9606
10424
|
});
|
|
@@ -9613,6 +10431,7 @@ var init_local_worker = __esm(() => {
|
|
|
9613
10431
|
observe({ source: "expired", answerEpoch: this.approvalAnswerEpoch ?? 0, eligibleExpiry: true });
|
|
9614
10432
|
return picked;
|
|
9615
10433
|
},
|
|
10434
|
+
shellExecutionCheck: (input) => turnSignal.aborted || executionGeneration !== this.turn ? "aborted" : this.opts.tools.shellExecutionCheck?.(input) ?? "valid",
|
|
9616
10435
|
sandboxProfile: this.sandboxProfile,
|
|
9617
10436
|
sandboxEnv: { ...proxyEnv(this.opts.tools.bashNetwork ?? false), AGENTHUB_PEER_ID: this.id },
|
|
9618
10437
|
signal: turnSignal,
|
|
@@ -9851,7 +10670,7 @@ var init_launch2 = __esm(() => {
|
|
|
9851
10670
|
|
|
9852
10671
|
// src/adapters/claude-task-sessions.ts
|
|
9853
10672
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
9854
|
-
import { mkdirSync as
|
|
10673
|
+
import { mkdirSync as mkdirSync10, readFileSync as readFileSync8 } from "fs";
|
|
9855
10674
|
import { dirname as dirname11 } from "path";
|
|
9856
10675
|
function readState(file) {
|
|
9857
10676
|
let text;
|
|
@@ -9889,7 +10708,7 @@ class ClaudeTaskSessions {
|
|
|
9889
10708
|
}
|
|
9890
10709
|
write(sessions) {
|
|
9891
10710
|
const next = { version: 1, sessions };
|
|
9892
|
-
|
|
10711
|
+
mkdirSync10(dirname11(this.file), { recursive: true, mode: 448 });
|
|
9893
10712
|
atomicPrivateJSON(this.file, next);
|
|
9894
10713
|
this.state = next;
|
|
9895
10714
|
}
|
|
@@ -9934,9 +10753,9 @@ var init_claude_task_sessions = __esm(() => {
|
|
|
9934
10753
|
|
|
9935
10754
|
// src/adapters/claude-implementer.ts
|
|
9936
10755
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
9937
|
-
import { lstatSync as
|
|
10756
|
+
import { lstatSync as lstatSync11, mkdtempSync as mkdtempSync5, rmSync as rmSync6, writeFileSync as writeFileSync8 } from "fs";
|
|
9938
10757
|
import { tmpdir as tmpdir5 } from "os";
|
|
9939
|
-
import { join as
|
|
10758
|
+
import { join as join17 } from "path";
|
|
9940
10759
|
function claudeImplementerArgs(turn, mcp, settings, prompt) {
|
|
9941
10760
|
return [
|
|
9942
10761
|
"-p",
|
|
@@ -9968,10 +10787,10 @@ function claudeImplementerArgs(turn, mcp, settings, prompt) {
|
|
|
9968
10787
|
async function proveClaudeImplementer(peer, command) {
|
|
9969
10788
|
const binary = Bun.which(command[0], { PATH: process.env.PATH }) ?? command[0];
|
|
9970
10789
|
const fingerprint = () => {
|
|
9971
|
-
const file = realPath(binary), stat =
|
|
10790
|
+
const file = realPath(binary), stat = lstatSync11(file);
|
|
9972
10791
|
return [file, stat.dev, stat.ino, stat.size, stat.mtimeMs, stat.ctimeMs].join(":");
|
|
9973
10792
|
};
|
|
9974
|
-
const binding = fingerprint(), home = mkdtempSync5(
|
|
10793
|
+
const binding = fingerprint(), home = mkdtempSync5(join17(tmpdir5(), "ahub-claude-proof-"));
|
|
9975
10794
|
let version = "", timedOut = false;
|
|
9976
10795
|
const child = new OwnedPrintChild({
|
|
9977
10796
|
peer,
|
|
@@ -9980,7 +10799,7 @@ async function proveClaudeImplementer(peer, command) {
|
|
|
9980
10799
|
cwd: home,
|
|
9981
10800
|
input: "",
|
|
9982
10801
|
envMode: "replace",
|
|
9983
|
-
env: { PATH: process.env.PATH, HOME: home, CLAUDE_CONFIG_DIR:
|
|
10802
|
+
env: { PATH: process.env.PATH, HOME: home, CLAUDE_CONFIG_DIR: join17(home, ".claude") },
|
|
9984
10803
|
maxFrameBytes: 4096,
|
|
9985
10804
|
maxOutputBytes: 4096,
|
|
9986
10805
|
onLine: (line) => {
|
|
@@ -10051,7 +10870,7 @@ var init_claude_implementer = __esm(() => {
|
|
|
10051
10870
|
if (options.mode !== undefined && options.mode !== "ask" && options.mode !== "ask-when-needed")
|
|
10052
10871
|
throw new Error("Claude implementer refuses unsupported permission mode");
|
|
10053
10872
|
this.mode = options.mode ?? "ask";
|
|
10054
|
-
this.sessions = new ClaudeTaskSessions(
|
|
10873
|
+
this.sessions = new ClaudeTaskSessions(join17(options.stateDir, claudeStateFile("claude-task-sessions", id)));
|
|
10055
10874
|
}
|
|
10056
10875
|
get permissionModeState() {
|
|
10057
10876
|
return this.mode;
|
|
@@ -10197,9 +11016,9 @@ var init_claude_implementer = __esm(() => {
|
|
|
10197
11016
|
this.options.notify?.(`${this.id}: task #${group.task} session fresh; recorded transcript is missing`);
|
|
10198
11017
|
}
|
|
10199
11018
|
}
|
|
10200
|
-
const launchId = randomUUID8(), directory = mkdtempSync5(
|
|
11019
|
+
const launchId = randomUUID8(), directory = mkdtempSync5(join17(this.options.stateDir, "claude-child-")), settings = join17(directory, "settings.json");
|
|
10201
11020
|
this.cleanup = () => rmSync6(directory, { recursive: true, force: true });
|
|
10202
|
-
|
|
11021
|
+
writeFileSync8(settings, sessionSettings(undefined, { script: this.options.factsHook, stateDir: this.options.stateDir, purpose: "permission" }), { mode: 384, flag: "wx" });
|
|
10203
11022
|
const env = {
|
|
10204
11023
|
AGENTHUB_MODE: "tools",
|
|
10205
11024
|
AGENTHUB_PEER_ID: this.id,
|
|
@@ -10333,6 +11152,14 @@ function createJcode(ctx) {
|
|
|
10333
11152
|
init_jcode();
|
|
10334
11153
|
return new JcodePeer(ctx.daemon.jcode);
|
|
10335
11154
|
}
|
|
11155
|
+
function createQwenDual(ctx) {
|
|
11156
|
+
if (!("qwenDual" in ctx.daemon))
|
|
11157
|
+
throw new Error("Qwen TUI requires its dual-output transport options");
|
|
11158
|
+
if (ctx.permission === "read-only")
|
|
11159
|
+
throw new Error("Qwen has no verified read-only permission mapping");
|
|
11160
|
+
init_qwen_dual();
|
|
11161
|
+
return new QwenDualPeer(ctx.instance, ctx.daemon.qwenDual);
|
|
11162
|
+
}
|
|
10336
11163
|
function createPi(ctx) {
|
|
10337
11164
|
if (!("pi" in ctx.daemon))
|
|
10338
11165
|
throw new Error("Pi requires its Pi transport options");
|
|
@@ -10384,6 +11211,7 @@ var init_native_factories = __esm(() => {
|
|
|
10384
11211
|
DAEMON_PORTS = {
|
|
10385
11212
|
"codex-appserver": (options) => ({ codex: options }),
|
|
10386
11213
|
acp: (options) => ({ acp: options }),
|
|
11214
|
+
"qwen-dual": (options) => ({ qwenDual: options }),
|
|
10387
11215
|
"owned-acp": (options) => ({ jcode: options }),
|
|
10388
11216
|
"pi-rpc": (options) => ({ pi: options }),
|
|
10389
11217
|
"local-worker": (options) => ({ local: options }),
|
|
@@ -10455,9 +11283,9 @@ var init_reviewer_variants = __esm(() => {
|
|
|
10455
11283
|
});
|
|
10456
11284
|
|
|
10457
11285
|
// src/adapters/agy/hooks.ts
|
|
10458
|
-
import { closeSync as
|
|
11286
|
+
import { closeSync as closeSync7, constants as constants6, fstatSync as fstatSync6, lstatSync as lstatSync12, openSync as openSync7, readSync as readSync5 } from "fs";
|
|
10459
11287
|
import { homedir as homedir5 } from "os";
|
|
10460
|
-
import { isAbsolute as isAbsolute6, join as
|
|
11288
|
+
import { isAbsolute as isAbsolute6, join as join18, relative as relative5 } from "path";
|
|
10461
11289
|
import { createHash as createHash6 } from "crypto";
|
|
10462
11290
|
function decodeAgyHook(event, value) {
|
|
10463
11291
|
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 +11323,19 @@ function readAgyTranscript(session, transcript) {
|
|
|
10495
11323
|
if (!UUID2.test(session))
|
|
10496
11324
|
return empty;
|
|
10497
11325
|
const home = process.env.HOME ?? homedir5();
|
|
10498
|
-
const candidates = ["antigravity-cli", "antigravity", "antigravity-ide"].map((surface) =>
|
|
11326
|
+
const candidates = ["antigravity-cli", "antigravity", "antigravity-ide"].map((surface) => join18(home, ".gemini", surface, "brain", session, ".system_generated/logs/transcript_full.jsonl"));
|
|
10499
11327
|
if (!candidates.includes(transcript))
|
|
10500
11328
|
return empty;
|
|
10501
11329
|
let fd;
|
|
10502
11330
|
try {
|
|
10503
11331
|
let cursor = home;
|
|
10504
11332
|
for (const name of relative5(home, transcript).split("/")) {
|
|
10505
|
-
cursor =
|
|
10506
|
-
if (
|
|
11333
|
+
cursor = join18(cursor, name);
|
|
11334
|
+
if (lstatSync12(cursor).isSymbolicLink())
|
|
10507
11335
|
return empty;
|
|
10508
11336
|
}
|
|
10509
|
-
fd =
|
|
10510
|
-
const stat =
|
|
11337
|
+
fd = openSync7(transcript, constants6.O_RDONLY | constants6.O_NOFOLLOW);
|
|
11338
|
+
const stat = fstatSync6(fd);
|
|
10511
11339
|
if (!stat.isFile() || stat.nlink !== 1)
|
|
10512
11340
|
return empty;
|
|
10513
11341
|
const cap = 4 * 1024 * 1024;
|
|
@@ -10516,7 +11344,7 @@ function readAgyTranscript(session, transcript) {
|
|
|
10516
11344
|
const buffer = Buffer.alloc(stat.size - from);
|
|
10517
11345
|
let bytes = 0;
|
|
10518
11346
|
while (bytes < buffer.length) {
|
|
10519
|
-
const count =
|
|
11347
|
+
const count = readSync5(fd, buffer, bytes, buffer.length - bytes, from + bytes);
|
|
10520
11348
|
if (!count)
|
|
10521
11349
|
break;
|
|
10522
11350
|
bytes += count;
|
|
@@ -10549,7 +11377,7 @@ function readAgyTranscript(session, transcript) {
|
|
|
10549
11377
|
return empty;
|
|
10550
11378
|
} finally {
|
|
10551
11379
|
if (fd !== undefined)
|
|
10552
|
-
|
|
11380
|
+
closeSync7(fd);
|
|
10553
11381
|
}
|
|
10554
11382
|
}
|
|
10555
11383
|
var EVENTS2, UUID2, record3 = (value) => !!value && typeof value === "object" && !Array.isArray(value), AGY_FACTS_HOOK;
|
|
@@ -10628,7 +11456,7 @@ function builtinCliEntries(definitions) {
|
|
|
10628
11456
|
}
|
|
10629
11457
|
// src/adapters/definitions/native-cli-entry-claude.ts
|
|
10630
11458
|
import { readFileSync as readFileSync9 } from "fs";
|
|
10631
|
-
import { join as
|
|
11459
|
+
import { join as join19 } from "path";
|
|
10632
11460
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
10633
11461
|
async function runClaudeEntry(ctx) {
|
|
10634
11462
|
const { args, cwd, stateDir, cliDir, unattendedEnv, projectConfig, fail } = ctx;
|
|
@@ -10679,17 +11507,17 @@ async function runClaudeEntry(ctx) {
|
|
|
10679
11507
|
}
|
|
10680
11508
|
}
|
|
10681
11509
|
let original;
|
|
10682
|
-
for (const file of [
|
|
11510
|
+
for (const file of [join19(executionCwd, ".claude", "settings.local.json"), join19(executionCwd, ".claude", "settings.json"), join19(process.env.HOME ?? "", ".claude", "settings.json")]) {
|
|
10683
11511
|
try {
|
|
10684
11512
|
original ??= JSON.parse(readFileSync9(file, "utf8")).statusLine;
|
|
10685
11513
|
} catch {}
|
|
10686
11514
|
}
|
|
10687
|
-
const facts = claudeObservationHooks(projectConfig(), { script:
|
|
11515
|
+
const facts = claudeObservationHooks(projectConfig(), { script: join19(cliDir, "facts-hook.ts"), stateDir });
|
|
10688
11516
|
let previousLaunch;
|
|
10689
11517
|
try {
|
|
10690
|
-
previousLaunch = JSON.parse(readFileSync9(
|
|
11518
|
+
previousLaunch = JSON.parse(readFileSync9(join19(stateDir, claudeStateFile("claude-launch", peer)), "utf8"));
|
|
10691
11519
|
} catch {}
|
|
10692
|
-
const launch = buildLaunch("claude", args, { unattended: unattendedEnv, statusLine: { script:
|
|
11520
|
+
const launch = buildLaunch("claude", args, { unattended: unattendedEnv, statusLine: { script: join19(cliDir, "statusline-tee.ts"), stateDir, ...original ? { original } : {} }, ...facts ? { facts } : {} });
|
|
10693
11521
|
let result;
|
|
10694
11522
|
try {
|
|
10695
11523
|
let launchRecord;
|
|
@@ -11174,7 +12002,7 @@ var init_native_cli_entry_jcode = () => {};
|
|
|
11174
12002
|
|
|
11175
12003
|
// src/adapters/definitions/native-cli-preview-claude.ts
|
|
11176
12004
|
import { readFileSync as readFileSync10 } from "fs";
|
|
11177
|
-
import { join as
|
|
12005
|
+
import { join as join20 } from "path";
|
|
11178
12006
|
function previewClaude(ctx) {
|
|
11179
12007
|
const { args, cwd, stateDir, cliDir, unattended, config } = ctx;
|
|
11180
12008
|
const { buildLaunch, claudeObservationHooks } = ctx.helpers;
|
|
@@ -11182,13 +12010,13 @@ function previewClaude(ctx) {
|
|
|
11182
12010
|
let envNames = state.envNames;
|
|
11183
12011
|
let launch;
|
|
11184
12012
|
let original;
|
|
11185
|
-
for (const file of [
|
|
12013
|
+
for (const file of [join20(cwd, ".claude/settings.local.json"), join20(cwd, ".claude/settings.json"), join20(process.env.HOME ?? "", ".claude/settings.json")]) {
|
|
11186
12014
|
try {
|
|
11187
12015
|
original ??= JSON.parse(readFileSync10(file, "utf8")).statusLine;
|
|
11188
12016
|
} catch {}
|
|
11189
12017
|
}
|
|
11190
|
-
const facts = claudeObservationHooks(config, { script:
|
|
11191
|
-
launch = buildLaunch("claude", args, { preview: true, unattended, statusLine: { script:
|
|
12018
|
+
const facts = claudeObservationHooks(config, { script: join20(cliDir, "facts-hook.ts"), stateDir });
|
|
12019
|
+
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
12020
|
const safeArgs = args.map((arg, i) => {
|
|
11193
12021
|
if (args[i - 1] === "--settings")
|
|
11194
12022
|
return "{}";
|
|
@@ -11199,7 +12027,7 @@ function previewClaude(ctx) {
|
|
|
11199
12027
|
return publicArg(arg);
|
|
11200
12028
|
});
|
|
11201
12029
|
const publicLaunch = buildLaunch("claude", safeArgs, { preview: true, unattended, statusLine: {
|
|
11202
|
-
script:
|
|
12030
|
+
script: join20(cliDir, "statusline-tee.ts"),
|
|
11203
12031
|
stateDir,
|
|
11204
12032
|
...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
12033
|
}, facts });
|
|
@@ -11232,7 +12060,7 @@ function previewCodex(ctx) {
|
|
|
11232
12060
|
var init_native_cli_preview_codex = () => {};
|
|
11233
12061
|
|
|
11234
12062
|
// src/adapters/definitions/native-cli-preview-kimi.ts
|
|
11235
|
-
import { join as
|
|
12063
|
+
import { join as join21 } from "path";
|
|
11236
12064
|
function previewKimi(ctx) {
|
|
11237
12065
|
const { args, cwd, stateDir, cliDir, config } = ctx;
|
|
11238
12066
|
const { childEnv, startModeOf, buildKimiLaunch, buildKimiTuiLaunch, nativeLaunchEnv, kimiMcpPlan } = ctx.helpers;
|
|
@@ -11255,14 +12083,14 @@ function previewKimi(ctx) {
|
|
|
11255
12083
|
launch = { ...native, cmd: native.cmd === "kimi" ? "kimi" : REDACTED, args: native.args.map((arg) => arg === "--model" || arg === REDACTED ? arg : REDACTED) };
|
|
11256
12084
|
envNames = Object.keys(nativeLaunchEnv("kimi", childEnv())).concat(["AGENTHUB_STATE_DIR", "AGENTHUB_PROJECT_DIR"]);
|
|
11257
12085
|
}
|
|
11258
|
-
const { file, action, changed, tracked } = kimiMcpPlan(cwd, stateDir,
|
|
12086
|
+
const { file, action, changed, tracked } = kimiMcpPlan(cwd, stateDir, join21(cliDir, "../../plugins/agent-hub/server.js"), mode);
|
|
11259
12087
|
kimiMetadata = { mode, mcp: { file, action, changed, tracked } };
|
|
11260
12088
|
return finishPreview(ctx, launch, envNames, unresolved, kimiMetadata);
|
|
11261
12089
|
}
|
|
11262
12090
|
var init_native_cli_preview_kimi = () => {};
|
|
11263
12091
|
|
|
11264
12092
|
// src/adapters/definitions/native-cli-preview-pi.ts
|
|
11265
|
-
import { join as
|
|
12093
|
+
import { join as join22, resolve as resolve15 } from "path";
|
|
11266
12094
|
function previewPi(ctx) {
|
|
11267
12095
|
const { args, stateDir, cliDir, config } = ctx;
|
|
11268
12096
|
const { relayModelIds, buildPiLaunch, startModeOf } = ctx.helpers;
|
|
@@ -11297,7 +12125,7 @@ function previewPi(ctx) {
|
|
|
11297
12125
|
tools: [],
|
|
11298
12126
|
preamble: REDACTED,
|
|
11299
12127
|
maxSteps: config.pi.max_steps
|
|
11300
|
-
}, {}, resolve15(
|
|
12128
|
+
}, {}, resolve15(join22(cliDir, "../pi/extension.ts")), UNRESOLVED, UNRESOLVED);
|
|
11301
12129
|
launch = pi;
|
|
11302
12130
|
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
12131
|
if (mode === "tui")
|
|
@@ -11382,8 +12210,8 @@ var init_native_config_policy = __esm(() => {
|
|
|
11382
12210
|
});
|
|
11383
12211
|
|
|
11384
12212
|
// src/adapters/definitions/native-policy-aux.ts
|
|
11385
|
-
import { closeSync as
|
|
11386
|
-
import { isAbsolute as isAbsolute7, join as
|
|
12213
|
+
import { closeSync as closeSync8, constants as constants7, fstatSync as fstatSync7, openSync as openSync8, readdirSync as readdirSync3, readFileSync as readFileSync11 } from "fs";
|
|
12214
|
+
import { isAbsolute as isAbsolute7, join as join23, normalize } from "path";
|
|
11387
12215
|
function claudeCrashPlan(_meta) {
|
|
11388
12216
|
return { how: "claude: the Claude Code plugin reconnects by itself while that session is still open" };
|
|
11389
12217
|
}
|
|
@@ -11428,13 +12256,13 @@ function localCrashPlan(meta) {
|
|
|
11428
12256
|
}
|
|
11429
12257
|
function claudePlugins(root, cwd) {
|
|
11430
12258
|
const enabled = new Map;
|
|
11431
|
-
for (const file of [
|
|
12259
|
+
for (const file of [join23(root, "settings.json"), ...cwd ? [join23(cwd, ".claude", "settings.json"), join23(cwd, ".claude", "settings.local.json")] : []]) {
|
|
11432
12260
|
const map = json(file)?.enabledPlugins;
|
|
11433
12261
|
if (map && typeof map === "object")
|
|
11434
12262
|
for (const [key, on] of Object.entries(map))
|
|
11435
12263
|
enabled.set(key, on === true);
|
|
11436
12264
|
}
|
|
11437
|
-
const installed = json(
|
|
12265
|
+
const installed = json(join23(root, "plugins", "installed_plugins.json"))?.plugins ?? {};
|
|
11438
12266
|
const out = [];
|
|
11439
12267
|
for (const [key, on] of enabled) {
|
|
11440
12268
|
if (!on)
|
|
@@ -11448,7 +12276,7 @@ function claudePlugins(root, cwd) {
|
|
|
11448
12276
|
return out;
|
|
11449
12277
|
}
|
|
11450
12278
|
function codexPlugins(root) {
|
|
11451
|
-
const config = text(
|
|
12279
|
+
const config = text(join23(root, "config.toml"));
|
|
11452
12280
|
if (config === undefined)
|
|
11453
12281
|
return [];
|
|
11454
12282
|
const found = new Map, disabled = new Set;
|
|
@@ -11467,7 +12295,7 @@ function codexPlugins(root) {
|
|
|
11467
12295
|
continue;
|
|
11468
12296
|
let versions = [];
|
|
11469
12297
|
try {
|
|
11470
|
-
versions = readdirSync3(
|
|
12298
|
+
versions = readdirSync3(join23(root, "plugins", "cache", market, plugin));
|
|
11471
12299
|
} catch {}
|
|
11472
12300
|
out.push({ name, version: versions.length === 1 ? version2(versions[0]) : null });
|
|
11473
12301
|
}
|
|
@@ -11487,8 +12315,8 @@ function absoluteContinuityPath(value) {
|
|
|
11487
12315
|
var str3 = (value) => typeof value === "string" && value ? value : undefined, PLUGINS, CLAUDE_ONLY, text = (file) => {
|
|
11488
12316
|
let fd;
|
|
11489
12317
|
try {
|
|
11490
|
-
fd =
|
|
11491
|
-
const st =
|
|
12318
|
+
fd = openSync8(file, constants7.O_RDONLY | constants7.O_NONBLOCK);
|
|
12319
|
+
const st = fstatSync7(fd);
|
|
11492
12320
|
if (!st.isFile() || st.size > 1024 * 1024)
|
|
11493
12321
|
return;
|
|
11494
12322
|
return readFileSync11(fd, "utf8");
|
|
@@ -11496,7 +12324,7 @@ var str3 = (value) => typeof value === "string" && value ? value : undefined, PL
|
|
|
11496
12324
|
return;
|
|
11497
12325
|
} finally {
|
|
11498
12326
|
if (fd !== undefined)
|
|
11499
|
-
|
|
12327
|
+
closeSync8(fd);
|
|
11500
12328
|
}
|
|
11501
12329
|
}, json = (file) => {
|
|
11502
12330
|
try {
|
|
@@ -11505,7 +12333,7 @@ var str3 = (value) => typeof value === "string" && value ? value : undefined, PL
|
|
|
11505
12333
|
} catch {
|
|
11506
12334
|
return;
|
|
11507
12335
|
}
|
|
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() ||
|
|
12336
|
+
}, 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
12337
|
var init_native_policy_aux = __esm(() => {
|
|
11510
12338
|
PLUGINS = new Map([["claude-mem", "claude-mem"], ["ponytail", "ponytail"], ["open-code-review", "open-code-review"]]);
|
|
11511
12339
|
CLAUDE_ONLY = new Map([["codex@openai-codex", "codex-plugin"]]);
|
|
@@ -11548,9 +12376,9 @@ var init_native_cli_launch_common = __esm(() => {
|
|
|
11548
12376
|
});
|
|
11549
12377
|
|
|
11550
12378
|
// src/adapters/definitions/native-cli-claude-launch.ts
|
|
11551
|
-
import { mkdirSync as
|
|
12379
|
+
import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync9, renameSync as renameSync5, unlinkSync as unlinkSync3, readFileSync as readFileSync12 } from "fs";
|
|
11552
12380
|
import { randomUUID as randomUUID10 } from "crypto";
|
|
11553
|
-
import { resolve as resolve16, join as
|
|
12381
|
+
import { resolve as resolve16, join as join24 } from "path";
|
|
11554
12382
|
function claudeChannel(userArgs) {
|
|
11555
12383
|
const bundle = resolve16(import.meta.dir, "../../../plugins/agent-hub/server.js");
|
|
11556
12384
|
for (let i = 0;i < userArgs.length; i++) {
|
|
@@ -11612,11 +12440,11 @@ function claudeNativeLaunch(ctx) {
|
|
|
11612
12440
|
const stateDir = ctx.facts?.stateDir ?? ctx.statusLine?.stateDir;
|
|
11613
12441
|
if (!stateDir)
|
|
11614
12442
|
throw new Error("Claude session settings require the hub state directory");
|
|
11615
|
-
const file =
|
|
12443
|
+
const file = join24(stateDir, `claude-settings-${randomUUID10()}.json`);
|
|
11616
12444
|
try {
|
|
11617
|
-
|
|
11618
|
-
|
|
11619
|
-
|
|
12445
|
+
mkdirSync11(stateDir, { recursive: true, mode: 448 });
|
|
12446
|
+
writeFileSync9(`${file}.tmp`, settingsArgs[1], { mode: 384, flag: "wx" });
|
|
12447
|
+
renameSync5(`${file}.tmp`, file);
|
|
11620
12448
|
} catch {
|
|
11621
12449
|
try {
|
|
11622
12450
|
unlinkSync3(`${file}.tmp`);
|
|
@@ -11669,7 +12497,7 @@ var init_native_cli_codex_launch = __esm(() => {
|
|
|
11669
12497
|
});
|
|
11670
12498
|
|
|
11671
12499
|
// src/hub/events.ts
|
|
11672
|
-
import { appendFileSync, closeSync as
|
|
12500
|
+
import { appendFileSync, closeSync as closeSync9, existsSync as existsSync8, fstatSync as fstatSync8, openSync as openSync9, readFileSync as readFileSync13, readSync as readSync6 } from "fs";
|
|
11673
12501
|
function readEvents(file, since = 0) {
|
|
11674
12502
|
if (!existsSync8(file))
|
|
11675
12503
|
return [];
|
|
@@ -11691,13 +12519,13 @@ var init_events = () => {};
|
|
|
11691
12519
|
// src/cli/recovery-session-file.ts
|
|
11692
12520
|
import { statSync as statSync4 } from "fs";
|
|
11693
12521
|
import { homedir as homedir6 } from "os";
|
|
11694
|
-
import { join as
|
|
12522
|
+
import { join as join25 } from "path";
|
|
11695
12523
|
function checkedSessionPath(binding, kind) {
|
|
11696
12524
|
if (kind === "rollout")
|
|
11697
|
-
return
|
|
11698
|
-
const config = binding.launch.env.CLAUDE_CONFIG_DIR ??
|
|
12525
|
+
return join25(binding.launch.env.CODEX_HOME ?? join25(homedir6(), ".codex"), "sessions");
|
|
12526
|
+
const config = binding.launch.env.CLAUDE_CONFIG_DIR ?? join25(homedir6(), ".claude");
|
|
11699
12527
|
const slug = binding.projectRoot.replace(/[^a-zA-Z0-9]/g, "-");
|
|
11700
|
-
return
|
|
12528
|
+
return join25(config, "projects", slug, `${binding.sessionId}.jsonl`);
|
|
11701
12529
|
}
|
|
11702
12530
|
function recordedFileState(file) {
|
|
11703
12531
|
try {
|
|
@@ -11710,7 +12538,7 @@ var init_recovery_session_file = () => {};
|
|
|
11710
12538
|
|
|
11711
12539
|
// src/adapters/definitions/native-recovery.ts
|
|
11712
12540
|
import { readdirSync as readdirSync4 } from "fs";
|
|
11713
|
-
import { basename as basename5, join as
|
|
12541
|
+
import { basename as basename5, join as join26 } from "path";
|
|
11714
12542
|
var rolloutState = (binding) => {
|
|
11715
12543
|
try {
|
|
11716
12544
|
return readdirSync4(checkedSessionPath(binding, "rollout"), { recursive: true }).some((name) => String(name).endsWith(".jsonl") && basename5(String(name)).includes(binding.sessionId));
|
|
@@ -11734,7 +12562,7 @@ var init_native_recovery = __esm(() => {
|
|
|
11734
12562
|
if (phase !== "plan")
|
|
11735
12563
|
return false;
|
|
11736
12564
|
let current, started = false, turned = false;
|
|
11737
|
-
for (const e of readEvents(
|
|
12565
|
+
for (const e of readEvents(join26(stateDir, "events.jsonl"))) {
|
|
11738
12566
|
if (e.type === "native_thread" && e.peer === binding.peer) {
|
|
11739
12567
|
current = e.thread;
|
|
11740
12568
|
if (e.thread === binding.sessionId && e.fresh)
|
|
@@ -11798,7 +12626,12 @@ var init_native_recovery = __esm(() => {
|
|
|
11798
12626
|
};
|
|
11799
12627
|
QWEN_RECOVERY_SESSION = {
|
|
11800
12628
|
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 } : {} })
|
|
12629
|
+
restoreArgs: (peer) => ({ ...startupArgs(peer), ...peer.sessionId ? { "session-id": peer.sessionId } : {} }),
|
|
12630
|
+
recoveryRefusal: (peer, phase = "plan") => {
|
|
12631
|
+
if (peer.args?.mode !== "tui")
|
|
12632
|
+
return;
|
|
12633
|
+
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)";
|
|
12634
|
+
}
|
|
11802
12635
|
};
|
|
11803
12636
|
});
|
|
11804
12637
|
|
|
@@ -11890,7 +12723,7 @@ var init_builtin_metadata = __esm(() => {
|
|
|
11890
12723
|
headlessForms = Object.freeze(["headless"]);
|
|
11891
12724
|
hubGrants = {
|
|
11892
12725
|
ask: "every write, edit and shell command asks",
|
|
11893
|
-
"ask-when-needed": "write and
|
|
12726
|
+
"ask-when-needed": "write, edit and proven bounded read-only shell commands inside the project run without asking; other shell commands and git writes still ask",
|
|
11894
12727
|
"never-ask": "every tool runs without asking, inside the hub sandbox and path guard"
|
|
11895
12728
|
};
|
|
11896
12729
|
CLAUDE_REVIEWER_VARIANT = {
|
|
@@ -12271,9 +13104,15 @@ var init_builtin_metadata = __esm(() => {
|
|
|
12271
13104
|
});
|
|
12272
13105
|
|
|
12273
13106
|
// src/adapters/definitions/native-cli-entry-qwen.ts
|
|
13107
|
+
function verifiedLaunch(value) {
|
|
13108
|
+
const launch = value;
|
|
13109
|
+
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))
|
|
13110
|
+
return;
|
|
13111
|
+
return { cmd: launch.cmd, args: launch.args, env: launch.env };
|
|
13112
|
+
}
|
|
12274
13113
|
async function runQwenEntry(ctx) {
|
|
12275
13114
|
const peer = ctx.instance ?? "qwen";
|
|
12276
|
-
const { args, cwd, stateDir, unattendedEnv, connect, fail, takeFlags } = ctx;
|
|
13115
|
+
const { args, cwd, stateDir, unattendedEnv, connect, fail, takeFlags, execWithEnv } = ctx;
|
|
12277
13116
|
const { launcherPreview } = ctx.helpers;
|
|
12278
13117
|
if (args.includes("--print-command") || args.includes("--dry-run")) {
|
|
12279
13118
|
try {
|
|
@@ -12284,28 +13123,55 @@ async function runQwenEntry(ctx) {
|
|
|
12284
13123
|
}
|
|
12285
13124
|
const { one, rest } = takeFlags(args, ["--mode", "--model", "--session-id"], []);
|
|
12286
13125
|
const adapter = ctx.definition;
|
|
13126
|
+
const usage = "usage: ahub qwen [--mode headless|tui] [--model <alias>] [--session-id <id>]";
|
|
12287
13127
|
if (one["--session-id"] !== undefined && !adapter.session.crashIdentity?.({ sessionId: one["--session-id"] }).sessionId)
|
|
12288
13128
|
fail("invalid Qwen session identity");
|
|
12289
|
-
if (rest.length || one["--mode"] !== undefined && !
|
|
12290
|
-
fail(
|
|
13129
|
+
if (rest.length || one["--mode"] !== undefined && !["headless", "tui"].includes(one["--mode"]))
|
|
13130
|
+
fail(usage);
|
|
13131
|
+
const mode = one["--mode"] === "tui" ? "tui" : "headless";
|
|
13132
|
+
if (mode === "tui" && ctx.instance)
|
|
13133
|
+
fail("extra Qwen instances require headless mode");
|
|
13134
|
+
if (mode === "tui" && !(process.stdin.isTTY && process.stdout.isTTY))
|
|
13135
|
+
fail("Qwen's TUI needs a terminal: run ahub qwen --mode tui in one, or use --mode headless");
|
|
12291
13136
|
const hub = await connect();
|
|
13137
|
+
let launch;
|
|
12292
13138
|
try {
|
|
12293
13139
|
const snapshot = (await hub.request({ t: "status" })).status;
|
|
12294
13140
|
const warning = retainedLaunchWarning(snapshot, peer, ctx.definition);
|
|
12295
13141
|
if (warning)
|
|
12296
13142
|
console.error(warning);
|
|
12297
|
-
const reply = await hub.request({ t: "start", peer, args: { mode
|
|
13143
|
+
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
13144
|
if (!reply.ok)
|
|
12299
13145
|
fail(reply.error);
|
|
12300
|
-
|
|
13146
|
+
if (mode === "tui") {
|
|
13147
|
+
if (reply.already)
|
|
13148
|
+
return console.log("qwen is already attached (its TUI is running); use that terminal");
|
|
13149
|
+
if (reply.adopted)
|
|
13150
|
+
return console.log("qwen: the hub read on from its saved position; the Qwen terminal it was driving is still running");
|
|
13151
|
+
launch = reply.launch;
|
|
13152
|
+
} else
|
|
13153
|
+
console.log(reply.already ? `${peer} is already attached` : `${peer} attached (headless); use ahub say @${peer} and the console for approvals`);
|
|
12301
13154
|
} finally {
|
|
12302
13155
|
hub.close();
|
|
12303
13156
|
}
|
|
13157
|
+
if (mode !== "tui")
|
|
13158
|
+
return;
|
|
13159
|
+
const verified = verifiedLaunch(launch);
|
|
13160
|
+
if (!verified)
|
|
13161
|
+
return fail("Qwen TUI launch metadata was not verified");
|
|
13162
|
+
const person = Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.startsWith("AGENTHUB_") || name === "AGENTHUB_HOME"));
|
|
13163
|
+
const env = { ...peerChildEnv(peer, person), ...verified.env };
|
|
13164
|
+
delete env.AGENTHUB_RECOVERY_OPERATION;
|
|
13165
|
+
delete env.AGENTHUB_UNATTENDED;
|
|
13166
|
+
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.");
|
|
13167
|
+
return execWithEnv(verified.cmd, verified.args, env);
|
|
12304
13168
|
}
|
|
12305
|
-
var init_native_cli_entry_qwen = () => {
|
|
13169
|
+
var init_native_cli_entry_qwen = __esm(() => {
|
|
13170
|
+
init_child_process();
|
|
13171
|
+
});
|
|
12306
13172
|
|
|
12307
13173
|
// src/adapters/definitions/native-cli-preview-qwen.ts
|
|
12308
|
-
import { join as
|
|
13174
|
+
import { join as join27 } from "path";
|
|
12309
13175
|
function previewQwen(ctx) {
|
|
12310
13176
|
const { tool, args, cwd, stateDir, cliDir, config } = ctx;
|
|
12311
13177
|
const state = previewState(args, ctx.baseEnvNames), { unresolved, value } = state;
|
|
@@ -12313,8 +13179,8 @@ function previewQwen(ctx) {
|
|
|
12313
13179
|
let launch;
|
|
12314
13180
|
const mode = value("--mode") ?? "headless", model = value("--model");
|
|
12315
13181
|
const adapter = ctx.definition;
|
|
12316
|
-
if (!
|
|
12317
|
-
throw new Error("Qwen
|
|
13182
|
+
if (!["headless", "tui"].includes(mode))
|
|
13183
|
+
throw new Error("unknown Qwen mode");
|
|
12318
13184
|
for (let i = 0;i < args.length; i += 2)
|
|
12319
13185
|
if (!["--model", "--mode", "--session-id"].includes(args[i]) || !args[i + 1])
|
|
12320
13186
|
throw new Error("unknown Qwen option");
|
|
@@ -12323,12 +13189,19 @@ function previewQwen(ctx) {
|
|
|
12323
13189
|
throw new Error("invalid Qwen session identity");
|
|
12324
13190
|
if (sessionId)
|
|
12325
13191
|
unresolved.push({ field: "sessionId", reason: "requested through ACP session/load at start; preview does not load or expose it" });
|
|
13192
|
+
if (mode === "tui") {
|
|
13193
|
+
const [bin = "qwen", ...prefix] = nativeCommand(config.qwen_cmd, ["qwen"]).filter((word) => word !== "--acp");
|
|
13194
|
+
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] : []] };
|
|
13195
|
+
unresolved.push({ field: "files", reason: "the daemon prepares the input and event files (0600) when the start is admitted; preview creates nothing" });
|
|
13196
|
+
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` });
|
|
13197
|
+
return finishPreview(ctx, launch, envNames, unresolved, undefined);
|
|
13198
|
+
}
|
|
12326
13199
|
launch = adapter.start.launch({
|
|
12327
13200
|
instance: tool,
|
|
12328
13201
|
form: "headless",
|
|
12329
13202
|
projectRoot: cwd,
|
|
12330
13203
|
stateDir,
|
|
12331
|
-
entrypoint:
|
|
13204
|
+
entrypoint: join27(cliDir, "main.js"),
|
|
12332
13205
|
userArgs: [],
|
|
12333
13206
|
unattended: false,
|
|
12334
13207
|
env: {},
|
|
@@ -12353,6 +13226,7 @@ var init_qwen = __esm(() => {
|
|
|
12353
13226
|
init_native_recovery();
|
|
12354
13227
|
init_acp();
|
|
12355
13228
|
init_qwen_native();
|
|
13229
|
+
init_native_factories();
|
|
12356
13230
|
QWEN_DEFINITION = {
|
|
12357
13231
|
api: 1,
|
|
12358
13232
|
kind: "qwen",
|
|
@@ -12371,10 +13245,11 @@ var init_qwen = __esm(() => {
|
|
|
12371
13245
|
conductorStartable: true,
|
|
12372
13246
|
hubSwitches: [],
|
|
12373
13247
|
ownedFlags: [],
|
|
12374
|
-
preflight: (request) => request.form !== undefined && request.form !== "headless"
|
|
13248
|
+
preflight: (request) => request.form !== undefined && request.form !== "headless" && request.form !== "tui" ? "unknown Qwen mode; usage: ahub qwen [--mode headless|tui]" : undefined,
|
|
13249
|
+
createTui: createQwenDual,
|
|
12375
13250
|
launch: (context) => {
|
|
12376
13251
|
if (context.form !== "headless")
|
|
12377
|
-
throw new Error("Qwen TUI is
|
|
13252
|
+
throw new Error("the Qwen TUI is launched from the files the daemon prepares; use ahub qwen --mode tui");
|
|
12378
13253
|
const launch = buildQwenLaunch(context.nativeCommand ?? ["qwen", "--acp"], context.model);
|
|
12379
13254
|
const argv = [launch.cmd, ...launch.args];
|
|
12380
13255
|
return { ...launch, metadata: { packageEntrypoint: context.entrypoint, argv, command: argv.map(shellQuote3).join(" "), env: {} } };
|
|
@@ -12414,7 +13289,7 @@ var init_qwen = __esm(() => {
|
|
|
12414
13289
|
turnEnd: "adapter",
|
|
12415
13290
|
budget: { account: "qwen" },
|
|
12416
13291
|
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"] },
|
|
13292
|
+
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
13293
|
display: QWEN_DISPLAY,
|
|
12419
13294
|
hooks: { recallPlatform: "qwen" }
|
|
12420
13295
|
};
|
|
@@ -12611,6 +13486,9 @@ function immutable(value, seen = new WeakSet) {
|
|
|
12611
13486
|
immutable(member, seen);
|
|
12612
13487
|
return Object.freeze(value);
|
|
12613
13488
|
}
|
|
13489
|
+
function isFixedBuiltinDefinition(definition) {
|
|
13490
|
+
return definition !== undefined && fixedBuiltinDefinitions.has(definition);
|
|
13491
|
+
}
|
|
12614
13492
|
function deriveAdapterLists(definitions) {
|
|
12615
13493
|
const ordered = (field, eligible = (_definition) => true) => definitions.map((definition, index) => ({ definition, index })).filter(({ definition }) => eligible(definition)).sort((left, right) => {
|
|
12616
13494
|
const a = left.definition.projection?.[field], b = right.definition.projection?.[field];
|
|
@@ -12780,7 +13658,7 @@ function assertDefinitionCoherence(definition) {
|
|
|
12780
13658
|
fail("reviewer", "unavailable reviewer must remain metadata-only");
|
|
12781
13659
|
}
|
|
12782
13660
|
function assertNativeCapabilityScope(definition) {
|
|
12783
|
-
if (
|
|
13661
|
+
if (isFixedBuiltinDefinition(definition))
|
|
12784
13662
|
return;
|
|
12785
13663
|
const declared = (block, key) => block !== undefined && (key in block);
|
|
12786
13664
|
const restricted = [
|
|
@@ -12788,7 +13666,10 @@ function assertNativeCapabilityScope(definition) {
|
|
|
12788
13666
|
["attach.admission", declared(definition.attach, "admission")],
|
|
12789
13667
|
["budget.resetSoftTokensOnResume", declared(definition.budget, "resetSoftTokensOnResume")],
|
|
12790
13668
|
["session.crashIdentity", declared(definition.session, "crashIdentity")],
|
|
13669
|
+
["session.nativeRevert", declared(definition.session, "nativeRevert")],
|
|
12791
13670
|
["start.daemonPort pi-rpc", definition.start.daemonPort === "pi-rpc"],
|
|
13671
|
+
["start.daemonPort qwen-dual", definition.start.daemonPort === "qwen-dual"],
|
|
13672
|
+
["start.createTui", declared(definition.start, "createTui")],
|
|
12792
13673
|
["routing", definition.routing !== undefined],
|
|
12793
13674
|
["projection", definition.projection !== undefined],
|
|
12794
13675
|
["instances.nativeHome", declared(definition.instances, "nativeHome")],
|
|
@@ -13225,6 +14106,14 @@ var init_work_lists = __esm(() => {
|
|
|
13225
14106
|
forbidden = new Set(["__proto__", "constructor", "prototype"]);
|
|
13226
14107
|
});
|
|
13227
14108
|
|
|
14109
|
+
// node_modules/@staix/agent-mem/src/memory/candidate-pending.ts
|
|
14110
|
+
var CANDIDATE_AGE_MS, CANDIDATE_SHOWN_GRACE_MS, CANDIDATE_DUE_MS;
|
|
14111
|
+
var init_candidate_pending = __esm(() => {
|
|
14112
|
+
CANDIDATE_AGE_MS = 30 * 86400000;
|
|
14113
|
+
CANDIDATE_SHOWN_GRACE_MS = 7 * 86400000;
|
|
14114
|
+
CANDIDATE_DUE_MS = 7 * 86400000;
|
|
14115
|
+
});
|
|
14116
|
+
|
|
13228
14117
|
// node_modules/@staix/agent-mem/src/memory/generation-sync.ts
|
|
13229
14118
|
var zero;
|
|
13230
14119
|
var init_generation_sync = __esm(() => {
|
|
@@ -13256,14 +14145,6 @@ var init_sync = __esm(() => {
|
|
|
13256
14145
|
liveGroups = new Set;
|
|
13257
14146
|
});
|
|
13258
14147
|
|
|
13259
|
-
// node_modules/@staix/agent-mem/src/memory/candidate-pending.ts
|
|
13260
|
-
var CANDIDATE_AGE_MS, CANDIDATE_SHOWN_GRACE_MS, CANDIDATE_DUE_MS;
|
|
13261
|
-
var init_candidate_pending = __esm(() => {
|
|
13262
|
-
CANDIDATE_AGE_MS = 30 * 86400000;
|
|
13263
|
-
CANDIDATE_SHOWN_GRACE_MS = 7 * 86400000;
|
|
13264
|
-
CANDIDATE_DUE_MS = 7 * 86400000;
|
|
13265
|
-
});
|
|
13266
|
-
|
|
13267
14148
|
// node_modules/@staix/agent-mem/src/memory/path-glob.ts
|
|
13268
14149
|
var init_path_glob = __esm(() => {
|
|
13269
14150
|
init_project2();
|
|
@@ -20192,7 +21073,7 @@ init_project();
|
|
|
20192
21073
|
// src/hub/instance-config.ts
|
|
20193
21074
|
init_registry();
|
|
20194
21075
|
import { readFileSync as readFileSync15 } from "fs";
|
|
20195
|
-
import { join as
|
|
21076
|
+
import { join as join30 } from "path";
|
|
20196
21077
|
|
|
20197
21078
|
// src/adapters/external-metadata.ts
|
|
20198
21079
|
import { types as types3 } from "util";
|
|
@@ -20269,8 +21150,8 @@ init_registry();
|
|
|
20269
21150
|
// src/hub/config-trust.ts
|
|
20270
21151
|
init_child_process();
|
|
20271
21152
|
init_git();
|
|
20272
|
-
import { lstatSync as
|
|
20273
|
-
import { dirname as dirname12, join as
|
|
21153
|
+
import { lstatSync as lstatSync13, statSync as statSync5 } from "fs";
|
|
21154
|
+
import { dirname as dirname12, join as join28 } from "path";
|
|
20274
21155
|
var MACHINE_LOCAL = [
|
|
20275
21156
|
"kimi_cmd",
|
|
20276
21157
|
"permission_modes",
|
|
@@ -20302,7 +21183,7 @@ function configTracked(cwd, name) {
|
|
|
20302
21183
|
return;
|
|
20303
21184
|
let opened;
|
|
20304
21185
|
try {
|
|
20305
|
-
opened = statSync5(
|
|
21186
|
+
opened = statSync5(join28(cwd, ".agenthub", name));
|
|
20306
21187
|
} catch {}
|
|
20307
21188
|
return r.stdout.split("\x00").filter(Boolean).some((row) => {
|
|
20308
21189
|
const path = row.slice(row.indexOf("\t") + 1);
|
|
@@ -20311,7 +21192,7 @@ function configTracked(cwd, name) {
|
|
|
20311
21192
|
if (!opened)
|
|
20312
21193
|
return path === `.agenthub/${name}`;
|
|
20313
21194
|
try {
|
|
20314
|
-
const s = statSync5(
|
|
21195
|
+
const s = statSync5(join28(cwd, path));
|
|
20315
21196
|
return !!opened && s.dev === opened.dev && s.ino === opened.ino;
|
|
20316
21197
|
} catch {
|
|
20317
21198
|
return false;
|
|
@@ -20368,7 +21249,7 @@ function readAdapterModuleDeclarations(cwd, layers) {
|
|
|
20368
21249
|
// src/hub/control-client.ts
|
|
20369
21250
|
init_project();
|
|
20370
21251
|
import { readFileSync as readFileSync14 } from "fs";
|
|
20371
|
-
import { join as
|
|
21252
|
+
import { join as join29, resolve as resolve17 } from "path";
|
|
20372
21253
|
function stateDirFor(cwd) {
|
|
20373
21254
|
return projectContext(cwd).stateDir;
|
|
20374
21255
|
}
|
|
@@ -20376,8 +21257,8 @@ var PROTOCOL = 16;
|
|
|
20376
21257
|
var RECOVERY_SOURCE_PROTOCOLS = [9, 10, 11, 12, 13, 14, 15, PROTOCOL];
|
|
20377
21258
|
function readControl(stateDir) {
|
|
20378
21259
|
try {
|
|
20379
|
-
const status = JSON.parse(readFileSync14(
|
|
20380
|
-
const token = readFileSync14(
|
|
21260
|
+
const status = JSON.parse(readFileSync14(join29(stateDir, "status.json"), "utf8"));
|
|
21261
|
+
const token = readFileSync14(join29(stateDir, "control-token"), "utf8").trim();
|
|
20381
21262
|
if (!Number.isInteger(status.controlPort) || status.controlPort < 1 || status.controlPort > 65535 || !token)
|
|
20382
21263
|
return;
|
|
20383
21264
|
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;
|
|
@@ -20561,6 +21442,37 @@ init_policy();
|
|
|
20561
21442
|
init_screen();
|
|
20562
21443
|
init_shard();
|
|
20563
21444
|
init_work_lists();
|
|
21445
|
+
// node_modules/@staix/agent-mem/src/memory/project-import.ts
|
|
21446
|
+
init_bridge();
|
|
21447
|
+
init_private_spans();
|
|
21448
|
+
init_shard();
|
|
21449
|
+
init_store();
|
|
21450
|
+
var PROJECT_IMPORT_MAX_BYTES = 64 * 1024 * 1024;
|
|
21451
|
+
var PROJECT_IMPORT_MAX_LINE_BYTES = 64 * 1024;
|
|
21452
|
+
var PROJECT_IMPORT_FUTURE_SKEW_MS = 5 * 60 * 1000;
|
|
21453
|
+
var NOTE_KEYS = new Set(["schema", "id", "at", "machine", "op", "project", "scope", "tier", "kind", "peer", "title", "text", "task", "about", "supersedes", "promotedFrom", "source", "expires"]);
|
|
21454
|
+
// node_modules/@staix/agent-mem/src/memory/external-import.ts
|
|
21455
|
+
init_aliases();
|
|
21456
|
+
|
|
21457
|
+
// node_modules/@staix/agent-mem/src/memory/candidates.ts
|
|
21458
|
+
init_candidate_pending();
|
|
21459
|
+
|
|
21460
|
+
// node_modules/@staix/agent-mem/src/memory/note-add.ts
|
|
21461
|
+
init_store();
|
|
21462
|
+
|
|
21463
|
+
// node_modules/@staix/agent-mem/src/memory/candidates.ts
|
|
21464
|
+
init_home();
|
|
21465
|
+
init_platform();
|
|
21466
|
+
init_private_spans();
|
|
21467
|
+
var CANDIDATE_RETENTION = { count: 500, ageMs: 30 * 86400000 };
|
|
21468
|
+
var COUNTER_RETENTION = { count: 1e4, ageMs: 90 * 86400000 };
|
|
21469
|
+
|
|
21470
|
+
// node_modules/@staix/agent-mem/src/memory/external-import.ts
|
|
21471
|
+
init_candidate_pending();
|
|
21472
|
+
init_platform();
|
|
21473
|
+
init_private_spans();
|
|
21474
|
+
init_screen();
|
|
21475
|
+
var EXTERNAL_IMPORT_LIMITS = { maxFiles: 256, maxDirectories: 512, maxEntriesPerDirectory: 512, maxDepth: 16, maxFileBytes: 1024 * 1024, maxCandidatesPerRun: 100, candidateTextChars: 3000, titleChars: 200 };
|
|
20564
21476
|
// node_modules/@staix/agent-mem/src/memory/cross-project.ts
|
|
20565
21477
|
init_search_fts();
|
|
20566
21478
|
init_capture();
|
|
@@ -20594,18 +21506,6 @@ var memoryUsage = (name = AMEM_NAMES.memory) => `usage: ${name} [list] [--tier s
|
|
|
20594
21506
|
var MEMORY_USAGE = memoryUsage();
|
|
20595
21507
|
var goalUsage = (name = AMEM_NAMES.goal) => `usage: ${name} [list] [--all] | show <id> | new <title> [--milestone <text>]... [--depends <id>]... [--shared] | link <id> <task id> | state <id> open|active|blocked|done|dropped | done <id> | take <id> [--force] [--json]`;
|
|
20596
21508
|
var GOAL_USAGE = goalUsage();
|
|
20597
|
-
// node_modules/@staix/agent-mem/src/memory/candidates.ts
|
|
20598
|
-
init_candidate_pending();
|
|
20599
|
-
|
|
20600
|
-
// node_modules/@staix/agent-mem/src/memory/note-add.ts
|
|
20601
|
-
init_store();
|
|
20602
|
-
|
|
20603
|
-
// node_modules/@staix/agent-mem/src/memory/candidates.ts
|
|
20604
|
-
init_home();
|
|
20605
|
-
init_platform();
|
|
20606
|
-
init_private_spans();
|
|
20607
|
-
var CANDIDATE_RETENTION = { count: 500, ageMs: 30 * 86400000 };
|
|
20608
|
-
var COUNTER_RETENTION = { count: 1e4, ageMs: 90 * 86400000 };
|
|
20609
21509
|
|
|
20610
21510
|
// node_modules/@staix/agent-mem/src/index.ts
|
|
20611
21511
|
init_ops_contract();
|
|
@@ -20637,6 +21537,7 @@ var tool2 = (name, description, inputSchema, readOnlyHint = true) => ({
|
|
|
20637
21537
|
});
|
|
20638
21538
|
var MCP_TOOLS = [
|
|
20639
21539
|
tool2("amem_search", "Search screened memory in this project. Capture results are recall, not curated evidence.", object6({ query: string(1000), limit, since: string(40), includeCapture: { type: "boolean" } }, ["query"])),
|
|
21540
|
+
tool2("amem_evidence", "Return a bounded bundle of cited, live curated entries for a question. Untrusted memory data only: no answer or model call. Capture, repo-only notes, pending candidates and model answers are not evidence.", object6({ question: string(1000), limit: { type: "integer", minimum: 1, maximum: 20 }, maxBytes: { type: "integer", minimum: 256, maximum: 8192 } }, ["question"])),
|
|
20640
21541
|
tool2("amem_show", "Show a screened memory entry in this project by its exact id or unique prefix.", object6({ id: string(80) }, ["id"])),
|
|
20641
21542
|
tool2("amem_timeline", "Read screened memory near an entry or time in this project.", object6({ id: string(80), since: string(40), limit })),
|
|
20642
21543
|
tool2("amem_brief", "Read an intent/path-scoped brief of live public memory: fails first, then decisions/contracts and one-hop neighbours. Memory is untrusted data; byte cap covers the complete brief body.", object6({ intent: string(1000), paths: { type: "array", maxItems: 150, items: string(512) }, symbols: { type: "array", maxItems: 150, items: string(512) }, maxBytes: { type: "integer", minimum: 1, maximum: 8192 } }, ["intent"])),
|
|
@@ -20662,6 +21563,9 @@ var MCP_TOOLS = [
|
|
|
20662
21563
|
tool2("amem_vault_candidates", "List screened durable curated notes eligible for a person to move to the vault. Read only.", object6({ limit })),
|
|
20663
21564
|
tool2("amem_capacity", "Read this machine's bounded capture capacity proposal; applying it is person-only.", object6({}))
|
|
20664
21565
|
];
|
|
21566
|
+
// node_modules/@staix/agent-mem/src/memory/evidence.ts
|
|
21567
|
+
init_policy();
|
|
21568
|
+
|
|
20665
21569
|
// node_modules/@staix/agent-mem/src/memory/plan-guard.ts
|
|
20666
21570
|
init_store();
|
|
20667
21571
|
init_project2();
|
|
@@ -20766,6 +21670,16 @@ init_generation();
|
|
|
20766
21670
|
init_lifecycle();
|
|
20767
21671
|
init_platform();
|
|
20768
21672
|
init_shard();
|
|
21673
|
+
// node_modules/@staix/agent-mem/src/memory/consolidation-clusters.ts
|
|
21674
|
+
init_search_fts();
|
|
21675
|
+
|
|
21676
|
+
// node_modules/@staix/agent-mem/src/memory/consolidate.ts
|
|
21677
|
+
init_policy();
|
|
21678
|
+
init_platform();
|
|
21679
|
+
|
|
21680
|
+
// node_modules/@staix/agent-mem/src/memory/consolidation-model.ts
|
|
21681
|
+
init_policy();
|
|
21682
|
+
init_platform();
|
|
20769
21683
|
// src/hub/tasks.ts
|
|
20770
21684
|
init_task_code_symbols();
|
|
20771
21685
|
init_test_integrity();
|
|
@@ -20781,7 +21695,7 @@ init_sdlc_metrics();
|
|
|
20781
21695
|
// package.json
|
|
20782
21696
|
var package_default = {
|
|
20783
21697
|
name: "@staix/agent-hub",
|
|
20784
|
-
version: "0.
|
|
21698
|
+
version: "0.18.0",
|
|
20785
21699
|
description: "Native multi-agent hub: Claude Code, Codex, Kimi Code, Pi and local inference as peers in one project",
|
|
20786
21700
|
license: "MIT",
|
|
20787
21701
|
type: "module",
|
|
@@ -20798,7 +21712,7 @@ var package_default = {
|
|
|
20798
21712
|
bun: ">=1.4.2"
|
|
20799
21713
|
},
|
|
20800
21714
|
dependencies: {
|
|
20801
|
-
"@staix/agent-mem": "0.
|
|
21715
|
+
"@staix/agent-mem": "0.8.0"
|
|
20802
21716
|
},
|
|
20803
21717
|
devDependencies: {
|
|
20804
21718
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
@@ -20852,6 +21766,14 @@ init_plan_conformance();
|
|
|
20852
21766
|
init_review_findings();
|
|
20853
21767
|
init_sdlc();
|
|
20854
21768
|
|
|
21769
|
+
// src/hub/sdlc-merge.ts
|
|
21770
|
+
init_review_findings();
|
|
21771
|
+
init_sdlc();
|
|
21772
|
+
|
|
21773
|
+
// src/hub/sdlc-mirror.ts
|
|
21774
|
+
init_envelope();
|
|
21775
|
+
var inFlight = new Set;
|
|
21776
|
+
|
|
20855
21777
|
// src/hub/tasks.ts
|
|
20856
21778
|
init_review_findings();
|
|
20857
21779
|
init_roles();
|
|
@@ -20870,13 +21792,13 @@ init_workflow();
|
|
|
20870
21792
|
init_deliverables();
|
|
20871
21793
|
|
|
20872
21794
|
// src/hub/deliverable-files.ts
|
|
20873
|
-
import { constants as
|
|
21795
|
+
import { constants as constants8, lstatSync as lstatSync14 } from "fs";
|
|
20874
21796
|
init_tools();
|
|
20875
21797
|
init_permission_mode();
|
|
20876
21798
|
init_project();
|
|
20877
21799
|
init_deliverables();
|
|
20878
21800
|
var CHUNK = 1024 * 1024;
|
|
20879
|
-
var NOFOLLOW =
|
|
21801
|
+
var NOFOLLOW = constants8.O_NOFOLLOW ?? 0;
|
|
20880
21802
|
|
|
20881
21803
|
// src/hub/tasks.ts
|
|
20882
21804
|
init_board();
|
|
@@ -20886,6 +21808,7 @@ init_project();
|
|
|
20886
21808
|
|
|
20887
21809
|
// src/hub/task-repository.ts
|
|
20888
21810
|
init_git();
|
|
21811
|
+
init_host_executable();
|
|
20889
21812
|
init_project();
|
|
20890
21813
|
init_spec_read();
|
|
20891
21814
|
var GIT_MAX_BYTES = 16 * 1024;
|
|
@@ -21108,7 +22031,7 @@ var INSTANCE_CONFIG_FILES = ["config.json", "config.local.json"];
|
|
|
21108
22031
|
function readInstanceConfigLayers(cwd) {
|
|
21109
22032
|
return INSTANCE_CONFIG_FILES.flatMap((name) => {
|
|
21110
22033
|
try {
|
|
21111
|
-
return [{ name, raw: JSON.parse(readFileSync15(
|
|
22034
|
+
return [{ name, raw: JSON.parse(readFileSync15(join30(cwd, ".agenthub", name), "utf8")) }];
|
|
21112
22035
|
} catch (error) {
|
|
21113
22036
|
if (error.code === "ENOENT")
|
|
21114
22037
|
return [];
|
|
@@ -29462,7 +30385,7 @@ class StdioServerTransport {
|
|
|
29462
30385
|
|
|
29463
30386
|
// src/adapters/claude-channel.ts
|
|
29464
30387
|
import { readFileSync as readFileSync16 } from "fs";
|
|
29465
|
-
import { join as
|
|
30388
|
+
import { join as join31 } from "path";
|
|
29466
30389
|
init_hub_tools();
|
|
29467
30390
|
init_envelope();
|
|
29468
30391
|
|
|
@@ -29504,7 +30427,7 @@ var channel = !toolsMode && process.env.AGENTHUB_CHANNEL === "1";
|
|
|
29504
30427
|
function roles2() {
|
|
29505
30428
|
const read = (name) => {
|
|
29506
30429
|
try {
|
|
29507
|
-
return JSON.parse(readFileSync16(
|
|
30430
|
+
return JSON.parse(readFileSync16(join31(projectRoot2, ".agenthub", name), "utf8")).roles ?? {};
|
|
29508
30431
|
} catch {
|
|
29509
30432
|
return {};
|
|
29510
30433
|
}
|
|
@@ -29523,7 +30446,7 @@ function sessionProfile() {
|
|
|
29523
30446
|
for (const name of ["config.json", "config.local.json"]) {
|
|
29524
30447
|
let data;
|
|
29525
30448
|
try {
|
|
29526
|
-
data = JSON.parse(readFileSync16(
|
|
30449
|
+
data = JSON.parse(readFileSync16(join31(projectRoot2, ".agenthub", name), "utf8"));
|
|
29527
30450
|
} catch (error) {
|
|
29528
30451
|
if (error.code === "ENOENT")
|
|
29529
30452
|
continue;
|
|
@@ -29550,7 +30473,7 @@ function sessionRoles() {
|
|
|
29550
30473
|
const read = (name) => {
|
|
29551
30474
|
let raw;
|
|
29552
30475
|
try {
|
|
29553
|
-
raw = JSON.parse(readFileSync16(
|
|
30476
|
+
raw = JSON.parse(readFileSync16(join31(projectRoot2, ".agenthub", name), "utf8")).catalog_roles;
|
|
29554
30477
|
} catch {
|
|
29555
30478
|
return {};
|
|
29556
30479
|
}
|
|
@@ -29580,7 +30503,7 @@ var HELD = `another session is attached to the hub as "${peerId}"; this one is s
|
|
|
29580
30503
|
var INBOX_CAP = 200;
|
|
29581
30504
|
function peerHeld() {
|
|
29582
30505
|
try {
|
|
29583
|
-
const status = JSON.parse(readFileSync16(
|
|
30506
|
+
const status = JSON.parse(readFileSync16(join31(stateDir, "status.json"), "utf8"));
|
|
29584
30507
|
const peer = status.peers?.[peerId];
|
|
29585
30508
|
if (channel && peer?.toolsOnly && !peer.claiming)
|
|
29586
30509
|
return false;
|