u-foo 1.7.2 → 1.7.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "u-foo",
3
- "version": "1.7.2",
3
+ "version": "1.7.3",
4
4
  "description": "Multi-Agent Workspace Protocol. Just add u. claude → uclaude, codex → ucodex.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "homepage": "https://ufoo.dev",
package/src/bus/daemon.js CHANGED
@@ -1,6 +1,6 @@
1
1
  const fs = require("fs");
2
2
  const path = require("path");
3
- const { readJSON, writeJSON, isPidAlive, isAgentPidAlive, ensureDir, safeNameToSubscriber, subscriberToSafeName } = require("./utils");
3
+ const { readJSON, writeJSON, isPidAlive, isAgentPidAlive, isMetaActive, ensureDir, safeNameToSubscriber, subscriberToSafeName } = require("./utils");
4
4
  const Injector = require("./inject");
5
5
  const QueueManager = require("./queue");
6
6
  const MessageManager = require("./message");
@@ -405,12 +405,13 @@ class BusDaemon {
405
405
  continue;
406
406
  }
407
407
 
408
- // 检查 PID 是否仍然存活
409
- if (meta.pid && !isAgentPidAlive(meta.pid)) {
408
+ // 检查 agent 是否仍然存活(PID + TTY 交叉检查)
409
+ if (!isMetaActive(meta)) {
410
410
  const now = new Date().toISOString().split("T")[1].slice(0, 8);
411
- console.log(`[daemon] ${now} Agent ${subscriber} (pid=${meta.pid}) is dead, marking inactive`);
411
+ console.log(`[daemon] ${now} Agent ${subscriber} (pid=${meta.pid || 0}) is dead, marking inactive`);
412
412
 
413
413
  meta.status = "inactive";
414
+ meta.activity_state = "";
414
415
  changed = true;
415
416
 
416
417
  // 清理队列目录和 offset
package/src/bus/utils.js CHANGED
@@ -310,23 +310,44 @@ function isMetaActive(meta) {
310
310
  // 2. PID 存活(最可靠)
311
311
  if (meta.pid && isAgentPidAlive(meta.pid)) return true;
312
312
 
313
- // 3. TTY 上有 agent 进程
313
+ // 3. PID 已记录但进程已死 确定离线
314
+ if (meta.pid) return false;
315
+
316
+ // 4. 无 PID(如 codex)— TTY 交叉校验
317
+ // 仅当 tty_shell_pid 也还活着时才信任 TTY 检查,
318
+ // 防止 TTY 上残留的僵尸进程导致误判存活
314
319
  if (meta.tty) {
315
320
  const ttyInfo = getTtyProcessInfo(meta.tty);
316
- if (ttyInfo && ttyInfo.hasAgent) return true;
321
+ if (ttyInfo && ttyInfo.hasAgent) {
322
+ // 如果记录了 tty_shell_pid,验证它还在
323
+ if (meta.tty_shell_pid) {
324
+ if (isPidAlive(meta.tty_shell_pid)) return true;
325
+ // shell pid 已死,TTY 上的进程是残留
326
+ return false;
327
+ }
328
+ // 无 tty_shell_pid,用 last_seen 超时兜底
329
+ if (meta.last_seen) {
330
+ const age = Date.now() - new Date(meta.last_seen).getTime();
331
+ return age <= HEARTBEAT_TIMEOUT_MS;
332
+ }
333
+ return true;
334
+ }
317
335
  }
318
336
 
319
- // 4. PID 存在但 dead(且 TTY 也没有 agent)→ 离线
320
- if (meta.pid) return false;
321
-
322
- // 5. 无 PID,用 last_seen 心跳超时兜底
337
+ // 5. PID TTY agent,用 last_seen 心跳超时兜底
323
338
  if (meta.status === "active" && meta.last_seen) {
324
339
  const age = Date.now() - new Date(meta.last_seen).getTime();
325
340
  return age <= HEARTBEAT_TIMEOUT_MS;
326
341
  }
327
342
 
328
- // 6. status=active 但无任何信息
329
- if (meta.status === "active") return true;
343
+ // 6. status=active 但无任何可靠信息 → 超时后判定离线
344
+ if (meta.status === "active") {
345
+ if (meta.joined_at) {
346
+ const age = Date.now() - new Date(meta.joined_at).getTime();
347
+ return age <= HEARTBEAT_TIMEOUT_MS;
348
+ }
349
+ return true;
350
+ }
330
351
 
331
352
  return false;
332
353
  }
@@ -839,6 +839,7 @@ function createCommandExecutor(options = {}) {
839
839
  alias,
840
840
  instance,
841
841
  dry_run: dryRun,
842
+ ...collectHostLaunchRequestContext(),
842
843
  });
843
844
  schedule(requestStatus, 1000);
844
845
  return;
package/src/cli.js CHANGED
@@ -791,6 +791,7 @@ async function runCli(argv) {
791
791
  alias,
792
792
  instance: opts.instance || "",
793
793
  dry_run: opts.dryRun === true,
794
+ ...collectHostLaunchRequestContext(),
794
795
  });
