u-foo 2.2.3 → 2.3.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.
Files changed (67) hide show
  1. package/SKILLS/ufoo/SKILL.md +56 -12
  2. package/SKILLS/uinit/SKILL.md +3 -2
  3. package/modules/AGENTS.template.md +2 -1
  4. package/modules/bus/README.md +1 -1
  5. package/modules/context/SKILLS/uctx/SKILL.md +6 -4
  6. package/package.json +1 -1
  7. package/src/agent/codexThreadProvider.js +2 -2
  8. package/src/agent/controllerToolExecutor.js +24 -1
  9. package/src/agent/credentials/claude.js +85 -16
  10. package/src/agent/credentials/codex.js +251 -23
  11. package/src/agent/defaultBootstrap.js +3 -1
  12. package/src/agent/directAuthStatus.js +264 -0
  13. package/src/agent/internalRunner.js +18 -12
  14. package/src/agent/loopObservability.js +10 -0
  15. package/src/agent/loopRuntime.js +19 -0
  16. package/src/agent/ufooAgent.js +43 -13
  17. package/src/agent/upstreamTransport.js +23 -8
  18. package/src/bus/index.js +13 -5
  19. package/src/bus/message.js +157 -9
  20. package/src/bus/nickname.js +14 -3
  21. package/src/bus/subscriber.js +30 -10
  22. package/src/chat/agentDirectory.js +2 -2
  23. package/src/chat/commandExecutor.js +190 -8
  24. package/src/chat/commands.js +23 -4
  25. package/src/chat/completionController.js +30 -7
  26. package/src/chat/daemonMessageRouter.js +2 -1
  27. package/src/chat/index.js +9 -8
  28. package/src/cli/groupCoreCommands.js +5 -0
  29. package/src/cli.js +309 -0
  30. package/src/code/UCODE_PROMPT.md +3 -2
  31. package/src/code/nativeRunner.js +2 -1
  32. package/src/code/prompts/ufoo.js +3 -2
  33. package/src/config.js +16 -3
  34. package/src/context/doctor.js +1 -1
  35. package/src/daemon/groupOrchestrator.js +13 -9
  36. package/src/daemon/index.js +35 -18
  37. package/src/daemon/nicknameScope.js +37 -0
  38. package/src/daemon/ops.js +22 -10
  39. package/src/daemon/promptRequest.js +11 -2
  40. package/src/daemon/reporting.js +2 -3
  41. package/src/daemon/soloBootstrap.js +15 -3
  42. package/src/daemon/status.js +5 -1
  43. package/src/group/bootstrap.js +1 -1
  44. package/src/group/promptProfiles.js +106 -22
  45. package/src/group/templates.js +1 -0
  46. package/src/init/index.js +4 -0
  47. package/src/memory/historySearch.js +308 -0
  48. package/src/memory/index.js +653 -8
  49. package/src/providerapi/redactor.js +4 -1
  50. package/src/status/index.js +26 -2
  51. package/src/tools/handlers/memory.js +168 -0
  52. package/src/tools/index.js +12 -0
  53. package/src/tools/registry.js +12 -0
  54. package/src/tools/schemaFixtures.js +213 -0
  55. package/src/tools/tier1/editMemory.js +14 -0
  56. package/src/tools/tier1/forget.js +14 -0
  57. package/src/tools/tier1/recall.js +14 -0
  58. package/src/tools/tier1/remember.js +14 -0
  59. package/src/tools/tier1/searchHistory.js +14 -0
  60. package/src/tools/tier1/searchMemory.js +14 -0
  61. package/templates/groups/build-lane.json +44 -6
  62. package/templates/groups/build-ultra.json +6 -5
  63. package/templates/groups/design-system.json +84 -0
  64. package/templates/groups/product-discovery.json +9 -4
  65. package/templates/groups/ui-plan-review.json +84 -0
  66. package/templates/groups/ui-polish.json +6 -2
  67. package/templates/groups/verify-ship.json +9 -4
