u-foo 1.8.4 → 1.8.8

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 (39) hide show
  1. package/package.json +1 -1
  2. package/src/agent/activityDetector.js +33 -0
  3. package/src/agent/claudeSessionFiles.js +127 -0
  4. package/src/agent/launcher.js +13 -2
  5. package/src/bus/index.js +12 -6
  6. package/src/bus/message.js +16 -0
  7. package/src/bus/store.js +72 -25
  8. package/src/bus/subscriber.js +16 -3
  9. package/src/chat/commandExecutor.js +0 -1
  10. package/src/chat/commands.js +10 -2
  11. package/src/chat/daemonCoordinator.js +1 -0
  12. package/src/chat/daemonMessageRouter.js +27 -16
  13. package/src/chat/daemonReconnect.js +6 -3
  14. package/src/chat/index.js +1 -0
  15. package/src/chat/inputMath.js +175 -38
  16. package/src/chat/inputSubmitHandler.js +10 -5
  17. package/src/chat/settingsController.js +3 -1
  18. package/src/chat/text.js +6 -0
  19. package/src/code/agent.js +27 -6
  20. package/src/code/nativeRunner.js +8 -4
  21. package/src/code/prompts/actions.js +21 -0
  22. package/src/code/prompts/efficiency.js +18 -0
  23. package/src/code/prompts/environment.js +50 -0
  24. package/src/code/prompts/identity.js +20 -0
  25. package/src/code/prompts/index.js +103 -0
  26. package/src/code/prompts/safety.js +11 -0
  27. package/src/code/prompts/sections.js +60 -0
  28. package/src/code/prompts/system.js +16 -0
  29. package/src/code/prompts/tasks.js +17 -0
  30. package/src/code/prompts/toolDescriptions/bash.js +21 -0
  31. package/src/code/prompts/toolDescriptions/edit.js +16 -0
  32. package/src/code/prompts/toolDescriptions/read.js +17 -0
  33. package/src/code/prompts/toolDescriptions/write.js +16 -0
  34. package/src/code/prompts/ufoo.js +21 -0
  35. package/src/daemon/groupOrchestrator.js +97 -7
  36. package/src/daemon/index.js +53 -14
  37. package/src/daemon/nicknameScope.js +80 -0
  38. package/src/daemon/ops.js +19 -6
  39. package/src/daemon/soloBootstrap.js +15 -2
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+
6
+ function asTrimmedString(value) {
7
+ if (typeof value !== "string") return "";
8
+ return value.trim();
9
+ }
10
+
11
+ function normalizeNicknameSegment(value = "", fallback = "agent") {
12
+ const normalized = asTrimmedString(value)
13
+ .toLowerCase()
14
+ .replace(/[^a-z0-9_-]+/g, "-")
15
+ .replace(/-+/g, "-")
16
+ .replace(/^-+|-+$/g, "");
17
+ return normalized || fallback;
18
+ }
19
+
20
+ function normalizeAutoNicknamePrefix(agentType = "") {
21
+ const normalized = asTrimmedString(agentType).toLowerCase();
22
+ if (normalized === "claude" || normalized === "claude-code") return "claude";
23
+ if (normalized === "codex") return "codex";
24
+ if (normalized === "ufoo" || normalized === "ucode" || normalized === "ufoo-code") return "ucode";
25
+ return "";
26
+ }
27
+
28
+ function buildProjectNicknamePrefix(projectRoot) {
29
+ const resolvedRoot = asTrimmedString(projectRoot) || process.cwd();
30
+ let baseName = "";
31
+ try {
32
+ const realRoot = fs.realpathSync.native
33
+ ? fs.realpathSync.native(resolvedRoot)
34
+ : fs.realpathSync(resolvedRoot);
35
+ baseName = path.basename(realRoot);
36
+ } catch {
37
+ baseName = path.basename(path.resolve(resolvedRoot));
38
+ }
39
+ return normalizeNicknameSegment(baseName, "project");
40
+ }
41
+
42
+ function isAutoGeneratedNickname(nickname = "", agentType = "") {
43
+ const normalized = normalizeNicknameSegment(nickname, "");
44
+ const match = normalized.match(/^([a-z0-9]+)-([a-z0-9_-]+)$/);
45
+ if (!match) return false;
46
+
47
+ const nicknamePrefix = match[1];
48
+ const suffix = match[2];
49
+ const knownPrefixes = new Set(["claude", "codex", "ucode"]);
50
+ const agentPrefix = normalizeAutoNicknamePrefix(agentType);
51
+ if (agentPrefix) knownPrefixes.add(agentPrefix);
52
+ if (!knownPrefixes.has(nicknamePrefix)) return false;
53
+ return /\d/.test(suffix);
54
+ }
55
+
56
+ function applyProjectNicknamePrefix(projectRoot, nickname = "", options = {}) {
57
+ const rawNickname = asTrimmedString(nickname);
58
+ if (!rawNickname) return "";
59
+
60
+ const projectPrefix = buildProjectNicknamePrefix(projectRoot);
61
+ const normalizedNickname = normalizeNicknameSegment(rawNickname, "");
62
+ if (!normalizedNickname) return "";
63
+
64
+ if (normalizedNickname === projectPrefix || normalizedNickname.startsWith(`${projectPrefix}-`)) {
65
+ return normalizedNickname;
66
+ }
67
+
68
+ if (options.force !== true && isAutoGeneratedNickname(normalizedNickname, options.agentType || "")) {
69
+ return normalizedNickname;
70
+ }
71
+
72
+ return `${projectPrefix}-${normalizedNickname}`;
73
+ }
74
+
75
+ module.exports = {
76
+ normalizeNicknameSegment,
77
+ buildProjectNicknamePrefix,
78
+ isAutoGeneratedNickname,
79
+ applyProjectNicknamePrefix,
80
+ };
package/src/daemon/ops.js CHANGED
@@ -7,6 +7,7 @@ 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
11
  const {
11
12
  createSession: createHostSession,
12
13
  } = require("../terminal/adapters/hostAdapter");