795
796
  if (opts.json) {
796
797
  console.log(JSON.stringify(resp?.data || {}, null, 2));
@@ -1684,6 +1685,7 @@ async function runCli(argv) {
1684
1685
  alias,
1685
1686
  instance,
1686
1687
  dry_run: dryRun,
1688
+ ...collectHostLaunchRequestContext(),
1687
1689
  });
1688
1690
  if (outputJson) {
1689
1691
  console.log(JSON.stringify(resp?.data || {}, null, 2));
@@ -190,6 +190,24 @@ function nowIso() {
190
190
  return new Date().toISOString();
191
191
  }
192
192
 
193
+ function buildLaunchHostContext(params = {}) {
194
+ const hostInjectSock = asTrimmedString(params.host_inject_sock || params.hostInjectSock);
195
+ const hostDaemonSock = asTrimmedString(params.host_daemon_sock || params.hostDaemonSock);
196
+ const hostName = asTrimmedString(params.host_name || params.hostName);
197
+ const hostSessionId = asTrimmedString(params.host_session_id || params.hostSessionId);
198
+ const context = {};
199
+ if (hostInjectSock) context.host_inject_sock = hostInjectSock;
200
+ if (hostDaemonSock) context.host_daemon_sock = hostDaemonSock;
201
+ if (hostName) context.host_name = hostName;
202
+ if (hostSessionId) context.host_session_id = hostSessionId;
203
+ if (params.host_capabilities && typeof params.host_capabilities === "object") {
204
+ context.host_capabilities = { ...params.host_capabilities };
205
+ } else if (params.hostCapabilities && typeof params.hostCapabilities === "object") {
206
+ context.host_capabilities = { ...params.hostCapabilities };
207
+ }
208
+ return context;
209
+ }
210
+
193
211
  function buildDefaultRuntime({
194
212
  groupId,
195
213
  instance,
@@ -309,6 +327,7 @@ function createGroupOrchestrator(options = {}) {
309
327
  const alias = asTrimmedString(params.alias);
310
328
  const instance = asTrimmedString(params.instance);
311
329
  const dryRun = params.dry_run === true || params.dryRun === true;
330
+ const launchHostContext = buildLaunchHostContext(params);
312
331
 
313
332
  if (!alias) {
314
333
  return { ok: false, error: "group run requires alias", status: "failed" };
@@ -377,6 +396,7 @@ function createGroupOrchestrator(options = {}) {
377
396
  agent: item.type,
378
397
  count: 1,
379
398
  nickname: item.nickname,
399
+ ...launchHostContext,
380
400
  };
381
401
 
382
402
  // eslint-disable-next-line no-await-in-loop
@@ -1080,11 +1080,26 @@ function startDaemon({ projectRoot, provider, model, resumeMode = "auto" }) {
1080
1080
  const alias = req.alias || req.template || "";
1081
1081
  const instance = req.instance || req.group_id || "";
1082
1082
  const dryRun = req.dry_run === true || req.dryRun === true;
1083
+ const hostInjectSock = req.host_inject_sock || req.hostInjectSock || "";
1084
+ const hostDaemonSock = req.host_daemon_sock || req.hostDaemonSock || "";
1085
+ const hostName = req.host_name || req.hostName || "";
1086
+ const hostSessionId = req.host_session_id || req.hostSessionId || "";
1087
+ const hostCapabilities =
1088
+ req.host_capabilities && typeof req.host_capabilities === "object"
1089
+ ? req.host_capabilities
1090
+ : ((req.hostCapabilities && typeof req.hostCapabilities === "object")
1091
+ ? req.hostCapabilities
1092
+ : null);
1083
1093
  try {
1084
1094
  const result = await daemonGroupOrchestrator.runGroup({
1085
1095
  alias,
1086
1096
  instance,
1087
1097
  dry_run: dryRun,
1098
+ host_inject_sock: hostInjectSock,
1099
+ host_daemon_sock: hostDaemonSock,
1100
+ host_name: hostName,
1101
+ host_session_id: hostSessionId,
1102
+ host_capabilities: hostCapabilities,
1088
1103
  });
1089
1104
  const ok = result && result.ok !== false;
1090
1105
  let reply = "";