u-foo 2.3.6 → 2.3.7

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": "2.3.6",
3
+ "version": "2.3.7",
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",
@@ -94,6 +94,10 @@ function isInternalLaunchMode(meta) {
94
94
  return mode === "internal" || mode === "internal-pty";
95
95
  }
96
96
 
97
+ function hasProviderSession(meta) {
98
+ return typeof meta?.provider_session_id === "string" && meta.provider_session_id.trim() !== "";
99
+ }
100
+
97
101
  /**
98
102
  * 订阅者管理
99
103
  */
@@ -412,9 +416,26 @@ class SubscriberManager {
412
416
  if (!this.busData.agents) return;
413
417
 
414
418
  for (const [id, meta] of Object.entries(this.busData.agents)) {
415
- if (isInternalLaunchMode(meta) && (meta.status === "inactive" || !isMetaActive(meta))) {
416
- delete this.busData.agents[id];
417
- this.cleanupSubscriberArtifacts(id);
419
+ if (isInternalLaunchMode(meta)) {
420
+ const recoverable = hasProviderSession(meta);
421
+ if (meta.status === "inactive") {
422
+ if (!recoverable) {
423
+ delete this.busData.agents[id];
424
+ this.cleanupSubscriberArtifacts(id);
425
+ }
426
+ continue;
427
+ }
428
+ if (!isMetaActive(meta)) {
429
+ if (recoverable) {
430
+ meta.status = "inactive";
431
+ meta.activity_state = "";
432
+ meta.last_seen = getTimestamp();
433
+ this.cleanupSubscriberArtifacts(id);
434
+ } else {
435
+ delete this.busData.agents[id];
436
+ this.cleanupSubscriberArtifacts(id);
437
+ }
438
+ }
418
439
  continue;
419
440
  }
420
441
  if (meta.status === "active" && !isMetaActive(meta)) {
package/src/daemon/ops.js CHANGED
@@ -594,7 +594,16 @@ async function spawnManagedHostAgent(
594
594
  return { child: null, subscriberId: resultSubscriberId, subscriberIds: [resultSubscriberId].filter(Boolean), sessionId, injectSock };
595
595
  }
596
596
 
597
- async function spawnInternalAgent(projectRoot, agent, count = 1, nickname = "", processManager = null, extraEnv = {}) {
597
+ async function spawnInternalAgent(
598
+ projectRoot,
599
+ agent,
600
+ count = 1,
601
+ nickname = "",
602
+ processManager = null,
603
+ extraEnv = {},
604
+ extraArgs = [],
605
+ options = {}
606
+ ) {
598
607
  const runner = resolveUfooRunnerPath();
599
608
  const logDir = getUfooPaths(projectRoot).runDir;
600
609
  fs.mkdirSync(logDir, { recursive: true });
@@ -627,10 +636,15 @@ async function spawnInternalAgent(projectRoot, agent, count = 1, nickname = "",
627
636
  // Daemon 预先在 bus 中注册
628
637
  bus.loadBusData();
629
638
  process.env.UFOO_PARENT_PID = String(originalPid);
639
+ const replaceAgentId = typeof options.replaceAgentId === "string" ? options.replaceAgentId.trim() : "";
640
+ if (replaceAgentId && bus.busData.agents && bus.busData.agents[replaceAgentId]) {
641
+ delete bus.busData.agents[replaceAgentId];
642
+ }
630
643
 
631
644
  const requestedNickname = nickname
632
645
  ? (count > 1 ? `${nickname}-${i + 1}` : nickname)
633
646
  : "";
647
+ const providerSessionId = typeof options.providerSessionId === "string" ? options.providerSessionId.trim() : "";
634
648
  const usePty = process.env.UFOO_INTERNAL_PTY !== "0";
635
649
  const launchMode = usePty ? "internal-pty" : "internal";
636
650
 
@@ -638,12 +652,14 @@ async function spawnInternalAgent(projectRoot, agent, count = 1, nickname = "",
638
652
  const joinResult = await bus.subscriberManager.join(sessionId, agentType, requestedNickname, {
639
653
  launchMode,
640
654
  parentPid: originalPid,
655
+ providerSessionId,
641
656
  });
642
657
  const finalNickname = joinResult.nickname || requestedNickname || "";
643
658
  bus.saveBusData();
644
659
 
645
660
  const runnerCmd = usePty ? "agent-pty-runner" : "agent-runner";
646
- const child = spawn(process.execPath, [runner, runnerCmd, agent], {
661
+ const args = Array.isArray(extraArgs) ? extraArgs : [];
662
+ const child = spawn(process.execPath, [runner, runnerCmd, agent, ...args], {
647
663
  // 关键改动:不使用 detached,daemon 作为父进程
648
664
  detached: false,
649
665
  stdio: ["ignore", errLog, errLog],
@@ -657,6 +673,7 @@ async function spawnInternalAgent(projectRoot, agent, count = 1, nickname = "",
657
673
  UFOO_NICKNAME: finalNickname,
658
674
  UFOO_LAUNCH_MODE: usePty ? "internal-pty" : "internal",
659
675
  UFOO_PARENT_PID: String(originalPid),
676
+ ...(providerSessionId ? { UFOO_PROVIDER_SESSION_ID: providerSessionId } : {}),
660
677
  },
661
678
  });
662
679
 
@@ -878,7 +895,8 @@ async function launchAgent(projectRoot, agent, count = 1, nickname = "", process
878
895
  count,
879
896
  nickname,
880
897
  processManager,
881
- launchEnvObject
898
+ launchEnvObject,
899
+ extraArgs
882
900
  );
883
901
  return { mode: "internal", launchScope, subscriberIds: result.subscriberIds };
884
902
  }
@@ -1072,11 +1090,6 @@ function collectRecoverableAgents(projectRoot, target = "") {
1072
1090
  continue;
1073
1091
  }
1074
1092
 
1075
- if (mode === "internal") {
1076
- skipped.push({ id, reason: "internal mode not supported for resume" });
1077
- continue;
1078
- }
1079
-
1080
1093
  recoverableEntries.push({ id, meta, agent });
1081
1094
  }
1082
1095
 
@@ -1113,19 +1126,48 @@ async function resumeAgents(projectRoot, target = "", processManager = null) {
1113
1126
  for (const item of recoverableEntries) {
1114
1127
  const nickname = resolveDisplayNickname(projectRoot, item.meta);
1115
1128
  const sessionId = item.meta.provider_session_id;
1116
- const reused = await tryReuseTerminal(projectRoot, item.id, item.meta, item.agent, sessionId);
1117
- if (!reused) {
1118
- const args = buildResumeArgs(item.agent, sessionId);
1119
- const envPrefix = "UFOO_SKIP_SESSION_PROBE=1";
1120
- if (mode === "tmux") {
1121
- // eslint-disable-next-line no-await-in-loop
1122
- await spawnTmuxWindow(projectRoot, item.agent, nickname, args, envPrefix);
1123
- } else {
1124
- // eslint-disable-next-line no-await-in-loop
1125
- await spawnManagedTerminalAgent(projectRoot, item.agent, nickname, processManager, args, envPrefix);
1129
+ const args = buildResumeArgs(item.agent, sessionId);
1130
+ let reused = false;
1131
+ let resumedId = item.id;
1132
+ if (mode === "internal") {
1133
+ // Internal agents have no terminal/pane to reattach. Start a fresh
1134
+ // daemon-managed runner and replace the old recoverable registration.
1135
+ // The provider session is still reused via the normal provider args.
1136
+ // eslint-disable-next-line no-await-in-loop
1137
+ const launchResult = await spawnInternalAgent(
1138
+ projectRoot,
1139
+ item.agent,
1140
+ 1,
1141
+ nickname,
1142
+ processManager,
1143
+ { UFOO_SKIP_SESSION_PROBE: "1" },
1144
+ args,
1145
+ { replaceAgentId: item.id, providerSessionId: sessionId }
1146
+ );
1147
+ resumedId = launchResult.subscriberIds && launchResult.subscriberIds[0]
1148
+ ? launchResult.subscriberIds[0]
1149
+ : item.id;
1150
+ } else {
1151
+ reused = await tryReuseTerminal(projectRoot, item.id, item.meta, item.agent, sessionId);
1152
+ if (!reused) {
1153
+ const envPrefix = "UFOO_SKIP_SESSION_PROBE=1";
1154
+ if (mode === "tmux") {
1155
+ // eslint-disable-next-line no-await-in-loop
1156
+ await spawnTmuxWindow(projectRoot, item.agent, nickname, args, envPrefix);
1157
+ } else {
1158
+ // eslint-disable-next-line no-await-in-loop
1159
+ await spawnManagedTerminalAgent(projectRoot, item.agent, nickname, processManager, args, envPrefix);
1160
+ }
1126
1161
  }
1127
1162
  }
1128
- resumed.push({ id: item.id, nickname, agent: item.agent, sessionId, reused });
1163
+ resumed.push({
1164
+ id: resumedId,
1165
+ previous_id: resumedId === item.id ? undefined : item.id,
1166
+ nickname,
1167
+ agent: item.agent,
1168
+ sessionId,
1169
+ reused,
1170
+ });
1129
1171
  }
1130
1172
 
1131
1173
  return { ok: true, resumed, skipped };