@@ -126,6 +127,11 @@ function resolveAgentId(projectRoot, agentId) {
126
127
  const entries = Object.entries(bus.agents || {});
127
128
  const match = entries.find(([, meta]) => meta?.nickname === agentId);
128
129
  if (match) return match[0];
130
+ const scopedNickname = applyProjectNicknamePrefix(projectRoot, agentId);
131
+ if (scopedNickname && scopedNickname !== agentId) {
132
+ const scopedMatch = entries.find(([, meta]) => meta?.nickname === scopedNickname);
133
+ if (scopedMatch) return scopedMatch[0];
134
+ }
129
135
  const normalized = normalizeLaunchAgent(agentId);
130
136
  const targetType = toBusAgentType(normalized) || agentId;
131
137
  const candidates = entries
@@ -147,6 +153,7 @@ function markAgentInactive(projectRoot, agentId) {
147
153
  data.agents[agentId] = {
148
154
  ...meta,
149
155
  status: "inactive",
156
+ activity_state: "",
150
157
  last_seen: new Date().toISOString(),
151
158
  };
152
159
  saveAgentsData(filePath, data);
@@ -842,6 +849,7 @@ async function launchAgent(projectRoot, agent, count = 1, nickname = "", process
842
849
  const terminalApp = normalizeTerminalAppPreference(options.terminalApp);
843
850
  const extraEnvObject = options.extraEnv && typeof options.extraEnv === "object" ? options.extraEnv : {};
844
851
  const extraEnvPrefix = buildShellEnvPrefix(extraEnvObject);
852
+ const extraArgs = Array.isArray(options.extraArgs) ? options.extraArgs : [];
845
853
  const normalizedAgent = normalizeLaunchAgent(agent);
846
854
  if (!normalizedAgent) {
847
855
  throw new Error(`unsupported agent type: ${agent}`);
@@ -894,7 +902,7 @@ async function launchAgent(projectRoot, agent, count = 1, nickname = "", process
894
902
  const nick = count > 1 ? `${nickname || defaultNick}-${i + 1}` : (nickname || "");
895
903
  if (useSeparateWindow) {
896
904
  // eslint-disable-next-line no-await-in-loop
897
- await spawnTmuxWindow(projectRoot, normalizedAgent, nick, [], extraEnvPrefix);
905
+ await spawnTmuxWindow(projectRoot, normalizedAgent, nick, extraArgs, extraEnvPrefix);
898
906
  } else if (useGroupRightColumnLayout && paneTarget) {
899
907
  const basePane = String(tmuxLayoutContext.basePane || paneTarget).trim() || paneTarget;
900
908
  tmuxLayoutContext.basePane = basePane;
@@ -904,14 +912,14 @@ async function launchAgent(projectRoot, agent, count = 1, nickname = "", process
904
912
  let splitResult;
905
913
  try {
906
914
  // eslint-disable-next-line no-await-in-loop
907
- splitResult = await spawnTmuxPane(projectRoot, normalizedAgent, nick, [], extraEnvPrefix, splitTarget, {
915
+ splitResult = await spawnTmuxPane(projectRoot, normalizedAgent, nick, extraArgs, extraEnvPrefix, splitTarget, {
908
916
  orientation: splitOrientation,
909
917
  capturePaneId: !rightColumnPane,
910
918
  });
911
919
  } catch {
912
920
  // Fallback to new window when current pane target cannot be resolved.
913
921
  // eslint-disable-next-line no-await-in-loop
914
- await spawnTmuxWindow(projectRoot, normalizedAgent, nick, [], extraEnvPrefix);
922
+ await spawnTmuxWindow(projectRoot, normalizedAgent, nick, extraArgs, extraEnvPrefix);
915
923
  continue;
916
924
  }
917
925
  if (!rightColumnPane && splitResult && splitResult.paneId) {
@@ -923,11 +931,11 @@ async function launchAgent(projectRoot, agent, count = 1, nickname = "", process
923
931
  } else {
924
932
  try {
925
933
  // eslint-disable-next-line no-await-in-loop
926
- await spawnTmuxPane(projectRoot, normalizedAgent, nick, [], extraEnvPrefix, paneTarget);
934
+ await spawnTmuxPane(projectRoot, normalizedAgent, nick, extraArgs, extraEnvPrefix, paneTarget);
927
935
  } catch {
928
936
  // Fallback to new window when current pane target cannot be resolved.
929
937
  // eslint-disable-next-line no-await-in-loop
930
- await spawnTmuxWindow(projectRoot, normalizedAgent, nick, [], extraEnvPrefix);
938
+ await spawnTmuxWindow(projectRoot, normalizedAgent, nick, extraArgs, extraEnvPrefix);
931
939
  }
932
940
  }
933
941
  }
@@ -1009,10 +1017,15 @@ function collectRecoverableAgents(projectRoot, target = "") {
1009
1017
 
1010
1018
  let targets = entries;
1011
1019
  if (target) {
1020
+ const scopedTarget = applyProjectNicknamePrefix(projectRoot, target);
1012
1021
  if (target.includes(":")) {
1013
1022
  targets = entries.filter(([id]) => id === target);
1014
1023
  } else {
1015
- targets = entries.filter(([id, meta]) => id === target || (meta && meta.nickname === target));
1024
+ targets = entries.filter(([id, meta]) =>
1025
+ id === target
1026
+ || (meta && meta.nickname === target)
1027
+ || (scopedTarget && scopedTarget !== target && meta && meta.nickname === scopedTarget)
1028
+ );
1016
1029
  }
1017
1030
  }
1018
1031
 
@@ -5,6 +5,7 @@ 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
9
  const { loadAgentsData, saveAgentsData } = require("../ufoo/agentsStore");
9
10
  const {
10
11
  loadPromptProfileRegistry,
@@ -194,13 +195,21 @@ function isLiveAgentMeta(meta) {
194
195
  function resolveExistingAgent(projectRoot, target = "") {
195
196
  const key = String(target || "").trim();
196
197
  if (!key) return null;
198
+ const scopedKey = applyProjectNicknamePrefix(projectRoot, key);
197
199
  const bus = loadBusMeta(projectRoot);
198
200
  const agents = bus && bus.agents ? bus.agents : {};
199
201
  if (isLiveAgentMeta(agents[key])) {
200
202
  return { subscriberId: key, meta: agents[key] };
201
203
  }
204
+ if (scopedKey && scopedKey !== key && isLiveAgentMeta(agents[scopedKey])) {
205
+ return { subscriberId: scopedKey, meta: agents[scopedKey] };
206
+ }
202
207
  for (const [subscriberId, meta] of Object.entries(agents)) {
203
- if (meta && meta.nickname === key && isLiveAgentMeta(meta)) {
208
+ if (
209
+ meta
210
+ && (meta.nickname === key || (scopedKey && scopedKey !== key && meta.nickname === scopedKey))
211
+ && isLiveAgentMeta(meta)
212
+ ) {
204
213
  return { subscriberId, meta };
205
214
  }
206
215
  }
@@ -232,7 +241,11 @@ function findOwningGroup(projectRoot, subscriberId = "") {
232
241
  && asTrimmedString(member.subscriber_id) === targetSubscriber
233
242
  && (member.status === "active" || member.status === "reused")
234
243
  && asTrimmedString(member.nickname)
235
- && (!liveNickname || asTrimmedString(member.nickname) === liveNickname)
244
+ && (
245
+ !liveNickname
246
+ || asTrimmedString(member.nickname) === liveNickname
247
+ || asTrimmedString(member.runtime_nickname) === liveNickname
248
+ )
236
249
  );
237
250
  if (found) {
238
251
  return {