@brierb/brier-cli 0.0.12 → 0.0.14

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.
@@ -22,15 +22,25 @@ export const RUNTIME_COMMANDS = Object.fromEntries(RUNTIME_REGISTRY.map((r) => [
22
22
  * AI runtime 单次(非交互)执行参数模板:命令前缀 flags,后面紧跟 prompt。
23
23
  * 服务端 task-start 携带 prompt 时,CLI 用它拼出
24
24
  * `spawn(<command>, [...flags, prompt])`;未列出的 runtime 按裸参数执行。
25
+ *
26
+ * OpenCode 使用 `--format json` 输出结构化事件(含 sessionID,供跨轮续接);
27
+ * 展示层会从中提取文本事件,JSON 事件本身不被当作最终正文。
25
28
  */
26
29
  export const RUNTIME_PROMPT_FLAGS = {
27
- OpenCode: ['run'],
30
+ OpenCode: ['run', '--format', 'json'],
28
31
  'Claude Code': ['-p'],
29
32
  'Codex CLI': ['exec'],
30
33
  'Gemini CLI': ['-p'],
31
34
  Aider: ['--message'],
32
35
  Goose: ['run'],
33
36
  };
37
+ /**
38
+ * 各 runtime 续接上一会话所需的参数名(值与 resumeSessionId 拼接)。
39
+ * 仅支持原生 resume/session 续接的 runtime 需要登记。
40
+ */
41
+ export const RUNTIME_RESUME_FLAGS = {
42
+ OpenCode: ['--session'],
43
+ };
34
44
  // macOS 自带磁盘分区工具 gpt(8) 位于 /usr/sbin/gpt,命中系统目录视为未安装。
35
45
  const SYSTEM_SBIN_RE = /^\/(usr\/)?sbin\//;
36
46
  const isExecutable = (file) => {
@@ -51,6 +51,7 @@ const startDaemon = (config) => {
51
51
  env: message.env,
52
52
  prompt: message.prompt,
53
53
  execMode: message.execMode,
54
+ resumeSessionId: message.resumeSessionId,
54
55
  });
55
56
  },
56
57
  onTaskCancel: (taskId) => {
@@ -1,12 +1,38 @@
1
1
  import { spawn } from 'node:child_process';
2
+ import { chmodSync } from 'node:fs';
3
+ import { dirname, join } from 'node:path';
4
+ import { createRequire } from 'node:module';
2
5
  import pty from 'node-pty';
3
- import { logger, resolveRuntimeExecutable, RUNTIME_PROMPT_FLAGS } from '../core/index.js';
6
+ import { logger, resolveRuntimeExecutable, RUNTIME_PROMPT_FLAGS, RUNTIME_RESUME_FLAGS, } from '../core/index.js';
7
+ const require = createRequire(import.meta.url);
8
+ /**
9
+ * 修复 node-pty 的 darwin spawn-helper 可执行权限。
10
+ *
11
+ * 现象:node-pty 在 macOS 上先 spawn 自带的 spawn-helper 再执行目标命令;
12
+ * 若该 helper 因下载/安装而缺失可执行位(0644),posix_spawnp 返回 EACCES,
13
+ * 且 node-pty 只抛出笼统的 "posix_spawnp failed."(无 errno),难以定位。
14
+ * 每次 pty 启动前兜底 chmod +x,兼容重装后权限再丢失的情况。
15
+ */
16
+ const ensurePtyHelperExecutable = () => {
17
+ if (process.platform !== 'darwin')
18
+ return;
19
+ try {
20
+ const pkgDir = dirname(require.resolve('node-pty/package.json'));
21
+ const helper = join(pkgDir, 'prebuilds', `darwin-${process.arch}`, 'spawn-helper');
22
+ chmodSync(helper, 0o755);
23
+ }
24
+ catch {
25
+ // helper 权限修正失败不致命:若 node-pty 自身可执行则正常走;否则抛错由上层捕获
26
+ }
27
+ };
4
28
  const MAX_CONCURRENT = 3;
5
29
  /** 取消后等待 SIGTERM 生效的时间,超时升级 SIGKILL(仅 pipe 模式需要;pty.kill 为同步终止) */
6
30
  const KILL_GRACE_MS = 2_000;
7
31
  /** dispose 整体等待上限 */
8
32
  const DISPOSE_TIMEOUT_MS = 2_000;
9
33
  const DISPOSE_POLL_MS = 100;
34
+ /** 交互 CLI 启动后到可接收输入的就绪等待(全屏 TUI 初始化较慢,如 opencode) */
35
+ const PTY_STARTUP_DELAY_MS = 1_200;
10
36
  export const createTaskExecutor = (callbacks) => {
11
37
  /** pipe 模式子进程(ChildProcess,原有实现) */
12
38
  const pipeProcs = new Map();
@@ -59,13 +85,19 @@ export const createTaskExecutor = (callbacks) => {
59
85
  };
60
86
  /**
61
87
  * 拼执行参数:
62
- * - prompt 模式(AI runtime):命令前缀 flags + prompt 原文
88
+ * - prompt 模式(AI runtime):命令前缀 flags + 续接参数(可选) + prompt 原文
63
89
  * - 命令模式:透传服务端给的 args
64
90
  */
65
91
  const buildArgs = (task) => {
66
92
  if (task.prompt) {
67
- const flags = RUNTIME_PROMPT_FLAGS[task.runtime] ?? [];
68
- return [...flags, task.prompt];
93
+ const args = [...(RUNTIME_PROMPT_FLAGS[task.runtime] ?? [])];
94
+ if (task.resumeSessionId) {
95
+ const flag = RUNTIME_RESUME_FLAGS[task.runtime];
96
+ if (flag)
97
+ args.push(...flag, task.resumeSessionId);
98
+ }
99
+ args.push(task.prompt);
100
+ return args;
69
101
  }
70
102
  return task.args ?? [];
71
103
  };
@@ -125,6 +157,7 @@ export const createTaskExecutor = (callbacks) => {
125
157
  const executePty = (task, cmd, args, childEnv, initialInput) => {
126
158
  let handle;
127
159
  try {
160
+ ensurePtyHelperExecutable();
128
161
  handle = pty.spawn(cmd, args, {
129
162
  name: 'xterm-256color',
130
163
  cols: 120,
@@ -139,9 +172,25 @@ export const createTaskExecutor = (callbacks) => {
139
172
  }
140
173
  ptyProcs.set(task.taskId, handle);
141
174
  logger.info(`Task ${task.taskId} started in pty mode: ${cmd} ${args.join(' ')}`, task.runtime);
142
- // prompt 作为首条会话消息敲入(补回车触发提交);无 prompt(command 模式)不注入
175
+ // prompt 作为首条会话消息敲入(补回车触发提交);无 prompt(command 模式)不注入。
176
+ // 时序:交互 CLI(尤其全屏 TUI,如 opencode)需要时间完成初始化,过早写入会被丢弃,
177
+ // 导致会话一直停在“等待输入”态;延迟到终端就绪后再注入。
143
178
  if (initialInput) {
144
- handle.write(initialInput);
179
+ const pendingWrite = setTimeout(() => {
180
+ try {
181
+ if (ptyProcs.get(task.taskId) === handle) {
182
+ handle.write(initialInput);
183
+ }
184
+ }
185
+ catch {
186
+ // 会话已结束等情况:忽略注入失败
187
+ }
188
+ }, PTY_STARTUP_DELAY_MS);
189
+ // 会话先于注入结束时取消定时器,避免向已死终端写入
190
+ const clearOnExit = () => {
191
+ clearTimeout(pendingWrite);
192
+ };
193
+ handle.onExit(clearOnExit);
145
194
  }
146
195
  handle.onData((data) => {
147
196
  // pty 只有一路输出(合并 stdout/stderr 的终端字节流),统一按 stdout 上行;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brierb/brier-cli",
3
- "version": "0.0.12",
3
+ "version": "0.0.14",
4
4
  "description": "Brier 平台接入命令行工具:通过加密隧道将本机接入 Brier 远程编码池,接收并执行远程任务",
5
5
  "keywords": [
6
6
  "brier",
@@ -10,8 +10,16 @@ export declare const RUNTIME_COMMANDS: Record<string, string>;
10
10
  * AI runtime 单次(非交互)执行参数模板:命令前缀 flags,后面紧跟 prompt。
11
11
  * 服务端 task-start 携带 prompt 时,CLI 用它拼出
12
12
  * `spawn(<command>, [...flags, prompt])`;未列出的 runtime 按裸参数执行。
13
+ *
14
+ * OpenCode 使用 `--format json` 输出结构化事件(含 sessionID,供跨轮续接);
15
+ * 展示层会从中提取文本事件,JSON 事件本身不被当作最终正文。
13
16
  */
14
17
  export declare const RUNTIME_PROMPT_FLAGS: Record<string, readonly string[]>;
18
+ /**
19
+ * 各 runtime 续接上一会话所需的参数名(值与 resumeSessionId 拼接)。
20
+ * 仅支持原生 resume/session 续接的 runtime 需要登记。
21
+ */
22
+ export declare const RUNTIME_RESUME_FLAGS: Record<string, readonly [string]>;
15
23
  /**
16
24
  * 解析 runtime 的可执行文件**绝对路径**:
17
25
  * 1. PATH 中查找(排除系统目录误报,如 /usr/sbin/gpt)
@@ -25,4 +25,6 @@ export interface TaskInfo {
25
25
  prompt?: string;
26
26
  /** 执行形态(可选):缺省 pipe(非交互,向后兼容);pty 为伪终端交互(支持 task-input) */
27
27
  execMode?: ExecMode;
28
+ /** 续接的 CLI 会话 ID(可选):opencode 等通过 --session 恢复上一会话上下文 */
29
+ resumeSessionId?: string;
28
30
  }
@@ -104,6 +104,8 @@ export type ServerMessage = {
104
104
  prompt?: string;
105
105
  /** 执行形态(可选):缺省 pipe(向后兼容旧服务端) */
106
106
  execMode?: ExecMode;
107
+ /** 续接的 CLI 会话 ID(如 opencode session);缺省不续接 */
108
+ resumeSessionId?: string;
107
109
  } | {
108
110
  type: 'task-cancel';
109
111
  taskId: string;