cofluxd 0.6.0 → 0.7.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/cofluxd.mjs +83 -1
- package/package.json +1 -1
package/cofluxd.mjs
CHANGED
|
@@ -664,6 +664,86 @@ function cmdUninstall(v) {
|
|
|
664
664
|
else console.log(`✓ 已卸载服务(保留二进制/配置/凭证于 ${HOME};--purge 可全清)`);
|
|
665
665
|
}
|
|
666
666
|
|
|
667
|
+
/* ------------------------------ hook:agent 事件信使 ------------------------------ */
|
|
668
|
+
// agent hook 的上报信使:用户在 claude/codex 的 hook 配置里指向本命令,事件发生时它把
|
|
669
|
+
// 事件名转发给本机 worker 的固定 gateway(POST /hook),供活动状态判定(执行中/等待交互)。
|
|
670
|
+
//
|
|
671
|
+
// 输入两种形态都收:claude 与 codex hooks 引擎走 stdin JSON;codex 旧式 notify 把 payload
|
|
672
|
+
// 作为最后一个 argv 传入。只转发事件名 + agent 会话 id + 本进程 pid/ppid——payload 里的
|
|
673
|
+
// prompt / 回答原文一律不出机(隐私边界)。
|
|
674
|
+
//
|
|
675
|
+
// 契约(worker 侧将来实现 /hook 时依赖):请求保持到收到响应才退出——worker 在处理期间
|
|
676
|
+
// 用上报的 pid 反查进程树归属哪个 session,本进程活着扫描才有效。
|
|
677
|
+
//
|
|
678
|
+
// 纪律:本命令绝不能干扰 agent 本体——任何失败(daemon 不在/端口不通/payload 畸形)都
|
|
679
|
+
// 静默退出 0(claude 把 Stop hook 的非零退出码解释为"阻止收尾");绝不写 stdout(claude
|
|
680
|
+
// 会把 hook 的 stdout 当决策 JSON 解析),调试信息走 stderr(COFLUX_HOOK_DEBUG=1 开启)。
|
|
681
|
+
const HOOK_STDIN_TIMEOUT_MS = 300; // stdin 没有数据时不能干等(notify 形态下 stdin 是继承的 TTY/空管道)
|
|
682
|
+
const HOOK_POST_TIMEOUT_MS = 2000;
|
|
683
|
+
|
|
684
|
+
const hookDebug = (...args) => { if (process.env.COFLUX_HOOK_DEBUG) console.error("[cofluxd hook]", ...args); };
|
|
685
|
+
|
|
686
|
+
async function readStdinJson() {
|
|
687
|
+
if (process.stdin.isTTY) return null;
|
|
688
|
+
const chunks = [];
|
|
689
|
+
const drained = (async () => { for await (const chunk of process.stdin) chunks.push(chunk); })().catch(() => {});
|
|
690
|
+
await Promise.race([drained, sleep(HOOK_STDIN_TIMEOUT_MS)]);
|
|
691
|
+
process.stdin.destroy(); // 超时后放掉 stdin,否则 for await 会吊着进程不退出
|
|
692
|
+
const raw = Buffer.concat(chunks).toString("utf8").trim();
|
|
693
|
+
if (!raw) return null;
|
|
694
|
+
try { return JSON.parse(raw); } catch { return null; }
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
async function cmdHook() {
|
|
698
|
+
try {
|
|
699
|
+
const agent = positionals[1];
|
|
700
|
+
if (agent !== "claude" && agent !== "codex") {
|
|
701
|
+
hookDebug(`未知 agent: ${agent ?? "(缺参)"}(需 claude|codex)`);
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
let payload = null;
|
|
705
|
+
if (positionals[2]) {
|
|
706
|
+
try { payload = JSON.parse(positionals[2]); } catch { /* 非 JSON 的多余参数,忽略 */ }
|
|
707
|
+
}
|
|
708
|
+
if (!payload) payload = await readStdinJson();
|
|
709
|
+
if (!payload || typeof payload !== "object") {
|
|
710
|
+
hookDebug("无有效 payload,忽略");
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
// claude/codex hooks 引擎用 hook_event_name;codex notify 用 type
|
|
714
|
+
const event = payload.hook_event_name || payload.type;
|
|
715
|
+
if (typeof event !== "string" || !event) {
|
|
716
|
+
hookDebug("payload 缺事件名,忽略");
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
const portResult = localGatewayPort();
|
|
720
|
+
if (!portResult.ok) {
|
|
721
|
+
hookDebug(portResult.error);
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
const body = {
|
|
725
|
+
agent,
|
|
726
|
+
event,
|
|
727
|
+
pid: process.pid,
|
|
728
|
+
ppid: process.ppid,
|
|
729
|
+
// agent 自身的会话标识(claude: session_id / codex notify: thread-id),供 worker 去重与调试
|
|
730
|
+
agentSessionId: payload.session_id ?? payload["thread-id"] ?? undefined,
|
|
731
|
+
};
|
|
732
|
+
hookDebug("POST /hook", JSON.stringify(body));
|
|
733
|
+
const res = await fetch(`http://127.0.0.1:${portResult.port}/hook`, {
|
|
734
|
+
method: "POST",
|
|
735
|
+
headers: { "content-type": "application/json" },
|
|
736
|
+
body: JSON.stringify(body),
|
|
737
|
+
signal: AbortSignal.timeout(HOOK_POST_TIMEOUT_MS),
|
|
738
|
+
});
|
|
739
|
+
hookDebug(`响应 ${res.status}`);
|
|
740
|
+
} catch (error) {
|
|
741
|
+
hookDebug(error?.message || String(error));
|
|
742
|
+
} finally {
|
|
743
|
+
process.exit(0); // 无论成败都干净退出:不给 agent 留非零退出码,也不让残留句柄吊住进程
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
667
747
|
const HELP = `cofluxd —— coflux daemon 管理
|
|
668
748
|
|
|
669
749
|
cofluxd 首次=up(打印浏览器授权链接),已配置=status
|
|
@@ -676,6 +756,8 @@ const HELP = `cofluxd —— coflux daemon 管理
|
|
|
676
756
|
cofluxd logs [-f] 看 daemon 日志
|
|
677
757
|
cofluxd down 停止
|
|
678
758
|
cofluxd uninstall [--purge] 卸载(--purge 连二进制/配置/凭证一并删)
|
|
759
|
+
cofluxd hook <claude|codex> [agent hook 信使] 读 stdin/argv 的事件 JSON,转发给本机 daemon
|
|
760
|
+
(在 claude/codex 的 hook 配置里指向本命令;失败静默,不干扰 agent)
|
|
679
761
|
|
|
680
762
|
up flags: --server <ws://.../daemon> --name <名> --shell <路径>
|
|
681
763
|
通用: --version <vX|latest>(不传时 up 沿用已有二进制,update 默认 latest) --bin-dir <dir>(用本地 cargo 产物) --no-start
|
|
@@ -707,7 +789,7 @@ let cmd = positionals[0];
|
|
|
707
789
|
if (values.help || cmd === "help") { console.log(HELP); process.exit(0); }
|
|
708
790
|
if (!cmd) cmd = fs.existsSync(SETTINGS) ? "status" : "up"; // 首次裸跑 → 引导
|
|
709
791
|
|
|
710
|
-
const handlers = { up: cmdUp, update: cmdUpdate, restart: cmdRestart, down: cmdDown, status: cmdStatus, doctor: cmdDoctor, fda: cmdFda, logs: cmdLogs, uninstall: cmdUninstall };
|
|
792
|
+
const handlers = { up: cmdUp, update: cmdUpdate, restart: cmdRestart, down: cmdDown, status: cmdStatus, doctor: cmdDoctor, fda: cmdFda, logs: cmdLogs, uninstall: cmdUninstall, hook: cmdHook };
|
|
711
793
|
const h = handlers[cmd];
|
|
712
794
|
if (!h) die(`未知命令: ${cmd}${MIGRATED[cmd] ? `\n${MIGRATED[cmd]}` : ""}\n\n${HELP}`);
|
|
713
795
|
await h(values);
|