@yeaft/webchat-agent 0.1.555 → 0.1.557
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 +126 -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,132 @@ 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
|
+
// Fallback path #1 (PM red-line): no groupId on payload → skip group
|
|
967
|
+
// resolution entirely and hand the text to the legacy single-agent
|
|
968
|
+
// dispatcher. Ensures backward-compat with old clients that never learned
|
|
969
|
+
// about groups.
|
|
970
|
+
if (!groupId) {
|
|
971
|
+
await handleUnifyChat({ ...msg, prompt: text });
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
// Open the group handle (meta + jsonl log). Unresolvable groups take the
|
|
976
|
+
// legacy fallback — never silently drop the send.
|
|
977
|
+
let groupHandle = null;
|
|
978
|
+
try {
|
|
979
|
+
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
980
|
+
if (yeaftDir) {
|
|
981
|
+
const { openGroup, loadGroupMeta } = await import('./groups/group-store.js');
|
|
982
|
+
const { join } = await import('node:path');
|
|
983
|
+
const { existsSync } = await import('node:fs');
|
|
984
|
+
const root = join(yeaftDir, 'groups');
|
|
985
|
+
const dir = join(root, groupId);
|
|
986
|
+
if (existsSync(dir) && loadGroupMeta(dir)) {
|
|
987
|
+
groupHandle = openGroup(root, groupId);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
} catch (err) {
|
|
991
|
+
console.warn('[Unify] unify_group_chat: group open failed', err?.message || err);
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
if (!groupHandle) {
|
|
995
|
+
await handleUnifyChat({ ...msg, prompt: text });
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
// Adapter layer (PM red-line: do NOT modify coordinator to fit this
|
|
1000
|
+
// consumer). We drive `createCoordinator()` with a capturing `deliver`
|
|
1001
|
+
// callback, collect its dispatched/fallback report, then translate each
|
|
1002
|
+
// target into (a) a per-VP `group_message` event for the UI and (b) a
|
|
1003
|
+
// per-VP prompt dispatched through the legacy `handleUnifyChat` path.
|
|
1004
|
+
//
|
|
1005
|
+
// Source of truth for mentions is the PAYLOAD (ChatInput parsed them
|
|
1006
|
+
// once). We pass them through the coordinator's `input.meta` so the
|
|
1007
|
+
// appended log carries the authoritative set — the coordinator will also
|
|
1008
|
+
// run its own parse over `text` for routing, which matches the payload by
|
|
1009
|
+
// construction (ChatInput's `parseMentions` is the same regex).
|
|
1010
|
+
const { createCoordinator } = await import('./groups/coordinator.js');
|
|
1011
|
+
const captured = [];
|
|
1012
|
+
const coord = createCoordinator(groupHandle, {
|
|
1013
|
+
deliver: (vpId, envelope) => { captured.push({ vpId, envelope }); },
|
|
1014
|
+
});
|
|
1015
|
+
|
|
1016
|
+
let report;
|
|
1017
|
+
try {
|
|
1018
|
+
report = coord.ingest({
|
|
1019
|
+
from: 'user',
|
|
1020
|
+
role: 'user',
|
|
1021
|
+
text,
|
|
1022
|
+
meta: { mentions },
|
|
1023
|
+
});
|
|
1024
|
+
} catch (err) {
|
|
1025
|
+
console.warn('[Unify] unify_group_chat: coord.ingest failed', err?.message || err);
|
|
1026
|
+
await handleUnifyChat({ ...msg, prompt: text });
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
// Fallback path #2: coordinator resolved no targets and no default VP.
|
|
1031
|
+
// Hand off to the legacy single-agent path so the text still runs.
|
|
1032
|
+
const dispatchedIds = Array.isArray(report?.dispatched) ? report.dispatched : [];
|
|
1033
|
+
if (dispatchedIds.length === 0 && !report?.fallback) {
|
|
1034
|
+
await handleUnifyChat({ ...msg, prompt: text });
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
// Per target: emit `group_message` tagged with `speakerVpId` (the VP
|
|
1039
|
+
// being addressed — F3's GroupSelector binding consumes this) + dispatch
|
|
1040
|
+
// through the Engine via handleUnifyChat with an `@vp-<id>` prompt prefix.
|
|
1041
|
+
for (const { vpId, envelope } of captured) {
|
|
1042
|
+
try {
|
|
1043
|
+
sendUnifyEvent({
|
|
1044
|
+
type: 'group_message',
|
|
1045
|
+
groupId,
|
|
1046
|
+
vpId,
|
|
1047
|
+
speakerVpId: vpId,
|
|
1048
|
+
text,
|
|
1049
|
+
mentions,
|
|
1050
|
+
trigger: envelope?.trigger || 'fallback',
|
|
1051
|
+
ts: Date.now(),
|
|
1052
|
+
});
|
|
1053
|
+
} catch { /* never crash WS pipeline */ }
|
|
1054
|
+
|
|
1055
|
+
try {
|
|
1056
|
+
await handleUnifyChat({
|
|
1057
|
+
...msg,
|
|
1058
|
+
prompt: `@vp-${vpId} ${text}`,
|
|
1059
|
+
groupId,
|
|
1060
|
+
vpId,
|
|
1061
|
+
speakerVpId: vpId,
|
|
1062
|
+
});
|
|
1063
|
+
} catch (err) {
|
|
1064
|
+
console.warn('[Unify] unify_group_chat: per-vp dispatch failed', vpId, err?.message || err);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
|
|
943
1069
|
/**
|
|
944
1070
|
* Handle a unify_chat message from the web UI.
|
|
945
1071
|
*
|