/** * cutie-connector setup — 一键配置 AI agent 平台 (OpenClaw / Hermes) + Cutie Connector * * 平台相关逻辑都在 src/platforms/ adapter;本文件只做: * - 流程编排(runSetup) * - 平台无关的 connector 自身安装(ensureGlobalInstall + setupAutoStart) * - Hermes cutie profile gateway 三层自启(setupHermesCutieAutoStart) * * Adapter 接口:src/platforms/base.ts */ import type { PlatformAdapter } from './platforms/base'; export interface SetupOptions { serverUrl: string; agentModel: string; /** 服务端下发的 AGENTS.md 模板;缺失时跳过 workspace 创建但仍跑 gateway/session 配置 */ agentsMd?: string; /** 服务端下发的 SOUL.md 模板;缺失时跳过 workspace 创建但仍跑 gateway/session 配置 */ soulMd?: string; } export interface SetupResult { workspaceCreated: boolean; agentAdded: boolean; sessionConfigured: boolean; responsesEndpointOk: boolean; autoStartConfigured: boolean; binaryPath: string | null; warnings: string[]; } /** * systemctl --user 需要 XDG_RUNTIME_DIR / DBUS_SESSION_BUS_ADDRESS 才能连上 * user manager。交互式登录 shell 自带;agent / cron 等非登录环境缺失——这正是 * Cutie-Claw-1 上安装脚本 `systemctl --user restart` 静默失败的原因。 * connector 自己构造这两个变量,让任何启动环境都能操作 user service。 */ export declare function systemctlUserEnv(): NodeJS.ProcessEnv; export declare function setupAutoStart(): boolean; /** * 层 2 守护自迁移:裸跑进程启动时自动把自己迁入 systemd user 守护。 * * KOL(或他的 agent)无论用什么方式把 connector 跑起来,都自动获得 * 崩溃自动重启 + 开机自启 + 升级无缝;不需要懂任何技术。 * * 返回 true = 守护服务已启动并通过 is-active 验证(短暂双实例后本进程退位), * caller 必须立即 process.exit(0)。返回 false = 服务未能启动(已回滚)或 * 环境不支持,维持裸跑(仍有 respawn 层 1 兜底)。 */ export declare function migrateToDaemonIfBare(): boolean; /** * M2(2026-07-03 Codex 存量审计):之前用 `line.includes('cutie-connector')` * 判定要删的 crontab 行——用户自己的、恰好提到 "cutie-connector" 的无关 cron * job(比如备份脚本注释、日志轮转任务)会被一起删掉。改为只匹配本安装器 * 自己写的精确命令形状:`@reboot start ...`(见 setupCrontab * 写入的格式),不含这个精确前缀的行一律保留。 * * 导出 + binPath 参数化供单测调用,理由同 isSameInstallDir 等:BIN_PATH * 是基于 os.homedir() 的模块级 const,`vi.mock('os', ...)` 对它不生效。 */ export declare function isManagedCrontabLine(line: string, binPath: string): boolean; export declare function ensureGlobalInstall(): string | null; /** * staging 事务化安装:新包先复制 + npm install 到 `.staging-`, * 完整性校验(cli.js + node_modules/ws)通过后才原子切换——旧 pkg rename 为 * `.prev`(保留一次回滚),staging rename 为 installDir,成功后 * 删掉 `.prev`。任何一步失败:staging 被清理(finally),installDir 保持 * 原样(要么从未被动过,要么切换失败时已经从 `.prev` 换回来)。 * * 导出供单测直接调用,路径参数化(理由同 isSameInstallDir/verifyInstallIntegrity: * installDir 基于 os.homedir() 的模块级 const,`vi.mock('os', ...)` 对它不生效)。 */ export declare function installViaStaging(pkgRoot: string, installDir: string, binDir: string, binPath: string): string | null; /** * pkgRoot 是否就是 installDir 本身(realpath 归一,处理符号链接/相对路径差异)。 * installDir 还不存在(首次安装)时不可能是同一目录,直接返回 false。 * * 导出供单测直接调用(installDir 参数化,测试不依赖 os.homedir()——`vi.mock('os', ...)` * 对本文件模块级 `const INSTALL_DIR = path.join(os.homedir(), ...)` 不生效,实测会 * 落到真实 $HOME,B4 排查中已踩过一次)。生产路径(ensureGlobalInstall)固定传 * 模块级 INSTALL_DIR,行为不变。 */ export declare function isSameInstallDir(pkgRoot: string, installDir: string): boolean; /** * 校验 cli.js + node_modules/ws 是否存在,写 BIN wrapper,返回 binPath(失败 * 返回 null)。导出供单测直接调用,理由同 isSameInstallDir。 * @param repairDeps true(pkgRoot === installDir 场景)时 ws 缺失会原地 * `npm install` 补一次再复查;false(正常复制流程,刚装完依赖)缺失直接判失败, * 不重复安装。 */ export declare function verifyInstallIntegrity(installDir: string, binDir: string, binPath: string, opts: { repairDeps: boolean; }): string | null; /** * 层 1 实现:非交互调用 hermes gateway install + 校验 unit 真的落盘且已 enable。 * cutie/workbench profile 共用(installHermesCutieUnit 泛化,行为对 cutie 完全 * 不变,仍保留原名+原签名作为薄封装,供既有测试直接调用)。 * * 2026-07-03 舰队核对教训:新版 hermes CLI 的 `gateway install` 会连续交互提问 * (Start now? / Start on boot?),此前直接 execSync(stdio:'pipe') 会永久卡死等 * stdin;喂 /dev/null 则 EOF 截断、命令看似结束但 unit 文件根本没写——4 台托管机 * 因此 gateway 无自启跑了数天。 * * 2026-07-03 二期加固(原 `yes | hermes ... install` 用 execSync 起一个 shell * 管道,`{timeout}` 杀的是 shell 进程,shell 被 SIGTERM 后 `yes` 和 `hermes` 两个 * 子进程有可能不会被同步回收,残留在后台):改用 spawnSync 直接起 hermes 本体, * `input` 预先喂几行 'y\n' 顶替 `yes |` 应答交互提问,`timeout` 直接作用于 * hermes 进程本身,被杀时不会有多余的中间 shell/yes 子进程残留。 * 代价:`input` 是有限字符串而非 `yes` 那种无限重复流——如果未来 hermes 交互 * 提问数超过下面给的行数,多出的提问会拿到 EOF 而非 'y'。当前已知提问数是 2 * (Start now? / Start on boot?),这里给 3 行留一点余量;EOF 截断的场景仍然会 * 被下面的 unit 落盘/enable 校验拦下,不会被误判成功——不是安全回归,只是 * 层 1 成功率可能比无限 `yes` 略低,层 2 crontab watchdog 兜底不受影响。 */ export declare function installHermesProfileUnit(homeDir: string, profileName: string): boolean; export declare function installHermesCutieUnit(homeDir: string): boolean; export declare function setupHermesCutieAutoStart(port: number): boolean; /** * workbench profile(策略工作台,scene=strategy_workbench)gateway 自启,三层 * fallback 结构与 setupHermesCutieAutoStart 对等。 * * 没有做成完全参数化共享函数:层 1(installHermesProfileUnit,已泛化+测试覆盖) * 直接复用;层 2/3(crontab watchdog / README 提示)独立实现而非把 * setupHermesCutieAutoStart 改成参数化——两份实现结构相同,唯一差异是 profile 名。 * cutie 版本的层 2 crontab 条目原先写的是 `cutie gateway start`(没有 `hermes -p` * 前缀,不是合法命令,层 2 watchdog 从未真正工作过),2026-07-03 二期加固已随 * setupHermesCutieAutoStart 一并修正为 `hermes -p cutie gateway start`;workbench * 从一开始就是技术上正确的 `hermes -p workbench gateway start` 形式,两边现在 * 写法一致。 */ export declare function setupHermesWorkbenchAutoStart(port: number): boolean; export declare function runSetup(opts: SetupOptions, platform: PlatformAdapter): Promise;