@brierb/brier-cli 0.0.7 → 0.0.9

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.
Files changed (63) hide show
  1. package/dist/commands/log.js +28 -0
  2. package/dist/commands/restart.js +9 -4
  3. package/dist/commands/run.js +13 -0
  4. package/dist/commands/start.js +2 -2
  5. package/dist/commands/status.js +11 -16
  6. package/dist/commands/stop.js +1 -1
  7. package/dist/config/credentials.js +25 -0
  8. package/dist/config/index.js +16 -0
  9. package/dist/config/load.js +26 -0
  10. package/dist/config/paths.js +14 -0
  11. package/dist/config/version.js +13 -0
  12. package/dist/core/index.js +11 -0
  13. package/dist/{runtimes.js → core/runtimes.js} +6 -0
  14. package/dist/daemon/DaemonManager.js +65 -66
  15. package/dist/daemon/DaemonRunner.js +97 -39
  16. package/dist/daemon/OutputBatcher.js +77 -0
  17. package/dist/daemon/TaskExecutor.js +72 -8
  18. package/dist/daemon/index.js +13 -0
  19. package/dist/daemon/state.js +100 -0
  20. package/dist/definitions/daemon.js +5 -0
  21. package/dist/definitions/index.js +12 -0
  22. package/dist/definitions/tunnel.js +9 -0
  23. package/dist/index.js +12 -31
  24. package/dist/tunnel/backoff.js +21 -0
  25. package/dist/tunnel/dispatcher.js +35 -0
  26. package/dist/tunnel/heartbeat.js +55 -0
  27. package/dist/tunnel/index.js +220 -0
  28. package/dist/tunnel/transport.js +112 -0
  29. package/dist/tunnel/url.js +10 -0
  30. package/package.json +3 -3
  31. package/types/commands/log.d.ts +5 -0
  32. package/types/commands/run.d.ts +5 -0
  33. package/types/commands/status.d.ts +1 -0
  34. package/types/config/credentials.d.ts +8 -0
  35. package/types/config/index.d.ts +16 -0
  36. package/types/config/load.d.ts +7 -0
  37. package/types/config/paths.d.ts +12 -0
  38. package/types/config/version.d.ts +2 -0
  39. package/types/core/index.d.ts +11 -0
  40. package/types/{runtimes.d.ts → core/runtimes.d.ts} +6 -0
  41. package/types/daemon/DaemonManager.d.ts +1 -1
  42. package/types/daemon/OutputBatcher.d.ts +39 -0
  43. package/types/daemon/TaskExecutor.d.ts +4 -2
  44. package/types/daemon/index.d.ts +13 -0
  45. package/types/daemon/state.d.ts +19 -0
  46. package/types/definitions/daemon.d.ts +58 -0
  47. package/types/definitions/index.d.ts +17 -0
  48. package/types/definitions/task.d.ts +25 -0
  49. package/types/definitions/tunnel.d.ts +112 -0
  50. package/types/tunnel/backoff.d.ts +32 -0
  51. package/types/tunnel/dispatcher.d.ts +26 -0
  52. package/types/tunnel/heartbeat.d.ts +36 -0
  53. package/types/tunnel/index.d.ts +38 -0
  54. package/types/tunnel/transport.d.ts +29 -0
  55. package/types/tunnel/url.d.ts +5 -0
  56. package/dist/config.js +0 -45
  57. package/dist/tunnel/TunnelClient.js +0 -218
  58. package/types/config.d.ts +0 -9
  59. package/types/tunnel/TunnelClient.d.ts +0 -10
  60. package/types/types.d.ts +0 -78
  61. /package/dist/{logger.js → core/logger.js} +0 -0
  62. /package/dist/{types.js → definitions/task.js} +0 -0
  63. /package/types/{logger.d.ts → core/logger.d.ts} +0 -0
