cofluxd 0.9.0 → 0.11.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 CHANGED
@@ -731,6 +731,10 @@ async function cmdHook() {
731
731
  agentSessionId: payload.session_id ?? payload["thread-id"] ?? undefined,
732
732
  // Claude Notification 的类型枚举(permission_prompt / agent_needs_input …),不含正文
733
733
  notification: typeof notification === "string" && notification ? notification : undefined,
734
+ // Stop/SubagentStop 独有:本会话在飞的后台工作(shell/subagent/monitor/workflow)。claude
735
+ // 官方设它就是为了让 hook 区分「真做完了」与「挂起等后台把自己叫醒」。只传条数——条目里的
736
+ // description 是自由文本(含路径与代码片段),按本命令的隐私边界不出机。
737
+ backgroundTasks: Array.isArray(payload.background_tasks) ? payload.background_tasks.length : undefined,
734
738
  };
735
739
  hookDebug("POST /hook", JSON.stringify(body));
736
740
  const res = await fetch(`http://127.0.0.1:${portResult.port}/hook`, {
@@ -757,6 +761,11 @@ async function cmdHook() {
757
761
 
758
762
  const AGENT_TIMEOUT_MS = 30_000;
759
763
  const DEFAULT_READ_LINES = 200;
764
+ // wait 的循环必须在 CLI 侧:单次 agentPost 撑不起长等待(daemon 控制 WS 有自己的往返超时)。
765
+ // 默认 30 分钟——编码任务常跑很久;轮询走 terminal.list(status 来自 sessionExit 事件链,
766
+ // 不受快照 ~2s 延迟影响),3 秒一次对本机 loopback 是零负担。
767
+ const DEFAULT_WAIT_TIMEOUT_S = 1800;
768
+ const WAIT_POLL_MS = 3000;
760
769
 
761
770
  async function agentPost(body) {
762
771
  const portResult = localGatewayPort();
@@ -820,8 +829,34 @@ async function cmdTerminal(values) {
820
829
  console.log(`# ${result.status}${exit}`);
821
830
  const text = tailLines(stripAnsi(result.ansi), lines);
822
831
  console.log(text || "(暂无输出)");
832
+ } else if (sub === "send") {
833
+ const taskId = positionals[2];
834
+ if (!taskId) die("terminal send 需要 <taskId>(用 cofluxd terminal list 查)");
835
+ const text = values.text ?? "";
836
+ if (!text && !values.enter) die(`terminal send 需要 --text "<文本>"(或至少 --enter 发一个回车)`);
837
+ await agentPost({ action: "terminal.send", taskId, text, enter: Boolean(values.enter) });
838
+ console.log(`已写入终端 ${taskId}(用 cofluxd terminal read ${taskId} 核对效果)`);
839
+ } else if (sub === "wait") {
840
+ const taskId = positionals[2];
841
+ if (!taskId) die("terminal wait 需要 <taskId>(用 cofluxd terminal list 查)");
842
+ const requested = Number(values.timeout);
843
+ const timeoutSec = Number.isFinite(requested) && requested > 0 ? requested : DEFAULT_WAIT_TIMEOUT_S;
844
+ const deadline = Date.now() + timeoutSec * 1000;
845
+ for (;;) {
846
+ const { terminals } = await agentPost({ action: "terminal.list" });
847
+ const t = terminals.find((x) => x.taskId === taskId);
848
+ if (!t) die(`本工作区没有终端 ${taskId}(用 cofluxd terminal list 查)`);
849
+ if (t.status === "exited") {
850
+ const exit = t.exitCode === undefined || t.exitCode === null ? "" : ` exit=${t.exitCode}`;
851
+ return void console.log(`# exited${exit}`);
852
+ }
853
+ if (Date.now() >= deadline) {
854
+ die(`等待超时(${timeoutSec}s):终端 ${taskId} 仍是 ${t.status}。可加大 --timeout,或 cofluxd terminal read ${taskId} 看现场`);
855
+ }
856
+ await sleep(WAIT_POLL_MS);
857
+ }
823
858
  } else {
824
- die(`terminal 需要子命令:new | list | read`);
859
+ die(`terminal 需要子命令:new | list | read | wait | send`);
825
860
  }
826
861
  }
827
862
 
@@ -832,6 +867,13 @@ async function cmdNotify() {
832
867
  console.log("已通知用户(工作区在侧栏转为「等待交互」)");
833
868
  }
834
869
 
870
+ async function cmdProgress() {
871
+ const message = positionals.slice(1).join(" ").trim();
872
+ if (!message) die(`progress 需要一句话,例如:cofluxd progress "复现了,正在定位 relay 重连"`);
873
+ await agentPost({ action: "progress", message });
874
+ console.log("已更新进度(显示在工作区卡片上,被下一条覆盖)");
875
+ }
876
+
835
877
  async function cmdPorts() {
836
878
  const { ports } = await agentPost({ action: "ports" });
837
879
  if (!ports.length) return void console.log("本工作区暂无监听端口");
@@ -860,7 +902,12 @@ const HELP = `cofluxd —— coflux daemon 管理
860
902
  cofluxd terminal list 列出本工作区的终端(含 status / 退出码)
861
903
  cofluxd terminal read <taskId> [--lines N]
862
904
  读某个终端的内容(纯文本,默认最后 200 行;终端已退出也能读)
905
+ cofluxd terminal wait <taskId> [--timeout <秒>]
906
+ 阻塞等到该终端退出,打印退出码(默认超时 30 分钟)
907
+ cofluxd terminal send <taskId> --text "<文本>" [--enter]
908
+ 往终端里输入文本(--enter 追加回车)。用户正在接管时会被拒
863
909
  cofluxd notify "<一句话>" 叫人:工作区在侧栏转为「等待交互」并显示这句话
910
+ cofluxd progress "<一句话>" 播报进度:显示在工作区卡片上,被下一条覆盖(不打扰用户)
864
911
  cofluxd ports 列出本工作区的监听端口及可直接打开的预览 URL
865
912
 
866
913
  up flags: --server <ws://.../daemon> --name <名> --shell <路径>
@@ -883,6 +930,9 @@ const { values, positionals } = parseArgs({
883
930
  title: { type: "string" },
884
931
  cmd: { type: "string" },
885
932
  lines: { type: "string" },
933
+ timeout: { type: "string" },
934
+ text: { type: "string" },
935
+ enter: { type: "boolean", default: false },
886
936
  version: { type: "string" },
887
937
  "bin-dir": { type: "string" },
888
938
  "no-start": { type: "boolean", default: false },
@@ -896,7 +946,7 @@ let cmd = positionals[0];
896
946
  if (values.help || cmd === "help") { console.log(HELP); process.exit(0); }
897
947
  if (!cmd) cmd = fs.existsSync(SETTINGS) ? "status" : "up"; // 首次裸跑 → 引导
898
948
 
899
- const handlers = { up: cmdUp, update: cmdUpdate, restart: cmdRestart, down: cmdDown, status: cmdStatus, doctor: cmdDoctor, fda: cmdFda, logs: cmdLogs, uninstall: cmdUninstall, hook: cmdHook, terminal: cmdTerminal, notify: cmdNotify, ports: cmdPorts };
949
+ const handlers = { up: cmdUp, update: cmdUpdate, restart: cmdRestart, down: cmdDown, status: cmdStatus, doctor: cmdDoctor, fda: cmdFda, logs: cmdLogs, uninstall: cmdUninstall, hook: cmdHook, terminal: cmdTerminal, notify: cmdNotify, progress: cmdProgress, ports: cmdPorts };
900
950
  const h = handlers[cmd];
901
951
  if (!h) die(`未知命令: ${cmd}${MIGRATED[cmd] ? `\n${MIGRATED[cmd]}` : ""}\n\n${HELP}`);
902
952
  await h(values);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cofluxd",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "description": "coflux daemon 管理 CLI:装/起/停/升级 Rust daemon(supervisor + worker)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -56,7 +56,44 @@ cofluxd terminal read <taskId> --lines 50
56
56
  **终端已经退出也能 read**——「命令跑完了看输出」正是最常用的场景。
57
57
 
58
58
  内容有最多约 2 秒的延迟(来自中心的定期快照),所以刚 `new` 完立刻 `read` 可能是空的。
59
- 要等一条命令跑完,隔几秒 `list` 一次看它转没转 `exited`,别忙等。
59
+
60
+ ### 等命令跑完
61
+
62
+ ```sh
63
+ cofluxd terminal wait <taskId> # 阻塞到该终端退出,打印退出码(默认最长等 30 分钟)
64
+ cofluxd terminal wait <taskId> --timeout 300 # 自定超时(秒);超时会明确报错并非零退出
65
+ ```
66
+
67
+ 要等一条命令跑完就用 `wait`,**别自己写轮询循环**——它一条命令阻塞到位,退出码直接给你。
68
+ 超时不代表命令失败,只是还没跑完:`read` 看看现场再决定继续等还是处理。
69
+
70
+ ### 往终端里输入
71
+
72
+ ```sh
73
+ cofluxd terminal send <taskId> --text "y" --enter # 输入一行并回车
74
+ cofluxd terminal send <taskId> --enter # 只按一个回车
75
+ ```
76
+
77
+ 用在命令要交互确认(y/N、选项)、或想在跑完的同一 shell 里补一条命令的时候。纪律:
78
+
79
+ - **先 `read` 再 `send`**:看清终端现在在等什么再输入,别盲打。
80
+ - **用户正在接管时会被拒**——这不是错误,是设计:人永远优先。被拒就停手,
81
+ 要沟通用 `notify`,别重试。
82
+ - send 超时后**不要直接重发**:先 `read` 确认刚才那次到底进没进去,重复输入比丢输入更糟。
83
+
84
+ ### 播报进度
85
+
86
+ ```sh
87
+ cofluxd progress "复现了,正在定位 relay 重连的时序"
88
+ ```
89
+
90
+ 一句话告诉用户你干到哪了,显示在工作区卡片上,被下一条覆盖。在关键节点更新:复现了、
91
+ 定位到了、修完在验、卡在哪。它**不打扰用户**,和 `notify` 是两条信道:
92
+
93
+ - `progress` = 播报(用户扫一眼就知道进展,不需要回应)
94
+ - `notify` = 叫人(工作区转「等待交互」,用户该来看看了)
95
+
96
+ 拿不准用哪个:不需要用户做任何事就用 `progress`。
60
97
 
61
98
  ### 叫人
62
99
 
@@ -82,7 +119,8 @@ cofluxd ports
82
119
 
83
120
  ## 边界
84
121
 
85
- - 你**只能开和读**,不能往别人的终端里打字。要交互就 `notify` 让用户接管。
122
+ - 你能开、读、等、输入,但**输入是人类优先的受限写权**:用户正在接管的终端你写不进去
123
+ (会被明确拒绝),用户随时接管也会把你顶掉。别和人抢终端。
86
124
  - 能看到的只有**你自己所在的工作区**,别的工作区和别的机器都看不见也碰不到。
87
125
  - 一个工作区同时活着的终端有上限(默认 8,含用户自己开的)。撞上限先 `list` 看看,
88
126
  多半是有跑完没收的;真是用户占满了,就 `notify` 告诉他,别硬试。