u-foo 2.3.18 → 2.3.20
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/README.md +8 -2
- package/README.zh-CN.md +8 -2
- package/package.json +1 -1
- package/src/chat/agentViewController.js +98 -11
- package/src/chat/commandExecutor.js +3 -3
- package/src/chat/commands.js +3 -3
- package/src/chat/daemonMessageRouter.js +18 -2
- package/src/chat/index.js +23 -0
- package/src/code/agent.js +118 -4
- package/src/code/cli.js +47 -0
- package/src/code/prompts/index.js +9 -0
- package/src/code/skills/index.js +74 -0
- package/src/code/skills/injection.js +120 -0
- package/src/code/skills/loader.js +218 -0
- package/src/code/skills/render.js +46 -0
- package/src/daemon/index.js +196 -12
- package/src/shared/eventContract.js +1 -0
package/src/daemon/index.js
CHANGED
|
@@ -739,6 +739,10 @@ function startBusBridge(projectRoot, provider, onEvent, onStatus, shouldDrain) {
|
|
|
739
739
|
subscriber: null,
|
|
740
740
|
queueFile: null,
|
|
741
741
|
pending: new Set(),
|
|
742
|
+
watchedAgents: new Set(),
|
|
743
|
+
lastEventSeq: 0,
|
|
744
|
+
emittedEventKeys: [],
|
|
745
|
+
emittedEventKeySet: new Set(),
|
|
742
746
|
};
|
|
743
747
|
const eventBus = new EventBus(projectRoot);
|
|
744
748
|
let joinInProgress = false;
|
|
@@ -758,6 +762,169 @@ function startBusBridge(projectRoot, provider, onEvent, onStatus, shouldDrain) {
|
|
|
758
762
|
return agentId;
|
|
759
763
|
}
|
|
760
764
|
|
|
765
|
+
function getEventDedupeKey(evt) {
|
|
766
|
+
if (!evt || typeof evt !== "object") return "";
|
|
767
|
+
const seq = Number(evt.seq);
|
|
768
|
+
if (Number.isFinite(seq) && seq > 0) return `seq:${seq}`;
|
|
769
|
+
return [
|
|
770
|
+
"event",
|
|
771
|
+
evt.timestamp || evt.ts || "",
|
|
772
|
+
evt.event || "",
|
|
773
|
+
evt.publisher || "",
|
|
774
|
+
evt.target || "",
|
|
775
|
+
JSON.stringify(evt.data || {}),
|
|
776
|
+
].join(":");
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
function rememberEmittedEvent(evt) {
|
|
780
|
+
const key = getEventDedupeKey(evt);
|
|
781
|
+
if (!key) return false;
|
|
782
|
+
if (state.emittedEventKeySet.has(key)) return true;
|
|
783
|
+
state.emittedEventKeySet.add(key);
|
|
784
|
+
state.emittedEventKeys.push(key);
|
|
785
|
+
if (state.emittedEventKeys.length > 500) {
|
|
786
|
+
const removed = state.emittedEventKeys.splice(0, state.emittedEventKeys.length - 500);
|
|
787
|
+
for (const item of removed) state.emittedEventKeySet.delete(item);
|
|
788
|
+
}
|
|
789
|
+
return false;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
function hasPositiveSeq(seq) {
|
|
793
|
+
const value = Number(seq);
|
|
794
|
+
return Number.isFinite(value) && value > 0;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
function toBridgeEvent(evt) {
|
|
798
|
+
const data = evt.data && typeof evt.data === "object" ? evt.data : {};
|
|
799
|
+
return {
|
|
800
|
+
seq: evt.seq,
|
|
801
|
+
event: evt.event,
|
|
802
|
+
publisher: evt.publisher,
|
|
803
|
+
target: evt.target,
|
|
804
|
+
data,
|
|
805
|
+
message: data.message || "",
|
|
806
|
+
state: data.state || "",
|
|
807
|
+
previous: data.previous || "",
|
|
808
|
+
subscriber: data.subscriber || "",
|
|
809
|
+
source: data.source || "",
|
|
810
|
+
injection_mode: data.injection_mode || "",
|
|
811
|
+
ts: evt.timestamp || evt.ts,
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
function emitBusEvent(evt) {
|
|
816
|
+
if (!evt || !onEvent) return;
|
|
817
|
+
if (rememberEmittedEvent(evt)) return;
|
|
818
|
+
onEvent(toBridgeEvent(evt));
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
function readAgentsData() {
|
|
822
|
+
try {
|
|
823
|
+
const busPath = getUfooPaths(projectRoot).agentsFile;
|
|
824
|
+
return JSON.parse(fs.readFileSync(busPath, "utf8"));
|
|
825
|
+
} catch {
|
|
826
|
+
return {};
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
function buildWatchedAliases() {
|
|
831
|
+
const aliases = new Set();
|
|
832
|
+
const bus = readAgentsData();
|
|
833
|
+
for (const agentId of state.watchedAgents) {
|
|
834
|
+
aliases.add(agentId);
|
|
835
|
+
const meta = bus.agents && bus.agents[agentId];
|
|
836
|
+
if (!meta) continue;
|
|
837
|
+
if (meta.nickname) aliases.add(meta.nickname);
|
|
838
|
+
if (meta.scoped_nickname) aliases.add(meta.scoped_nickname);
|
|
839
|
+
if (meta.display_nickname) aliases.add(meta.display_nickname);
|
|
840
|
+
}
|
|
841
|
+
return aliases;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
function isWatchedEvent(evt, aliases = buildWatchedAliases()) {
|
|
845
|
+
if (!evt || (evt.event !== "message" && evt.event !== "activity_state_changed")) return false;
|
|
846
|
+
const publisher = String(evt.publisher || "");
|
|
847
|
+
const target = String(evt.target || "");
|
|
848
|
+
const subscriber = evt.data && evt.data.subscriber ? String(evt.data.subscriber) : "";
|
|
849
|
+
return aliases.has(publisher) || aliases.has(target) || aliases.has(subscriber);
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
function getEventFiles() {
|
|
853
|
+
try {
|
|
854
|
+
const dir = getUfooPaths(projectRoot).busEventsDir;
|
|
855
|
+
return fs.readdirSync(dir)
|
|
856
|
+
.filter((name) => name.endsWith(".jsonl"))
|
|
857
|
+
.sort()
|
|
858
|
+
.map((name) => path.join(dir, name));
|
|
859
|
+
} catch {
|
|
860
|
+
return [];
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
function readCurrentSeq() {
|
|
865
|
+
try {
|
|
866
|
+
const raw = fs.readFileSync(path.join(getUfooPaths(projectRoot).busDir, "seq.counter"), "utf8").trim();
|
|
867
|
+
const seq = Number(raw);
|
|
868
|
+
return Number.isFinite(seq) ? seq : 0;
|
|
869
|
+
} catch {
|
|
870
|
+
return 0;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
function readEventFile(file) {
|
|
875
|
+
try {
|
|
876
|
+
return fs.readFileSync(file, "utf8")
|
|
877
|
+
.split(/\r?\n/)
|
|
878
|
+
.filter(Boolean)
|
|
879
|
+
.map((line) => {
|
|
880
|
+
try {
|
|
881
|
+
return JSON.parse(line);
|
|
882
|
+
} catch {
|
|
883
|
+
return null;
|
|
884
|
+
}
|
|
885
|
+
})
|
|
886
|
+
.filter(Boolean);
|
|
887
|
+
} catch {
|
|
888
|
+
return [];
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
function emitRecentWatchedEvents(agentId, limit = 80) {
|
|
893
|
+
if (!agentId) return;
|
|
894
|
+
const previous = new Set(state.watchedAgents);
|
|
895
|
+
state.watchedAgents.add(agentId);
|
|
896
|
+
const aliases = buildWatchedAliases();
|
|
897
|
+
state.watchedAgents = previous;
|
|
898
|
+
const matches = [];
|
|
899
|
+
const files = getEventFiles().slice(-3);
|
|
900
|
+
for (const file of files) {
|
|
901
|
+
for (const evt of readEventFile(file)) {
|
|
902
|
+
if (isWatchedEvent(evt, aliases)) matches.push(evt);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
for (const evt of matches.slice(-limit)) emitBusEvent(evt);
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
function pollWatchedEvents() {
|
|
909
|
+
if (state.watchedAgents.size === 0) {
|
|
910
|
+
state.lastEventSeq = readCurrentSeq();
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
const aliases = buildWatchedAliases();
|
|
914
|
+
let maxSeq = state.lastEventSeq;
|
|
915
|
+
for (const file of getEventFiles().slice(-2)) {
|
|
916
|
+
for (const evt of readEventFile(file)) {
|
|
917
|
+
const seq = Number(evt.seq);
|
|
918
|
+
if (hasPositiveSeq(seq)) {
|
|
919
|
+
if (seq <= state.lastEventSeq) continue;
|
|
920
|
+
if (seq > maxSeq) maxSeq = seq;
|
|
921
|
+
}
|
|
922
|
+
if (isWatchedEvent(evt, aliases)) emitBusEvent(evt);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
state.lastEventSeq = Math.max(state.lastEventSeq, maxSeq);
|
|
926
|
+
}
|
|
927
|
+
|
|
761
928
|
function ensureSubscriber() {
|
|
762
929
|
if (state.subscriber || joinInProgress) return;
|
|
763
930
|
const debugFile = path.join(getUfooPaths(projectRoot).runDir, "bus-join-debug.txt");
|
|
@@ -785,9 +952,7 @@ function startBusBridge(projectRoot, provider, onEvent, onStatus, shouldDrain) {
|
|
|
785
952
|
})();
|
|
786
953
|
}
|
|
787
954
|
|
|
788
|
-
function
|
|
789
|
-
ensureSubscriber();
|
|
790
|
-
if (typeof shouldDrain === "function" && !shouldDrain()) return;
|
|
955
|
+
function pollQueue() {
|
|
791
956
|
if (!state.queueFile) return;
|
|
792
957
|
if (!fs.existsSync(state.queueFile)) return;
|
|
793
958
|
let content = "";
|
|
@@ -828,15 +993,7 @@ function startBusBridge(projectRoot, provider, onEvent, onStatus, shouldDrain) {
|
|
|
828
993
|
continue;
|
|
829
994
|
}
|
|
830
995
|
if (!evt) continue;
|
|
831
|
-
|
|
832
|
-
onEvent({
|
|
833
|
-
event: evt.event,
|
|
834
|
-
publisher: evt.publisher,
|
|
835
|
-
target: evt.target,
|
|
836
|
-
message: evt.data?.message || "",
|
|
837
|
-
ts: evt.timestamp || evt.ts,
|
|
838
|
-
});
|
|
839
|
-
}
|
|
996
|
+
emitBusEvent(evt);
|
|
840
997
|
if (evt.publisher && state.pending.has(evt.publisher)) {
|
|
841
998
|
state.pending.delete(evt.publisher);
|
|
842
999
|
if (onStatus) {
|
|
@@ -847,6 +1004,13 @@ function startBusBridge(projectRoot, provider, onEvent, onStatus, shouldDrain) {
|
|
|
847
1004
|
}
|
|
848
1005
|
}
|
|
849
1006
|
|
|
1007
|
+
function poll() {
|
|
1008
|
+
ensureSubscriber();
|
|
1009
|
+
if (typeof shouldDrain === "function" && !shouldDrain()) return;
|
|
1010
|
+
pollQueue();
|
|
1011
|
+
pollWatchedEvents();
|
|
1012
|
+
}
|
|
1013
|
+
|
|
850
1014
|
const interval = setInterval(poll, 1000);
|
|
851
1015
|
return {
|
|
852
1016
|
markPending(target) {
|
|
@@ -865,6 +1029,19 @@ function startBusBridge(projectRoot, provider, onEvent, onStatus, shouldDrain) {
|
|
|
865
1029
|
} catch {}
|
|
866
1030
|
return state.subscriber;
|
|
867
1031
|
},
|
|
1032
|
+
watchAgent(agentId, enabled = true) {
|
|
1033
|
+
if (!agentId) return;
|
|
1034
|
+
if (enabled) {
|
|
1035
|
+
emitRecentWatchedEvents(agentId);
|
|
1036
|
+
state.watchedAgents.add(agentId);
|
|
1037
|
+
state.lastEventSeq = Math.max(state.lastEventSeq, readCurrentSeq());
|
|
1038
|
+
} else {
|
|
1039
|
+
state.watchedAgents.delete(agentId);
|
|
1040
|
+
if (state.watchedAgents.size === 0) {
|
|
1041
|
+
state.lastEventSeq = readCurrentSeq();
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
},
|
|
868
1045
|
stop() {
|
|
869
1046
|
clearInterval(interval);
|
|
870
1047
|
},
|
|
@@ -1187,6 +1364,13 @@ function startDaemon({ projectRoot, provider, model, resumeMode = "auto" }) {
|
|
|
1187
1364
|
}
|
|
1188
1365
|
return;
|
|
1189
1366
|
}
|
|
1367
|
+
if (req.type === IPC_REQUEST_TYPES.BUS_WATCH) {
|
|
1368
|
+
const agentId = String(req.agent_id || "").trim();
|
|
1369
|
+
if (agentId) {
|
|
1370
|
+
busBridge.watchAgent(agentId, req.enabled !== false);
|
|
1371
|
+
}
|
|
1372
|
+
return;
|
|
1373
|
+
}
|
|
1190
1374
|
if (req.type === IPC_REQUEST_TYPES.CRON) {
|
|
1191
1375
|
if (!daemonCronController) {
|
|
1192
1376
|
socket.write(
|