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.
- package/SKILLS/ufoo/SKILL.md +56 -12
- package/SKILLS/uinit/SKILL.md +3 -2
- package/modules/AGENTS.template.md +2 -1
- package/modules/bus/README.md +1 -1
- package/modules/context/SKILLS/uctx/SKILL.md +6 -4
- package/package.json +1 -1
- package/src/agent/codexThreadProvider.js +2 -2
- package/src/agent/controllerToolExecutor.js +24 -1
- package/src/agent/credentials/claude.js +85 -16
- package/src/agent/credentials/codex.js +251 -23
- package/src/agent/defaultBootstrap.js +3 -1
- package/src/agent/directAuthStatus.js +264 -0
- package/src/agent/internalRunner.js +18 -12
- package/src/agent/loopObservability.js +10 -0
- package/src/agent/loopRuntime.js +19 -0
- package/src/agent/ufooAgent.js +43 -13
- package/src/agent/upstreamTransport.js +23 -8
- package/src/bus/index.js +13 -5
- package/src/bus/message.js +157 -9
- package/src/bus/nickname.js +14 -3
- package/src/bus/subscriber.js +30 -10
- package/src/chat/agentDirectory.js +2 -2
- package/src/chat/commandExecutor.js +190 -8
- package/src/chat/commands.js +23 -4
- package/src/chat/completionController.js +30 -7
- package/src/chat/daemonMessageRouter.js +2 -1
- package/src/chat/index.js +9 -8
- package/src/cli/groupCoreCommands.js +5 -0
- package/src/cli.js +309 -0
- package/src/code/UCODE_PROMPT.md +3 -2
- package/src/code/nativeRunner.js +2 -1
- package/src/code/prompts/ufoo.js +3 -2
- package/src/config.js +16 -3
- package/src/context/doctor.js +1 -1
- package/src/daemon/groupOrchestrator.js +13 -9
- package/src/daemon/index.js +35 -18
- package/src/daemon/nicknameScope.js +37 -0
- package/src/daemon/ops.js +22 -10
- package/src/daemon/promptRequest.js +11 -2
- package/src/daemon/reporting.js +2 -3
- package/src/daemon/soloBootstrap.js +15 -3
- package/src/daemon/status.js +5 -1
- package/src/group/bootstrap.js +1 -1
- package/src/group/promptProfiles.js +106 -22
- package/src/group/templates.js +1 -0
- package/src/init/index.js +4 -0
- package/src/memory/historySearch.js +308 -0
- package/src/memory/index.js +653 -8
- package/src/providerapi/redactor.js +4 -1
- package/src/status/index.js +26 -2
- package/src/tools/handlers/memory.js +168 -0
- package/src/tools/index.js +12 -0
- package/src/tools/registry.js +12 -0
- package/src/tools/schemaFixtures.js +213 -0
- package/src/tools/tier1/editMemory.js +14 -0
- package/src/tools/tier1/forget.js +14 -0
- package/src/tools/tier1/recall.js +14 -0
- package/src/tools/tier1/remember.js +14 -0
- package/src/tools/tier1/searchHistory.js +14 -0
- package/src/tools/tier1/searchMemory.js +14 -0
- package/templates/groups/build-lane.json +44 -6
- package/templates/groups/build-ultra.json +6 -5
- package/templates/groups/design-system.json +84 -0
- package/templates/groups/product-discovery.json +9 -4
- package/templates/groups/ui-plan-review.json +84 -0
- package/templates/groups/ui-polish.json +6 -2
- package/templates/groups/verify-ship.json +9 -4
package/src/daemon/index.js
CHANGED
|
@@ -31,7 +31,11 @@ const {
|
|
|
31
31
|
buildSoloBootstrapFingerprint,
|
|
32
32
|
rollbackLaunchAfterRoleAssignmentFailure,
|
|
33
33
|
} = require("./soloBootstrap");
|
|
34
|
-
const {
|
|
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
|
|
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
|
-
|
|
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]) =>
|
|
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]
|
|
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 =
|
|
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
|
-
|
|
631
|
+
const scopedNickname = applyProjectNicknamePrefix(projectRoot, requestedNickname, {
|
|
619
632
|
agentType: targetMeta.agent_type || "",
|
|
620
633
|
});
|
|
621
|
-
|
|
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 =
|
|
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 {
|
|
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]) =>
|
|
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]) =>
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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({
|
package/src/daemon/reporting.js
CHANGED
|
@@ -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
|
|
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 {
|
|
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
|
-
&& (
|
|
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 =
|
|
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) {
|
package/src/daemon/status.js
CHANGED
|
@@ -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
|
|
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,
|
package/src/group/bootstrap.js
CHANGED
|
@@ -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
|
-
"-
|
|
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.",
|