@yeaft/webchat-agent 0.1.554 → 0.1.556
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/connection/message-router.js +6 -1
- package/package.json +1 -1
- package/unify/vp/vp-bridge.js +15 -3
- package/unify/web-bridge.js +100 -0
|
@@ -36,7 +36,7 @@ import { sendToServer, flushMessageBuffer } from './buffer.js';
|
|
|
36
36
|
import { handleRestartAgent, handleUpgradeAgent } from './upgrade.js';
|
|
37
37
|
import { loadMcpServers, updateMcpConfig } from '../mcp.js';
|
|
38
38
|
import { getLlmConfig, updateLlmConfig, getUnifySettings, updateUnifySettings } from '../unify/config-api.js';
|
|
39
|
-
import { handleUnifyChat, handleUnifyModeSwitch, handleUnifyModelSwitch, resetUnifySession, handleUnifyLoadHistory, handleUnifyMergeThread, handleUnifyForkThread, handleUnifyAbortThread, handleUnifyAbortAll, handleUnifyVpSubscribe, handleUnifyVpCreate, handleUnifyVpUpdate, handleUnifyVpDelete, handleUnifyVpRead, handleUnifyTaskMessage, handleUnifyUserMemoryWrite, handleUnifyUserMemoryRemove, handleUnifyListGroups, handleUnifyCreateGroup, handleUnifyRenameGroup, handleUnifyArchiveGroup, handleUnifyAddMember, handleUnifyRemoveMember, handleUnifySetDefaultVp, handleUnifyDreamTrigger } from '../unify/web-bridge.js';
|
|
39
|
+
import { handleUnifyChat, handleUnifyGroupChat, handleUnifyModeSwitch, handleUnifyModelSwitch, resetUnifySession, handleUnifyLoadHistory, handleUnifyMergeThread, handleUnifyForkThread, handleUnifyAbortThread, handleUnifyAbortAll, handleUnifyVpSubscribe, handleUnifyVpCreate, handleUnifyVpUpdate, handleUnifyVpDelete, handleUnifyVpRead, handleUnifyTaskMessage, handleUnifyUserMemoryWrite, handleUnifyUserMemoryRemove, handleUnifyListGroups, handleUnifyCreateGroup, handleUnifyRenameGroup, handleUnifyArchiveGroup, handleUnifyAddMember, handleUnifyRemoveMember, handleUnifySetDefaultVp, handleUnifyDreamTrigger } from '../unify/web-bridge.js';
|
|
40
40
|
|
|
41
41
|
export async function handleMessage(msg) {
|
|
42
42
|
switch (msg.type) {
|
|
@@ -360,6 +360,11 @@ export async function handleMessage(msg) {
|
|
|
360
360
|
await handleUnifyChat(msg);
|
|
361
361
|
break;
|
|
362
362
|
|
|
363
|
+
// task-338-F4: Unify group-chat dispatch via GroupCoordinator.
|
|
364
|
+
case 'unify_group_chat':
|
|
365
|
+
await handleUnifyGroupChat(msg);
|
|
366
|
+
break;
|
|
367
|
+
|
|
363
368
|
case 'unify_load_history':
|
|
364
369
|
await handleUnifyLoadHistory(msg);
|
|
365
370
|
break;
|
package/package.json
CHANGED
package/unify/vp/vp-bridge.js
CHANGED
|
@@ -145,7 +145,7 @@ function _fanout(evt) {
|
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
function ensureLoader(registry = defaultRegistry) {
|
|
148
|
-
if (_loaderStarted) return _loader;
|
|
148
|
+
if (_loaderStarted) return { loader: _loader, fresh: false };
|
|
149
149
|
_loaderStarted = true;
|
|
150
150
|
try {
|
|
151
151
|
_loader = new VpLoader({
|
|
@@ -158,7 +158,7 @@ function ensureLoader(registry = defaultRegistry) {
|
|
|
158
158
|
// Hot-reload optional; subscribe still returns whatever scan loaded.
|
|
159
159
|
_loader = null;
|
|
160
160
|
}
|
|
161
|
-
return _loader;
|
|
161
|
+
return { loader: _loader, fresh: true };
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
/**
|
|
@@ -209,7 +209,19 @@ export function buildVpSnapshot(registry = defaultRegistry) {
|
|
|
209
209
|
* @returns {() => void} unsubscribe fn
|
|
210
210
|
*/
|
|
211
211
|
export function handleVpSubscribe(sendUnifyEvent, registry = defaultRegistry) {
|
|
212
|
-
ensureLoader(registry);
|
|
212
|
+
const { loader, fresh } = ensureLoader(registry);
|
|
213
|
+
// task-338-F2: replay semantics. The loader's own start() already scans
|
|
214
|
+
// on first creation, so on a `fresh` loader we skip the extra rescan (it
|
|
215
|
+
// would be redundant work and, more importantly, tests that seed the
|
|
216
|
+
// registry BEFORE subscribing would see their seeded entries wiped by
|
|
217
|
+
// a rescan against an unrelated DEFAULT_VP_LIB_DIR). On subsequent
|
|
218
|
+
// subscribes (page reload, reconnect, second web client), rescanNow()
|
|
219
|
+
// refreshes the registry so every client gets a snapshot that reflects
|
|
220
|
+
// current disk — catching the case where the FS watcher missed an
|
|
221
|
+
// event or VPs were added between the initial scan and this subscribe.
|
|
222
|
+
if (!fresh && loader && typeof loader.rescanNow === 'function') {
|
|
223
|
+
try { loader.rescanNow(); } catch { /* never crash subscribe on rescan */ }
|
|
224
|
+
}
|
|
213
225
|
_subscribers.add(sendUnifyEvent);
|
|
214
226
|
try {
|
|
215
227
|
sendUnifyEvent(buildVpSnapshot(registry));
|
package/unify/web-bridge.js
CHANGED
|
@@ -940,6 +940,106 @@ function handleEngineEvent(event, threadId, hctx) {
|
|
|
940
940
|
}
|
|
941
941
|
}
|
|
942
942
|
|
|
943
|
+
/**
|
|
944
|
+
* task-338-F4: Handle a unify_group_chat message from the web UI.
|
|
945
|
+
*
|
|
946
|
+
* Routes user text through the group coordinator's dispatch contract:
|
|
947
|
+
* 1. @-mentions → each mentioned vpId (intersected with group roster)
|
|
948
|
+
* 2. no mention → group.defaultVpId
|
|
949
|
+
* 3. no default VP → fallback to legacy single-agent handleUnifyChat
|
|
950
|
+
*
|
|
951
|
+
* Emits a `group_message` event tagged with `vpId` per dispatched target so
|
|
952
|
+
* frontend can render VP-scoped feedback. Does NOT itself run the engine —
|
|
953
|
+
* for each resolved target, it delegates into handleUnifyChat (which owns
|
|
954
|
+
* the Dispatcher + AbortController + timeout plumbing).
|
|
955
|
+
*
|
|
956
|
+
* Message shape: { type:'unify_group_chat', groupId, text, mentions? }
|
|
957
|
+
*
|
|
958
|
+
* @param {{groupId:string, text:string, mentions?:string[], agentId?:string, userId?:string, username?:string}} msg
|
|
959
|
+
*/
|
|
960
|
+
export async function handleUnifyGroupChat(msg) {
|
|
961
|
+
if (!msg || typeof msg !== 'object') return;
|
|
962
|
+
const { groupId, text } = msg;
|
|
963
|
+
if (!text?.trim()) return;
|
|
964
|
+
const mentions = Array.isArray(msg.mentions) ? msg.mentions : [];
|
|
965
|
+
|
|
966
|
+
// Resolve the group meta. If the group is missing / not found, fall back
|
|
967
|
+
// to legacy single-agent behavior so the send never silently drops.
|
|
968
|
+
let meta = null;
|
|
969
|
+
try {
|
|
970
|
+
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
971
|
+
if (yeaftDir && groupId) {
|
|
972
|
+
const { openGroup, loadGroupMeta } = await import('./groups/group-store.js');
|
|
973
|
+
const { join } = await import('node:path');
|
|
974
|
+
const { existsSync } = await import('node:fs');
|
|
975
|
+
const root = join(yeaftDir, 'groups');
|
|
976
|
+
const dir = join(root, groupId);
|
|
977
|
+
if (existsSync(dir) && loadGroupMeta(dir)) {
|
|
978
|
+
const handle = openGroup(root, groupId);
|
|
979
|
+
meta = handle.getMeta();
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
} catch (err) {
|
|
983
|
+
console.warn('[Unify] unify_group_chat: group resolve failed', err?.message || err);
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
// Resolve target vpIds.
|
|
987
|
+
const roster = Array.isArray(meta?.roster) ? meta.roster : [];
|
|
988
|
+
let targets = [];
|
|
989
|
+
if (mentions.length > 0) {
|
|
990
|
+
// Intersect mentions with roster. Unknown mentions are ignored (not
|
|
991
|
+
// dispatched) — coordinator §6 "not_in_roster" semantics.
|
|
992
|
+
targets = mentions.filter((v) => roster.includes(v));
|
|
993
|
+
}
|
|
994
|
+
if (targets.length === 0 && meta?.defaultVpId && roster.includes(meta.defaultVpId)) {
|
|
995
|
+
targets = [meta.defaultVpId];
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// Fallback: no target resolvable → behave as the legacy single-agent path
|
|
999
|
+
// so that the user's text still lands on the Engine.
|
|
1000
|
+
if (targets.length === 0) {
|
|
1001
|
+
await handleUnifyChat({
|
|
1002
|
+
...msg,
|
|
1003
|
+
prompt: text,
|
|
1004
|
+
});
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
// Tag outbound events with vpId so the UI can attribute them per VP. One
|
|
1009
|
+
// `group_message` event per target (mirrors coordinator.deliver() calls).
|
|
1010
|
+
for (const vpId of targets) {
|
|
1011
|
+
try {
|
|
1012
|
+
sendUnifyEvent({
|
|
1013
|
+
type: 'group_message',
|
|
1014
|
+
groupId,
|
|
1015
|
+
vpId,
|
|
1016
|
+
text,
|
|
1017
|
+
mentions,
|
|
1018
|
+
trigger: mentions.includes(vpId) ? 'mention' : 'fallback',
|
|
1019
|
+
ts: Date.now(),
|
|
1020
|
+
});
|
|
1021
|
+
} catch { /* never crash WS pipeline */ }
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
// For each target, dispatch into the engine via handleUnifyChat. We prepend
|
|
1025
|
+
// an `@vp-<id>` tag on the prompt so the Dispatcher can route per-VP when
|
|
1026
|
+
// VP-bound Engine wiring lands (task-338-F2/F5); today handleUnifyChat
|
|
1027
|
+
// still runs a single shared Engine, which preserves legacy behaviour.
|
|
1028
|
+
for (const vpId of targets) {
|
|
1029
|
+
const scoped = `@vp-${vpId} ${text}`;
|
|
1030
|
+
try {
|
|
1031
|
+
await handleUnifyChat({
|
|
1032
|
+
...msg,
|
|
1033
|
+
prompt: scoped,
|
|
1034
|
+
groupId,
|
|
1035
|
+
vpId,
|
|
1036
|
+
});
|
|
1037
|
+
} catch (err) {
|
|
1038
|
+
console.warn('[Unify] unify_group_chat: per-vp dispatch failed', vpId, err?.message || err);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
|
|
943
1043
|
/**
|
|
944
1044
|
* Handle a unify_chat message from the web UI.
|
|
945
1045
|
*
|