@@ -0,0 +1,112 @@
1
+ import { WebSocket } from 'ws';
2
+ /**
3
+ * WebSocket 传输层封装:屏蔽 ws 库细节,向骨架暴露统一事件与命令。
4
+ *
5
+ * 职责边界:
6
+ * - connect() 每次调用会先清理上一个连接(移除监听 + terminate),再建立新连接;
7
+ * - 事件(open/message/close/error)通过 on() 订阅,订阅一次后跨多次重连保持有效;
8
+ * - 协议层 ping 自动回 pong,调用方无需感知;
9
+ * - send() 返回是否已入队:未连接或发送缓冲积压超限(背压)时返回 false,
10
+ * 由上层决定丢弃或重试,本模块不抛错。
11
+ */
12
+ /** 发送缓冲上限(字节):超过该值 send() 返回 false 触发背压,防止慢网时内存无上限增长 */
13
+ const MAX_BUFFERED_AMOUNT_BYTES = 4 * 1024 * 1024;
14
+ export const createWebSocketTransport = () => {
15
+ let ws = null;
16
+ const listeners = {
17
+ open: new Set(),
18
+ close: new Set(),
19
+ message: new Set(),
20
+ error: new Set(),
21
+ };
22
+ const emit = (event, ...args) => {
23
+ // 泛型下 Parameters 的元组关系无法直接展开,统一按 rest 参数调用
24
+ for (const callback of listeners[event]) {
25
+ callback(...args);
26
+ }
27
+ };
28
+ const teardown = () => {
29
+ if (ws) {
30
+ ws.removeAllListeners();
31
+ if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
32
+ ws.terminate();
33
+ }
34
+ ws = null;
35
+ }
36
+ };
37
+ const readyState = () => (ws ? ws.readyState : -1);
38
+ return {
39
+ connect(url, headers) {
40
+ teardown();
41
+ const socket = new WebSocket(url, { headers });
42
+ ws = socket;
43
+ socket.on('open', () => emit('open'));
44
+ socket.on('message', (data) => emit('message', data.toString()));
45
+ socket.on('close', (code, reason) => emit('close', code, reason.toString()));
46
+ socket.on('error', (err) => emit('error', err.message));
47
+ socket.on('ping', () => socket.pong());
48
+ },
49
+ send(data) {
50
+ if (ws === null || ws.readyState !== WebSocket.OPEN) {
51
+ return false;
52
+ }
53
+ if (ws.bufferedAmount > MAX_BUFFERED_AMOUNT_BYTES) {
54
+ return false;
55
+ }
56
+ try {
57
+ ws.send(data);
58
+ return true;
59
+ }
60
+ catch {
61
+ return false;
62
+ }
63
+ },
64
+ close(code, reason) {
65
+ if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) {
66
+ ws.close(code, reason);
67
+ }
68
+ },
69
+ terminate() {
70
+ ws?.terminate();
71
+ },
72
+ isOpen() {
73
+ return readyState() === WebSocket.OPEN;
74
+ },
75
+ isConnecting() {
76
+ return readyState() === WebSocket.CONNECTING;
77
+ },
78
+ isClosed() {
79
+ return ws === null || ws.readyState === WebSocket.CLOSED;
80
+ },
81
+ waitClosed(timeoutMs) {
82
+ return new Promise((resolve) => {
83
+ let settled = false;
84
+ const onClose = () => {
85
+ if (settled)
86
+ return;
87
+ settled = true;
88
+ cleanup();
89
+ resolve();
90
+ };
91
+ const timer = setTimeout(() => {
92
+ if (settled)
93
+ return;
94
+ settled = true;
95
+ cleanup();
96
+ resolve();
97
+ }, timeoutMs);
98
+ const cleanup = () => {
99
+ clearTimeout(timer);
100
+ listeners.close.delete(onClose);
101
+ };
102
+ listeners.close.add(onClose);
103
+ });
104
+ },
105
+ on(event, callback) {
106
+ listeners[event].add(callback);
107
+ return () => {
108
+ listeners[event].delete(callback);
109
+ };
110
+ },
111
+ };
112
+ };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * 隧道接入端点工具:把服务端 http(s) 地址转换为 WebSocket 隧道地址。
3
+ * 纯字符串转换(wss/ws://host/tunnel),无状态、无副作用。
4
+ */
5
+ export const toWsUrl = (serverUrl) => {
6
+ return (serverUrl
7
+ .replace(/^https:\/\//, 'wss://')
8
+ .replace(/^http:\/\//, 'ws://')
9
+ .replace(/\/$/, '') + '/tunnel');
10
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brierb/brier-cli",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "description": "Brier 平台接入命令行工具:通过加密隧道将本机接入 Brier 远程编码池,接收并执行远程任务",
5
5
  "keywords": [
6
6
  "brier",
@@ -19,8 +19,8 @@
19
19
  "types": "./types/index.d.ts"
20
20
  },
21
21
  "./tunnel": {
22
- "import": "./dist/tunnel/TunnelClient.js",
23
- "types": "./types/tunnel/TunnelClient.d.ts"
22
+ "import": "./dist/tunnel/index.js",
23
+ "types": "./types/tunnel/index.d.ts"
24
24
  }
25
25
  },
26
26
  "files": [
@@ -0,0 +1,5 @@
1
+ /**
2
+ * `brier daemon log`:查看后台服务日志。
3
+ * 输出 ~/.brier/daemon.log 最近 100 行;文件不存在时给出提示与路径。
4
+ */
5
+ export declare const logCommand: () => void;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * 命令执行包装:统一 try/catch 与失败退出码。
3
+ * commander 的 action 直接返回 runCommand(...),消除各命令重复的错误处理样板。
4
+ */
5
+ export declare const runCommand: (fn: () => void | Promise<void>) => Promise<void>;
@@ -1 +1,2 @@
1
+ /** `brier daemon status`:展示 daemon 运行状态与状态文件详情。 */
1
2
  export declare const statusCommand: () => void;
@@ -0,0 +1,8 @@
1
+ /** 读取持久化的接入令牌;文件缺失或损坏时返回 undefined。 */
2
+ export declare const readStoredToken: () => string | undefined;
3
+ /**
4
+ * 持久化接入令牌到 ~/.brier/credentials.json。
5
+ * 写入使用 mode 0600,并对已存在的文件强制收紧权限(防止先前被宽权限创建)。
6
+ * 失败会抛错,由调用方决定是否阻断启动。
7
+ */
8
+ export declare const writeCredentials: (token: string) => void;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * 配置域统一出口:提供 daemon 装配所需的全部环境信息。
3
+ *
4
+ * 目录内文件职责:
5
+ * - load.ts: loadConfig(),从环境变量装配 DaemonConfig
6
+ * - paths.ts: 应用路径常量(~/.brier 下的 PID/日志文件)
7
+ * - version.ts:CLI 版本读取(package.json)
8
+ */
9
+ /** daemon 装配配置(BRIER_SERVER_URL / BRIER_TOKEN → DaemonConfig) */
10
+ export { loadConfig } from './load.js';
11
+ /** 应用路径常量:BRIER_DIR / DAEMON_STATE_FILE / LOG_FILE / CREDENTIALS_FILE */
12
+ export { BRIER_DIR, DAEMON_STATE_FILE, LOG_FILE, CREDENTIALS_FILE } from './paths.js';
13
+ /** CLI 版本读取 */
14
+ export { readCliVersion } from './version.js';
15
+ /** 接入令牌持久化(credentials.json,0600) */
16
+ export { readStoredToken, writeCredentials } from './credentials.js';
@@ -0,0 +1,7 @@
1
+ import type { DaemonConfig } from '../definitions/index.js';
2
+ /**
3
+ * daemon 装配配置加载:从 BRIER_SERVER_URL / BRIER_TOKEN 环境变量
4
+ * 读取 serverUrl / token,并组合本机信息(hostname/os/runtimes/version)。
5
+ * 校验失败(缺少必填项)时抛出带说明的错误。
6
+ */
7
+ export declare const loadConfig: () => DaemonConfig;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * 应用文件路径常量。
3
+ * 集中到 ~/.brier/ 下,保证与 daemon 启动时的工作目录无关。
4
+ */
5
+ /** Brier 用户数据目录(~/.brier) */
6
+ export declare const BRIER_DIR: string;
7
+ /** daemon 运行状态文件(JSON,结构见 definitions/daemon.ts 的 DaemonState) */
8
+ export declare const DAEMON_STATE_FILE: string;
9
+ /** daemon 运行日志文件 */
10
+ export declare const LOG_FILE: string;
11
+ /** daemon 接入令牌持久化文件(0600,供 `daemon restart` 无参回退) */
12
+ export declare const CREDENTIALS_FILE: string;
@@ -0,0 +1,2 @@
1
+ /** CLI 自身版本:读取 dist 两级的 package.json(与 `brier --version` 同源)。 */
2
+ export declare const readCliVersion: () => string;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * 公共底座统一出口:无业务依赖的基础能力,供 config/daemon/tunnel/commands 各域使用。
3
+ *
4
+ * 目录内文件职责:
5
+ * - logger.ts: 文件日志(configureLogger / logger)
6
+ * - runtimes.ts:AI runtime 注册表、可执行文件解析与已安装探测
7
+ */
8
+ /** 文件日志 */
9
+ export { configureLogger, logger } from './logger.js';
10
+ /** AI runtime 注册表 / 命令 / prompt 参数 / 解析 / 探测 */
11
+ export * from './runtimes.js';
@@ -21,3 +21,9 @@ export declare const RUNTIME_PROMPT_FLAGS: Record<string, readonly string[]>;
21
21
  * runtime 一定可被执行,不依赖 daemon 进程的 PATH。
22
22
  */
23
23
  export declare const resolveRuntimeExecutable: (runtime: string) => string | null;
24
+ /**
25
+ * 探测本机已安装的 AI runtime 名称列表。
26
+ * 过滤注册表:只保留 resolveRuntimeExecutable 能解析出可执行文件的项
27
+ * (探测与执行共用同一解析,保证“探测到”的 runtime 一定能被 spawn)。
28
+ */
29
+ export declare const detectInstalledRuntimes: () => string[];
@@ -1,4 +1,4 @@
1
- import type { DaemonStatus } from '../types.js';
1
+ import type { DaemonStatus } from '../definitions/index.js';
2
2
  export interface DaemonManager {
3
3
  start: (options: {
4
4
  serverUrl: string;
@@ -0,0 +1,39 @@
1
+ import type { StreamType } from '../definitions/index.js';
2
+ /**
3
+ * 任务输出批量发送器(micro-batching)。
4
+ *
5
+ * 动机:task-output 是高频小消息,逐块上行会在传输、序列化与服务端逐帧 DB 写入上
6
+ * 放大开销。本模块按 (taskId, stream) 缓冲,满足以下任一条件即 flush:
7
+ * - 单缓冲累计达到 maxBatchBytes(大输出即时发送,不等时间窗);
8
+ * - 到达 flushIntervalMs(低流量输出不会滞留过久,保证可感知延迟有上界)。
9
+ *
10
+ * 语义保证:
11
+ * - 同一 (taskId, stream) 严格保序,合并为一条消息发出;
12
+ * - 不同 task/stream 互不阻塞,各自独立 flush;
13
+ * - 只聚合、不截断、不背压(背压仍由上层 safeSend/连接层负责)。
14
+ */
15
+ export interface OutputChunk {
16
+ taskId: string;
17
+ stream: StreamType;
18
+ /** 该缓冲项合并后的完整数据 */
19
+ data: string;
20
+ }
21
+ export interface OutputBatcherOptions {
22
+ /** 单个 (taskId, stream) 缓冲的字节阈值(按字符串 length 近似),达到即立即 flush */
23
+ maxBatchBytes?: number;
24
+ /** 周期 flush 时间窗(ms),保证低流量也有延迟上界 */
25
+ flushIntervalMs?: number;
26
+ /** flush 回调:一次回调可含多个缓冲项 */
27
+ onFlush: (chunks: OutputChunk[]) => void;
28
+ }
29
+ export interface OutputBatcher {
30
+ /** 追加一块输出;达到字节阈值会立即触发 flush */
31
+ push: (taskId: string, stream: StreamType, data: string) => void;
32
+ /** 立即 flush 指定 task 的全部缓冲(发送终态消息前调用,避免尾输出丢失) */
33
+ flushTask: (taskId: string) => void;
34
+ /** 立即 flush 全部缓冲(停止前调用) */
35
+ flushAll: () => void;
36
+ /** 清理定时器与残留缓冲(调用前应先 flushAll) */
37
+ dispose: () => void;
38
+ }
39
+ export declare const createOutputBatcher: (options: OutputBatcherOptions) => OutputBatcher;
@@ -1,4 +1,4 @@
1
- import type { TaskInfo } from '../types.js';
1
+ import type { TaskInfo } from '../definitions/index.js';
2
2
  export interface TaskExecutorCallbacks {
3
3
  onOutput: (taskId: string, stream: 'stdout' | 'stderr', data: string) => void;
4
4
  onComplete: (taskId: string, exitCode: number) => void;
@@ -6,7 +6,9 @@ export interface TaskExecutorCallbacks {
6
6
  }
7
7
  export interface TaskExecutor {
8
8
  execute: (task: TaskInfo) => void;
9
+ /** 取消单个任务:SIGTERM,宽限期后 SIGKILL */
9
10
  cancel: (taskId: string) => void;
10
- cancelAll: () => void;
11
+ /** 停止并收尾:对全部运行中任务 SIGTERM,等待退出,未退出的 SIGKILL */
12
+ dispose: () => Promise<void>;
11
13
  }
12
14
  export declare const createTaskExecutor: (callbacks: TaskExecutorCallbacks) => TaskExecutor;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * daemon 域统一出口。
3
+ *
4
+ * 说明:DaemonRunner.ts 是 daemon 子进程的入口脚本(模块顶层即执行 main(),
5
+ * 并校验 BRIER_DAEMON_MODE),由 DaemonManager 以文件路径 spawn,
6
+ * 不应被 import,故不在此导出。本目录其余模块均无副作用,可安全经此导入。
7
+ */
8
+ /** daemon 进程生命周期管理(前台 CLI 使用) */
9
+ export { createDaemonManager, type DaemonManager } from './DaemonManager.js';
10
+ /** 任务执行器(daemon 子进程内 spawn runtime) */
11
+ export { createTaskExecutor, type TaskExecutor, type TaskExecutorCallbacks, } from './TaskExecutor.js';
12
+ /** daemon 运行状态读取(status/restart 展示与回退) */
13
+ export { readDaemonState } from './state.js';
@@ -0,0 +1,19 @@
1
+ import type { DaemonState } from '../definitions/index.js';
2
+ /** 仅判断 pid 是否存活(kill 0)。 */
3
+ export declare const isProcessAlive: (pid: number) => boolean;
4
+ /**
5
+ * 校验状态记录对应的 daemon 是否存活:
6
+ * pid 存活 + 命令行含 DaemonRunner(防止 PID 复用后指向无关进程)。
7
+ * ps 不可用时退化为仅存活判断,避免跨平台误伤。
8
+ */
9
+ export declare const isDaemonRunning: (state: DaemonState) => boolean;
10
+ /** 读取运行状态;文件缺失或字段不全(含旧版格式)时返回 null。 */
11
+ export declare const readDaemonState: () => DaemonState | null;
12
+ /** 写入完整运行状态(Manager 启动时调用)。 */
13
+ export declare const writeDaemonState: (state: DaemonState) => void;
14
+ /** 合并更新状态(Runner 侧置 ready/bootError/tunnelState,保留其余字段)。 */
15
+ export declare const updateDaemonState: (patch: Partial<DaemonState>) => void;
16
+ /** 清理运行状态文件(不存在时静默)。 */
17
+ export declare const removeDaemonState: () => void;
18
+ /** 轮询等待 pid 退出;超时返回 false。 */
19
+ export declare const waitForProcessExit: (pid: number, timeoutMs?: number) => Promise<boolean>;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * daemon 的装配配置、生命周期状态与进程间通信文件。
3
+ * 本文件三个类型均为“daemon 进程自身/前台 CLI 管理 daemon”使用,不参与隧道线协议。
4
+ */
5
+ /**
6
+ * daemon 后台服务运行状态(前台 CLI 使用,如 `brier daemon status`)。
7
+ *
8
+ * 当前实现实际只产出 running | stopped(进程不在即清理状态文件并返回 stopped);
9
+ * error 为预留值,尚无产出点。
10
+ */
11
+ export type DaemonStatus = 'running' | 'stopped' | 'error';
12
+ /**
13
+ * daemon 装配配置。
14
+ *
15
+ * 由 config.loadConfig() 从 BRIER_SERVER_URL / BRIER_TOKEN 环境变量加载,
16
+ * DaemonRunner 启动后整体注入 TunnelClient;其中 hostname/os/runtimes/version
17
+ * 在握手时随 auth 消息上报服务端用于展示与任务路由。
18
+ */
19
+ export interface DaemonConfig {
20
+ /** Brier 服务端地址(http/https,连接时由 toWsUrl 转换为 ws/wss 并拼接 /tunnel) */
21
+ serverUrl: string;
22
+ /** 接入令牌(BRIER_TOKEN),同时用于 wss Authorization 头与 auth 消息 */
23
+ token: string;
24
+ /** 本机主机名(同时作为服务端按用户维度 upsert 工作电脑的标识键) */
25
+ hostname: string;
26
+ /** 操作系统描述(`${type} ${platform} ${arch}`,仅供展示) */
27
+ os: string;
28
+ /** 探测到的已安装 AI runtime 列表(探测与执行共用 resolveRuntimeExecutable) */
29
+ runtimes: string[];
30
+ /** CLI 自身版本(读取 package.json),供服务端展示客户端版本 */
31
+ version: string;
32
+ }
33
+ /**
34
+ * daemon 运行状态文件内容(~/.brier/daemon.json)。
35
+ *
36
+ * 前台 CLI 与 daemon 子进程之间没有 IPC 通道,二者通过该文件 + 信号协作:
37
+ * - 前台 DaemonManager:启动时写入(ready=false),轮询 ready 做就绪握手;
38
+ * - daemon 子进程(Runner):配置加载成功进入运行后置 ready=true,
39
+ * 启动早期失败时写 bootError,隧道状态变化时更新 tunnelState;
40
+ * - 前台 status/restart:读取展示与回退(serverUrl)。
41
+ *
42
+ * 身份校验:以 pid + 进程命令行(含 DaemonRunner)双重匹配,
43
+ * 避免 PID 被系统复用后误判/误杀其他进程。
44
+ */
45
+ export interface DaemonState {
46
+ /** daemon 子进程 PID */
47
+ pid: number;
48
+ /** 启动时间戳(ms) */
49
+ startTime: number;
50
+ /** 启动时连接的服务端地址,restart 未传 --server-url 时回退使用 */
51
+ serverUrl: string;
52
+ /** 子进程是否已完成启动(Runner 进入运行循环后置 true) */
53
+ ready: boolean;
54
+ /** 启动早期失败原因(仅失败时写入) */
55
+ bootError?: string;
56
+ /** 最近一次隧道状态(Runner 随状态机更新,供 status 展示) */
57
+ tunnelState?: string;
58
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * 类型契约统一出口。
3
+ *
4
+ * 目录按功能域拆分:
5
+ * - tunnel.ts:隧道域(线协议 StreamType / ClientMessage / ServerMessage + 连接状态机 TunnelState)
6
+ * - task.ts: 本地任务执行上下文(消息 → 子进程执行)
7
+ * - daemon.ts:daemon 装配配置 / 生命周期状态 / PID 文件
8
+ *
9
+ * 约定:业务模块一律从本文件('../definitions/index.js')导入,不要跨层直接引用子文件,
10
+ * 以便后续调整目录结构时只需改这里,不动各调用方。
11
+ */
12
+ /** 隧道域:输出流来源、线协议上行/下行消息、连接状态机 */
13
+ export type { StreamType, ClientMessage, ServerMessage, TunnelState } from './tunnel.js';
14
+ /** 本地任务执行上下文(TaskExecutor 入参) */
15
+ export type { TaskInfo } from './task.js';
16
+ /** daemon 生命周期相关:DaemonStatus / DaemonConfig / DaemonState */
17
+ export type { DaemonStatus, DaemonConfig, DaemonState } from './daemon.js';
@@ -0,0 +1,25 @@
1
+ /**
2
+ * 本地任务执行上下文。
3
+ *
4
+ * TunnelClient 收到下行 task-start 消息后,将其转换为本结构交给
5
+ * TaskExecutor.execute() 执行——是“隧道传输形态”(ServerMessage)
6
+ * 与“子进程执行形态”之间的进程内中间对象,不上报服务端。
7
+ */
8
+ export interface TaskInfo {
9
+ taskId: string;
10
+ /** 所属 AI runtime 名称;command 模式为空串 */
11
+ runtime: string;
12
+ /**
13
+ * 可执行命令:
14
+ * - command 模式:服务端下发的显式 shell 命令
15
+ * - runtime 模式:由 resolveRuntimeExecutable 解析为绝对路径(保证脱离受限 PATH 也能 spawn)
16
+ */
17
+ command: string;
18
+ args: string[];
19
+ /** 子进程工作目录(可选) */
20
+ cwd?: string;
21
+ /** 注入子进程的额外环境变量(可选) */
22
+ env?: Record<string, string>;
23
+ /** 自然语言指令(可选):存在时按 RUNTIME_PROMPT_FLAGS 拼到命令参数中执行 */
24
+ prompt?: string;
25
+ }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * 隧道域类型(daemon ⇄ Brier 服务端 的连接与消息)。
3
+ *
4
+ * 聚合三组归属不同的类型:
5
+ * 1. StreamType / ClientMessage / ServerMessage:线协议(WebSocket JSON 文本帧),
6
+ * 与服务端 Rust `brier_type::tunnel::*` 镜像,任何命名/字段改动两端必须同步;
7
+ * 2. TunnelState:daemon 进程内的连接状态机(服务端无对应,不上报)。
8
+ */
9
+ /**
10
+ * 任务输出流的来源。
11
+ *
12
+ * 由 daemon 的 TaskExecutor 转发子进程输出时标记(stdout / stderr),
13
+ * 随上行消息 task-output 一并上报给服务端。
14
+ *
15
+ * 注意:序列化值需与服务端 Rust 侧 `brier_type::tunnel::StreamType` 保持一致(lowercase),
16
+ * 新增流类型时两端必须同步修改。
17
+ */
18
+ export type StreamType = 'stdout' | 'stderr';
19
+ /**
20
+ * 隧道线协议消息(daemon ⇄ Brier 服务端,WebSocket JSON 文本帧)。
21
+ *
22
+ * 使用可辨识联合:以 `type` 字段区分消息,type 值为 kebab-case(如 task-output / auth-ok),
23
+ * 字段名为 camelCase。与服务端 Rust `brier_type::tunnel::{ClientMessage, ServerMessage}`
24
+ * 经 serde(tag="type", rename_all="kebab-case" + 字段 rename)逐字段对齐,
25
+ * 任何一端新增/删除字段或改动命名时,另一端必须同步修改,否则 JSON 解析会失败或丢字段。
26
+ */
27
+ /**
28
+ * 上行消息(daemon → 服务端)。
29
+ *
30
+ * - auth:连接建立后的第一条鉴权消息;token 同时通过 wss Authorization 头携带(双通道,服务端以 header 为准)
31
+ * - heartbeat:应用层心跳,触发服务端落库 last_seen_at 并回执 heartbeat-ack
32
+ * - task-output:任务实时输出;当前实现为 stdout/stderr 每收到一个数据块即发一条
33
+ * - task-complete:子进程正常退出(exitCode 为进程退出码)
34
+ * - task-error:执行失败(如找不到可执行文件、超过并发上限)
35
+ * - runtime-info:runtime 清单上报(服务端目前仅记录日志,属预留能力)
36
+ */
37
+ export type ClientMessage = {
38
+ type: 'auth';
39
+ token: string;
40
+ hostname: string;
41
+ os: string;
42
+ runtimes: string[];
43
+ /** CLI 自身版本(package.json),旧版客户端可能不带该字段 */
44
+ version?: string;
45
+ } | {
46
+ type: 'heartbeat';
47
+ timestamp: number;
48
+ } | {
49
+ type: 'task-output';
50
+ taskId: string;
51
+ stream: StreamType;
52
+ data: string;
53
+ } | {
54
+ type: 'task-complete';
55
+ taskId: string;
56
+ exitCode: number;
57
+ } | {
58
+ type: 'task-error';
59
+ taskId: string;
60
+ error: string;
61
+ } | {
62
+ type: 'runtime-info';
63
+ runtimes: string[];
64
+ };
65
+ /**
66
+ * 下行消息(服务端 → daemon)。
67
+ *
68
+ * - auth-ok:鉴权通过并完成注册,computerId 为本机在服务端的工作电脑 ID
69
+ * - auth-failed:鉴权失败(令牌无效等),reason 为失败原因
70
+ * - heartbeat-ack:心跳回执(echo 客户端发送的 timestamp)
71
+ * - task-start:下发任务执行;prompt 模式由 CLI 按 runtime 拼参数,command 模式直接执行
72
+ * - task-cancel:请求终止正在执行的子进程(SIGTERM)
73
+ * - query-runtimes:查询本机 runtime 清单(当前服务端不会主动发送,属预留;CLI 保留处理以兼容旧服务端)
74
+ */
75
+ export type ServerMessage = {
76
+ type: 'auth-ok';
77
+ computerId: string;
78
+ } | {
79
+ type: 'auth-failed';
80
+ reason: string;
81
+ } | {
82
+ type: 'heartbeat-ack';
83
+ timestamp: number;
84
+ } | {
85
+ type: 'task-start';
86
+ taskId: string;
87
+ /** 所属 AI runtime 名称(如 Claude Code / OpenCode);command 模式为空串 */
88
+ runtime: string;
89
+ /** 可执行命令:command 模式为显式命令;runtime 模式为空,由 CLI 探测解析 */
90
+ command: string;
91
+ args: string[];
92
+ /** 子进程工作目录(可选) */
93
+ cwd?: string;
94
+ /** 注入子进程的额外环境变量(可选) */
95
+ env?: Record<string, string>;
96
+ /** 自然语言指令(可选):存在时按 RUNTIME_PROMPT_FLAGS 拼入执行参数 */
97
+ prompt?: string;
98
+ } | {
99
+ type: 'task-cancel';
100
+ taskId: string;
101
+ } | {
102
+ type: 'query-runtimes';
103
+ };
104
+ /**
105
+ * 隧道连接状态机。
106
+ *
107
+ * 仅存在于 daemon 进程内(TunnelClient 内部维护、DaemonRunner 订阅打印),不上报服务端:
108
+ * connecting(握手)→ connected(收到 auth-ok)→ 断线进入 reconnecting;
109
+ * 收到 auth-failed 或重连次数耗尽(50 次退避上限)时进入 error 并停止,
110
+ * 主动 stop 时进入 disconnected。
111
+ */
112
+ export type TunnelState = 'connecting' | 'connected' | 'disconnected' | 'reconnecting' | 'error';
@@ -0,0 +1,32 @@
1
+ /**
2
+ * 重连退避策略(纯逻辑,无 IO、无副作用)。
3
+ *
4
+ * 与原实现的等价行为:指数退避(1s 起步、封顶 maxMs),累计 maxAttempts 次后放弃。
5
+ * next() 返回 -1 表示“已耗尽、应停止重连”;attempts 仅供日志展示尝试次数。
6
+ */
7
+ export interface BackoffOptions {
8
+ /** 基础延迟(ms),首次重连即使用该值 */
9
+ baseMs: number;
10
+ /** 延迟封顶(ms) */
11
+ maxMs: number;
12
+ /** 最大重连尝试次数,达到后放弃 */
13
+ maxAttempts: number;
14
+ /**
15
+ * 是否启用 full jitter:在 [0, 计算延迟] 内取随机值,
16
+ * 避免大量客户端同时断线后以完全相同的节奏重连(重连风暴)。
17
+ * 默认开启。
18
+ */
19
+ jitter?: boolean;
20
+ }
21
+ export interface Backoff {
22
+ /**
23
+ * 计算下一次等待毫秒数。
24
+ * 已尝试次数达到 maxAttempts 时返回 -1(调用方应停止重连)。
25
+ */
26
+ next(): number;
27
+ /** 已排定/已执行的尝试次数(供日志与判断) */
28
+ readonly attempts: number;
29
+ /** 连接成功后归零 */
30
+ reset(): void;
31
+ }
32
+ export declare const createBackoff: (options: BackoffOptions) => Backoff;
@@ -0,0 +1,26 @@
1
+ import type { ServerMessage } from '../definitions/index.js';
2
+ /**
3
+ * 服务端下行消息解析与分发。
4
+ *
5
+ * 只做两件事:把 WS 文本帧 JSON.parse 成 ServerMessage,再按 type 分发给对应回调。
6
+ * 不接触 ws、状态机或执行器——骨架通过回调把“协议 → 业务动作”接起来,
7
+ * 本模块因此不依赖网络与运行状态,职责保持单一。
8
+ */
9
+ export interface ServerMessageHandlers {
10
+ /** 鉴权通过(携带服务端分配的工作电脑 ID) */
11
+ onAuthOk: (computerId: string) => void;
12
+ /** 鉴权失败(reason 为原因) */
13
+ onAuthFailed: (reason: string) => void;
14
+ /** 心跳回执 */
15
+ onHeartbeatAck: (timestamp: number) => void;
16
+ /** 任务下发 */
17
+ onTaskStart: (message: Extract<ServerMessage, {
18
+ type: 'task-start';
19
+ }>) => void;
20
+ /** 任务取消 */
21
+ onTaskCancel: (taskId: string) => void;
22
+ /** 服务端查询本机 runtime 清单 */
23
+ onQueryRuntimes: () => void;
24
+ }
25
+ /** 解析并分发一条服务端消息;JSON 解析失败或未知 type 记日志后静默返回。 */
26
+ export declare const dispatchServerMessage: (raw: string, handlers: ServerMessageHandlers) => void;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * 应用层心跳策略。
3
+ *
4
+ * 只负责节奏:到点调用 onBeat() 发送心跳;onBeat 返回 true(已发出)时启动
5
+ * ack 超时计时,收到 ack 由调用方调 ack() 解除;超时触发 onTimeout()。
6
+ * 本模块“只上报不决策”——超时后如何处理(如关闭连接)由上层骨架决定。
7
+ *
8
+ * 注意:每次触发心跳前先清理上一次的 ack 超时计时器,避免重复计时器叠加。
9
+ */
10
+ export interface HeartbeatOptions {
11
+ /** 心跳发送间隔(ms) */
12
+ intervalMs: number;
13
+ /** 等待 ack 的超时(ms) */
14
+ timeoutMs: number;
15
+ /**
16
+ * 心跳发送回调。返回是否已真正发出(连接未打开时返回 false,
17
+ * 此时不启动 ack 超时计时)。
18
+ */
19
+ onBeat: () => boolean;
20
+ /** ack 超时(对端无响应)回调,由上层决定如何处置 */
21
+ onTimeout: () => void;
22
+ /**
23
+ * onBeat 抛错时的回调(可选)。setInterval 回调中的异常会作为
24
+ * uncaughtException 直接杀死进程,因此 beat() 内部必须吞掉。
25
+ */
26
+ onBeatError?: (error: unknown) => void;
27
+ }
28
+ export interface Heartbeat {
29
+ /** 启动心跳(幂等:先停止已有计时器再开始) */
30
+ start(): void;
31
+ /** 停止心跳并清理所有计时器 */
32
+ stop(): void;
33
+ /** 收到 ack 时调用:解除当前 ack 超时计时 */
34
+ ack(): void;
35
+ }
36
+ export declare const createHeartbeat: (options: HeartbeatOptions) => Heartbeat;