@@ -31,7 +31,11 @@ const {
31
31
  buildSoloBootstrapFingerprint,
32
32
  rollbackLaunchAfterRoleAssignmentFailure,
33
33
  } = require("./soloBootstrap");
34
- const { applyProjectNicknamePrefix } = require("./nicknameScope");
34
+ const {
35
+ applyProjectNicknamePrefix,
36
+ resolveDisplayNickname,
37
+ resolveScopedNickname,
38
+ } = require("./nicknameScope");
35
39
 
36
40
  let providerSessions = null;
37
41
  let probeHandles = new Map();
@@ -60,7 +64,7 @@ function normalizeLaunchAgent(agent = "") {
60
64
  return "";
61
65
  }
62
66
 
63
- async function renameSpawnedAgent(projectRoot, agentType, nickname, startIso) {
67
+ async function renameSpawnedAgent(projectRoot, agentType, nickname, startIso, scopedNickname = "") {
64
68
  if (!nickname) return null;
65
69
  const busPath = getUfooPaths(projectRoot).agentsFile;
66
70
  const targetType = normalizeBusAgentType(agentType);
@@ -79,11 +83,11 @@ async function renameSpawnedAgent(projectRoot, agentType, nickname, startIso) {
79
83
  await sleep(200);
80
84
  continue;
81
85
  }
82
- let candidates = entries.filter(([, meta]) => !meta.nickname);
86
+ let candidates = entries.filter(([, meta]) => !resolveDisplayNickname(projectRoot, meta));
83
87
  if (candidates.length === 0) candidates = entries;
84
88
  candidates.sort((a, b) => (a[1].joined_at || "").localeCompare(b[1].joined_at || ""));
85
89
  const [agentId] = candidates[candidates.length - 1];
86
- await eventBus.rename(agentId, nickname, "ufoo-agent");
90
+ await eventBus.rename(agentId, nickname, "ufoo-agent", { scopedNickname });
87
91
  return { ok: true, agent_id: agentId, nickname };
88
92
  } catch (err) {
89
93
  lastError = err && err.message ? err.message : String(err || "rename failed");
@@ -382,13 +386,20 @@ async function waitForNewSubscriber(projectRoot, agentType, existing, timeoutMs
382
386
  return null;
383
387
  }
384
388
 
385
- function checkAndCleanupNickname(projectRoot, nickname, { tty = "", agentType = "" } = {}) {
386
- if (!nickname) return { existing: null, cleaned: false };
389
+ function checkAndCleanupNickname(projectRoot, nickname, { tty = "", agentType = "", scopedNickname = "" } = {}) {
390
+ const conflictNickname = scopedNickname || applyProjectNicknamePrefix(projectRoot, nickname, {
391
+ agentType,
392
+ force: true,
393
+ });
394
+ if (!conflictNickname) return { existing: null, cleaned: false };
387
395
  const busPath = getUfooPaths(projectRoot).agentsFile;
388
396
  try {
389
397
  const bus = JSON.parse(fs.readFileSync(busPath, "utf8"));
390
398
  const entries = Object.entries(bus.agents || {})
391
- .filter(([, meta]) => meta && meta.nickname === nickname);
399
+ .filter(([, meta]) => {
400
+ const candidate = resolveScopedNickname(projectRoot, meta);
401
+ return meta && candidate === conflictNickname;
402
+ });
392
403
 
393
404
  if (entries.length === 0) {
394
405
  return { existing: null, cleaned: false };
@@ -431,7 +442,7 @@ function resolveSubscriberNickname(projectRoot, subscriberId) {
431
442
  try {
432
443
  const busPath = getUfooPaths(projectRoot).agentsFile;
433
444
  const bus = JSON.parse(fs.readFileSync(busPath, "utf8"));
434
- return bus.agents?.[subscriberId]?.nickname || "";
445
+ return resolveDisplayNickname(projectRoot, bus.agents?.[subscriberId] || {});
435
446
  } catch {
436
447
  return "";
437
448
  }
@@ -453,7 +464,8 @@ async function handleOps(projectRoot, ops = [], processManager = null) {
453
464
  continue;
454
465
  }
455
466
  const requestedNickname = String(op.nickname || "").trim();
456
- const nickname = applyProjectNicknamePrefix(projectRoot, requestedNickname, { agentType: agent });
467
+ const nickname = requestedNickname;
468
+ const scopedNickname = applyProjectNicknamePrefix(projectRoot, requestedNickname, { agentType: agent });
457
469
  const startTime = new Date(Date.now() - 1000);
458
470
  const startIso = startTime.toISOString();
459
471
  if (nickname && count > 1) {
@@ -468,7 +480,7 @@ async function handleOps(projectRoot, ops = [], processManager = null) {
468
480
  }
469
481
  try {
470
482
  // Check for existing agent with same nickname
471
- const { existing, cleaned } = checkAndCleanupNickname(projectRoot, nickname);
483
+ const { existing, cleaned } = checkAndCleanupNickname(projectRoot, nickname, { scopedNickname, agentType: agent });
472
484
  if (existing) {
473
485
  // Agent with this nickname already exists and is active
474
486
  results.push({
@@ -486,6 +498,7 @@ async function handleOps(projectRoot, ops = [], processManager = null) {
486
498
  }
487
499
  // eslint-disable-next-line no-await-in-loop
488
500
  const launchResult = await launchAgent(projectRoot, agent, count, nickname, processManager, {
501
+ scopedNickname,
489
502
  launchScope: op.launch_scope || "",
490
503
  terminalApp: op.terminal_app || "",
491
504
  tmuxLayoutContext:
@@ -555,7 +568,7 @@ async function handleOps(projectRoot, ops = [], processManager = null) {
555
568
  });
556
569
  if (nickname) {
557
570
  // eslint-disable-next-line no-await-in-loop
558
- const renameResult = await renameSpawnedAgent(projectRoot, agent, nickname, startIso);
571
+ const renameResult = await renameSpawnedAgent(projectRoot, agent, nickname, startIso, scopedNickname);
559
572
  if (renameResult) {
560
573
  results.push({ action: "rename", ...renameResult });
561
574
  }
@@ -615,10 +628,11 @@ async function handleOps(projectRoot, ops = [], processManager = null) {
615
628
  continue;
616
629
  }
617
630
  const targetMeta = eventBus.busData.agents[targetId] || {};
618
- nickname = applyProjectNicknamePrefix(projectRoot, requestedNickname, {
631
+ const scopedNickname = applyProjectNicknamePrefix(projectRoot, requestedNickname, {
619
632
  agentType: targetMeta.agent_type || "",
620
633
  });
621
- const result = await eventBus.rename(targetId, nickname, "ufoo-agent");
634
+ nickname = requestedNickname;
635
+ const result = await eventBus.rename(targetId, nickname, "ufoo-agent", { scopedNickname });
622
636
  results.push({
623
637
  action: "rename",
624
638
  ok: true,
@@ -1311,9 +1325,7 @@ function startDaemon({ projectRoot, provider, model, resumeMode = "auto" }) {
1311
1325
  const parsedCount = parseInt(count, 10);
1312
1326
  const finalCount = Number.isFinite(parsedCount) && parsedCount > 0 ? parsedCount : 1;
1313
1327
  const requestedProfile = String(prompt_profile || "").trim();
1314
- const explicitNickname = applyProjectNicknamePrefix(projectRoot, String(nickname || "").trim(), {
1315
- agentType: normalizedAgent,
1316
- });
1328
+ const explicitNickname = String(nickname || "").trim();
1317
1329
  if (requestedProfile && finalCount > 1) {
1318
1330
  socket.write(
1319
1331
  `${JSON.stringify({
@@ -2027,23 +2039,28 @@ function startDaemon({ projectRoot, provider, model, resumeMode = "auto" }) {
2027
2039
  if (skipProbe) joinOptions.skipProbe = true;
2028
2040
 
2029
2041
  let finalNickname = nickname || "";
2042
+ let scopedNickname = applyProjectNicknamePrefix(projectRoot, finalNickname, {
2043
+ agentType: normalizeBusAgentType(agentType),
2044
+ });
2030
2045
  if (finalNickname) {
2031
2046
  const nickCheck = checkAndCleanupNickname(projectRoot, finalNickname, {
2032
2047
  tty: tty || "",
2033
2048
  agentType: normalizeBusAgentType(agentType),
2049
+ scopedNickname,
2034
2050
  });
2035
2051
  if (nickCheck.existing) {
2036
2052
  finalNickname = "";
2053
+ scopedNickname = "";
2037
2054
  }
2038
2055
  }
2039
2056
  await eventBus.join(
2040
2057
  sessionId,
2041
2058
  normalizeBusAgentType(agentType),
2042
2059
  finalNickname,
2043
- joinOptions,
2060
+ { ...joinOptions, scopedNickname },
2044
2061
  );
2045
2062
  if (finalNickname) {
2046
- eventBus.rename(subscriberId, finalNickname, "ufoo-agent");
2063
+ eventBus.rename(subscriberId, finalNickname, "ufoo-agent", { scopedNickname });
2047
2064
  }
2048
2065
  eventBus.saveBusData();
2049
2066
  const resolvedNickname = resolveSubscriberNickname(projectRoot, subscriberId) || finalNickname || "";
@@ -72,9 +72,46 @@ function applyProjectNicknamePrefix(projectRoot, nickname = "", options = {}) {
72
72
  return `${projectPrefix}-${normalizedNickname}`;
73
73
  }
74
74
 
75
+ function stripProjectNicknamePrefix(projectRoot, nickname = "") {
76
+ const normalizedNickname = normalizeNicknameSegment(nickname, "");
77
+ if (!normalizedNickname) return "";
78
+ const projectPrefix = buildProjectNicknamePrefix(projectRoot);
79
+ const scopedPrefix = `${projectPrefix}-`;
80
+ if (!normalizedNickname.startsWith(scopedPrefix)) {
81
+ return normalizedNickname;
82
+ }
83
+ const stripped = normalizedNickname.slice(scopedPrefix.length).replace(/^-+/, "");
84
+ return stripped || normalizedNickname;
85
+ }
86
+
87
+ function resolveDisplayNickname(projectRoot, meta = {}, fallback = "") {
88
+ const explicit = asTrimmedString(meta.nickname);
89
+ if (explicit) {
90
+ return meta.scoped_nickname ? explicit : stripProjectNicknamePrefix(projectRoot, explicit);
91
+ }
92
+ const scoped = asTrimmedString(meta.scoped_nickname);
93
+ if (scoped) return stripProjectNicknamePrefix(projectRoot, scoped);
94
+ return asTrimmedString(fallback);
95
+ }
96
+
97
+ function resolveScopedNickname(projectRoot, meta = {}, fallback = "") {
98
+ const scoped = asTrimmedString(meta.scoped_nickname);
99
+ if (scoped) return scoped;
100
+ const explicit = asTrimmedString(meta.nickname);
101
+ if (explicit) {
102
+ return meta.scoped_nickname ? scoped : applyProjectNicknamePrefix(projectRoot, explicit, { force: true });
103
+ }
104
+ const fallbackValue = asTrimmedString(fallback);
105
+ if (!fallbackValue) return "";
106
+ return applyProjectNicknamePrefix(projectRoot, fallbackValue, { force: true });
107
+ }
108
+
75
109
  module.exports = {
76
110
  normalizeNicknameSegment,
77
111
  buildProjectNicknamePrefix,
78
112
  isAutoGeneratedNickname,
79
113
  applyProjectNicknamePrefix,
114
+ stripProjectNicknamePrefix,
115
+ resolveDisplayNickname,
116
+ resolveScopedNickname,
80
117
  };
package/src/daemon/ops.js CHANGED
@@ -7,7 +7,10 @@ const { loadAgentsData, saveAgentsData } = require("../ufoo/agentsStore");
7
7
  const { isAgentPidAlive, getTtyProcessInfo } = require("../bus/utils");
8
8
  const { isITerm2 } = require("../terminal/detect");
9
9
  const { createTerminalAdapterRouter } = require("../terminal/adapterRouter");
10
- const { applyProjectNicknamePrefix } = require("./nicknameScope");
10
+ const {
11
+ applyProjectNicknamePrefix,
12
+ resolveDisplayNickname,
13
+ } = require("./nicknameScope");
11
14
  const {
12
15
  createSession: createHostSession,
13
16
  } = require("../terminal/adapters/hostAdapter");
@@ -125,11 +128,15 @@ function resolveAgentId(projectRoot, agentId) {
125
128
  try {
126
129
  const bus = JSON.parse(fs.readFileSync(busPath, "utf8"));
127
130
  const entries = Object.entries(bus.agents || {});
128
- const match = entries.find(([, meta]) => meta?.nickname === agentId);
131
+ const match = entries.find(([, meta]) =>
132
+ meta?.nickname === agentId || meta?.scoped_nickname === agentId
133
+ );
129
134
  if (match) return match[0];
130
135
  const scopedNickname = applyProjectNicknamePrefix(projectRoot, agentId);
131
136
  if (scopedNickname && scopedNickname !== agentId) {
132
- const scopedMatch = entries.find(([, meta]) => meta?.nickname === scopedNickname);
137
+ const scopedMatch = entries.find(([, meta]) =>
138
+ meta?.nickname === scopedNickname || meta?.scoped_nickname === scopedNickname
139
+ );
133
140
  if (scopedMatch) return scopedMatch[0];
134
141
  }
135
142
  const normalized = normalizeLaunchAgent(agentId);
@@ -848,7 +855,11 @@ async function launchAgent(projectRoot, agent, count = 1, nickname = "", process
848
855
  const launchScope = normalizeLaunchScope(options.launchScope, "inplace");
849
856
  const terminalApp = normalizeTerminalAppPreference(options.terminalApp);
850
857
  const extraEnvObject = options.extraEnv && typeof options.extraEnv === "object" ? options.extraEnv : {};
851
- const extraEnvPrefix = buildShellEnvPrefix(extraEnvObject);
858
+ const scopedNickname = typeof options.scopedNickname === "string" ? options.scopedNickname.trim() : "";
859
+ const launchEnvObject = scopedNickname
860
+ ? { ...extraEnvObject, UFOO_SCOPED_NICKNAME: scopedNickname }
861
+ : extraEnvObject;
862
+ const extraEnvPrefix = buildShellEnvPrefix(launchEnvObject);
852
863
  const extraArgs = Array.isArray(options.extraArgs) ? options.extraArgs : [];
853
864
  const normalizedAgent = normalizeLaunchAgent(agent);
854
865
  if (!normalizedAgent) {
@@ -862,7 +873,7 @@ async function launchAgent(projectRoot, agent, count = 1, nickname = "", process
862
873
  count,
863
874
  nickname,
864
875
  processManager,
865
- extraEnvObject
876
+ launchEnvObject
866
877
  );
867
878
  return { mode: "internal", launchScope, subscriberIds: result.subscriberIds };
868
879
  }
@@ -954,7 +965,7 @@ async function launchAgent(projectRoot, agent, count = 1, nickname = "", process
954
965
  nick,
955
966
  processManager,
956
967
  extraArgs,
957
- extraEnvObject,
968
+ launchEnvObject,
958
969
  hostContext
959
970
  );
960
971
  if (result.subscriberId) subscriberIds.push(result.subscriberId);
@@ -1023,8 +1034,8 @@ function collectRecoverableAgents(projectRoot, target = "") {
1023
1034
  } else {
1024
1035
  targets = entries.filter(([id, meta]) =>
1025
1036
  id === target
1026
- || (meta && meta.nickname === target)
1027
- || (scopedTarget && scopedTarget !== target && meta && meta.nickname === scopedTarget)
1037
+ || (meta && (meta.nickname === target || meta.scoped_nickname === target))
1038
+ || (scopedTarget && scopedTarget !== target && meta && (meta.nickname === scopedTarget || meta.scoped_nickname === scopedTarget))
1028
1039
  );
1029
1040
  }
1030
1041
  }
@@ -1076,7 +1087,8 @@ function getRecoverableAgents(projectRoot, target = "") {
1076
1087
  const { mode, recoverableEntries, skipped } = collectRecoverableAgents(projectRoot, target);
1077
1088
  const recoverable = recoverableEntries.map((item) => ({
1078
1089
  id: item.id,
1079
- nickname: item.meta.nickname || "",
1090
+ nickname: resolveDisplayNickname(projectRoot, item.meta),
1091
+ display_nickname: resolveDisplayNickname(projectRoot, item.meta),
1080
1092
  agent: item.agent,
1081
1093
  sessionId: item.meta.provider_session_id || "",
1082
1094
  launchMode: item.meta.launch_mode || "",
@@ -1094,7 +1106,7 @@ async function resumeAgents(projectRoot, target = "", processManager = null) {
1094
1106
 
1095
1107
  const resumed = [];
1096
1108
  for (const item of recoverableEntries) {
1097
- const nickname = item.meta.nickname || "";
1109
+ const nickname = resolveDisplayNickname(projectRoot, item.meta);
1098
1110
  const sessionId = item.meta.provider_session_id;
1099
1111
  const reused = await tryReuseTerminal(projectRoot, item.id, item.meta, item.agent, sessionId);
1100
1112
  if (!reused) {
@@ -212,15 +212,16 @@ async function handlePromptRequest(options = {}) {
212
212
 
213
213
  const privateReports = listControllerInboxEntries(projectRoot, "ufoo-agent", { num: 100 });
214
214
  const useGlobalProjectRouter = isGlobalController;
215
- const promptRunner = runPromptWithAssistant;
216
215
  const ufooAgentOptions = useGlobalProjectRouter ? { routingMode: "global-router" } : { controllerMode };
217
216
  let nextRequestMeta = requestMeta;
218
- if (!Object.prototype.hasOwnProperty.call(nextRequestMeta, "agent_execution_path") && controllerMode !== CONTROLLER_MODES.LEGACY) {
217
+ const hasExplicitRequestMeta = Object.keys(nextRequestMeta).length > 0;
218
+ if (hasExplicitRequestMeta && !Object.prototype.hasOwnProperty.call(nextRequestMeta, "agent_execution_path") && controllerMode !== CONTROLLER_MODES.LEGACY) {
219
219
  nextRequestMeta = {
220
220
  ...nextRequestMeta,
221
221
  agent_execution_path: controllerMode,
222
222
  };
223
223
  }
224
+ let forceMainRouterFallback = false;
224
225
 
225
226
  const logGateRouterEvent = (event, details = {}) => {
226
227
  controllerObserver.emit(event, details);
@@ -275,6 +276,7 @@ async function handlePromptRequest(options = {}) {
275
276
  attachGateRouterMeta("provider_error", {
276
277
  error: routed && routed.error ? routed.error : "route_agent_failed",
277
278
  });
279
+ forceMainRouterFallback = true;
278
280
  logGateRouterEvent("controller.gate_router_upgraded", {
279
281
  reason: "provider_error",
280
282
  fallback_used: "main_router",
@@ -295,6 +297,7 @@ async function handlePromptRequest(options = {}) {
295
297
  confidence: Number(route.confidence || 0),
296
298
  route_reason: route.reason || "",
297
299
  });
300
+ forceMainRouterFallback = true;
298
301
  logGateRouterEvent("controller.gate_router_upgraded", {
299
302
  reason: upgradeReason,
300
303
  decision: route.decision || "",
@@ -339,6 +342,7 @@ async function handlePromptRequest(options = {}) {
339
342
  route_reason: route.reason || "",
340
343
  error: err && err.message ? err.message : String(err),
341
344
  });
345
+ forceMainRouterFallback = true;
342
346
  logGateRouterEvent("controller.gate_router_upgraded", {
343
347
  reason: "dispatch_failed",
344
348
  target: route.target,
@@ -351,6 +355,11 @@ async function handlePromptRequest(options = {}) {
351
355
  }
352
356
 
353
357
  const promptText = buildPromptWithPrivateReports(req.text || "", privateReports, nextRequestMeta);
358
+ const promptRunner = loopRuntime.enabled
359
+ && !forceMainRouterFallback
360
+ && typeof injectedLoopRunner === "function"
361
+ ? injectedLoopRunner
362
+ : runPromptWithAssistant;
354
363
 
355
364
  try {
356
365
  const handled = await promptRunner({
@@ -9,6 +9,7 @@ const {
9
9
  appendControllerInboxEntry,
10
10
  } = require("../report/store");
11
11
  const { getUfooPaths } = require("../ufoo/paths");
12
+ const { resolveDisplayNickname } = require("./nicknameScope");
12
13
 
13
14
  function resolveAgentDisplayName(projectRoot, agentId) {
14
15
  if (!agentId) return "unknown-agent";
@@ -16,9 +17,7 @@ function resolveAgentDisplayName(projectRoot, agentId) {
16
17
  const busPath = getUfooPaths(projectRoot).agentsFile;
17
18
  const bus = JSON.parse(fs.readFileSync(busPath, "utf8"));
18
19
  const meta = bus && bus.agents ? bus.agents[agentId] : null;
19
- if (meta && typeof meta.nickname === "string" && meta.nickname.trim()) {
20
- return meta.nickname.trim();
21
- }
20
+ if (meta) return resolveDisplayNickname(projectRoot, meta) || agentId;
22
21
  } catch {
23
22
  // ignore
24
23
  }
@@ -5,7 +5,11 @@ const EventBus = require("../bus");
5
5
  const { prepareUcodeBootstrap } = require("../agent/ucodeBootstrap");
6
6
  const { isMetaActive } = require("../bus/utils");
7
7
  const { getUfooPaths } = require("../ufoo/paths");
8
- const { applyProjectNicknamePrefix } = require("./nicknameScope");
8
+ const {
9
+ applyProjectNicknamePrefix,
10
+ resolveDisplayNickname,
11
+ resolveScopedNickname,
12
+ } = require("./nicknameScope");
9
13
  const { loadAgentsData, saveAgentsData } = require("../ufoo/agentsStore");
10
14
  const {
11
15
  loadPromptProfileRegistry,
@@ -207,7 +211,11 @@ function resolveExistingAgent(projectRoot, target = "") {
207
211
  for (const [subscriberId, meta] of Object.entries(agents)) {
208
212
  if (
209
213
  meta
210
- && (meta.nickname === key || (scopedKey && scopedKey !== key && meta.nickname === scopedKey))
214
+ && (
215
+ meta.nickname === key
216
+ || meta.scoped_nickname === key
217
+ || (scopedKey && scopedKey !== key && (meta.nickname === scopedKey || meta.scoped_nickname === scopedKey))
218
+ )
211
219
  && isLiveAgentMeta(meta)
212
220
  ) {
213
221
  return { subscriberId, meta };
@@ -222,7 +230,8 @@ function findOwningGroup(projectRoot, subscriberId = "") {
222
230
  const liveMeta = getAgentRuntimeMeta(projectRoot, targetSubscriber);
223
231
  if (!isLiveAgentMeta(liveMeta)) return null;
224
232
  if (asTrimmedString(liveMeta.role_owner).toLowerCase() === "solo") return null;
225
- const liveNickname = asTrimmedString(liveMeta.nickname);
233
+ const liveNickname = resolveDisplayNickname(projectRoot, liveMeta);
234
+ const liveScopedNickname = resolveScopedNickname(projectRoot, liveMeta);
226
235
  const groupsDir = getUfooPaths(projectRoot).groupsDir;
227
236
  if (!groupsDir) return null;
228
237
  let files = [];
@@ -244,7 +253,10 @@ function findOwningGroup(projectRoot, subscriberId = "") {
244
253
  && (
245
254
  !liveNickname
246
255
  || asTrimmedString(member.nickname) === liveNickname
256
+ || asTrimmedString(member.scoped_nickname) === liveNickname
257
+ || asTrimmedString(member.scoped_nickname) === liveScopedNickname
247
258
  || asTrimmedString(member.runtime_nickname) === liveNickname
259
+ || asTrimmedString(member.runtime_nickname) === liveScopedNickname
248
260
  )
249
261
  );
250
262
  if (found) {
@@ -2,6 +2,7 @@ const fs = require("fs");
2
2
  const path = require("path");
3
3
  const { getUfooPaths } = require("../ufoo/paths");
4
4
  const { isMetaActive } = require("../bus/utils");
5
+ const { resolveDisplayNickname, resolveScopedNickname } = require("./nicknameScope");
5
6
  const { readReportSummary, countControllerInboxEntries } = require("../report/store");
6
7
  const { readRecentLoopSummary } = require("../agent/loopObservability");
7
8
 
@@ -146,7 +147,8 @@ function buildStatus(projectRoot, options = {}) {
146
147
  : [];
147
148
  const active = activeEntries.map(({ id }) => id);
148
149
  const activeMeta = activeEntries.map(({ id, meta }) => {
149
- const nickname = meta?.nickname || "";
150
+ const nickname = resolveDisplayNickname(projectRoot, meta);
151
+ const scoped_nickname = resolveScopedNickname(projectRoot, meta);
150
152
  const display = nickname ? nickname : id;
151
153
  const launch_mode = meta?.launch_mode || "unknown";
152
154
  const tmux_pane = meta?.tmux_pane || "";
@@ -156,6 +158,8 @@ function buildStatus(projectRoot, options = {}) {
156
158
  return {
157
159
  id,
158
160
  nickname,
161
+ scoped_nickname,
162
+ display_nickname: nickname,
159
163
  display,
160
164
  launch_mode,
161
165
  tmux_pane,
@@ -5,7 +5,7 @@ const crypto = require("crypto");
5
5
  const SHARED_UFOO_PROTOCOL = [
6
6
  "ufoo protocol:",
7
7
  "- At session start, sync shared context with `ufoo ctx decisions -l` and `ufoo ctx decisions -n 1`.",
8
- "- Record a decision ONLY for important, plan-level knowledge: architectural choices, multi-option trade-off analysis, cross-agent coordination decisions, or plans that affect other agents. Do NOT record routine findings, simple bug fixes, or trivial observations. Use `ufoo ctx decisions new \"Title\"` BEFORE acting.",
8
+ "- Default to no new decision. Record one ONLY for important, plan-level knowledge: architectural choices, multi-option trade-off analysis, cross-agent coordination decisions, or plans that affect other agents. Do NOT record routine findings, simple bug fixes, trivial observations, or generic plan/evaluation/recommendation requests. Durable project facts belong in shared memory, not decisions. Use `ufoo ctx decisions new \"Title\"` BEFORE acting only when that bar is met.",
9
9
  "- Use `ufoo bus send <target-nickname> \"<message>\"` for agent-to-agent handoffs.",
10
10
  "- If you receive pending bus work, execute it immediately, reply to the sender, then `ufoo bus ack \"$UFOO_SUBSCRIBER_ID\"`.",
11
11
  "- Use `ufoo report` for controller/runtime status updates, not as a substitute for direct handoffs.",