@yeaft/webchat-agent 0.1.689 → 0.1.691
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 +4 -1
- package/package.json +1 -1
- package/unify/engine.js +6 -5
- package/unify/groups/group-crud.js +20 -0
- package/unify/groups/group-store.js +7 -0
- package/unify/prompts.js +10 -0
- package/unify/web-bridge.js +62 -8
|
@@ -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, getSearchSettings, updateSearchSettings, fetchTavilyUsage } from '../unify/config-api.js';
|
|
39
|
-
import { handleUnifyGroupChat, handleUnifyModeSwitch, handleUnifyModelSwitch, resetUnifySession, handleUnifyLoadHistory, handleUnifyAbortThread, handleUnifyAbortAll, handleUnifyAbortTurn, handleUnifyVpSubscribe, handleUnifyVpCreate, handleUnifyVpUpdate, handleUnifyVpDelete, handleUnifyVpRead, handleUnifyFeatureMessage, handleUnifyFetchSummaryHistory, handleUnifyFeatureCrud, handleUnifyListGroups, handleUnifyCreateGroup, handleUnifyRenameGroup, handleUnifyArchiveGroup, handleUnifyDeleteGroup, handleUnifyAddMember, handleUnifyRemoveMember, handleUnifySetDefaultVp, handleUnifyDreamTrigger } from '../unify/web-bridge.js';
|
|
39
|
+
import { handleUnifyGroupChat, handleUnifyModeSwitch, handleUnifyModelSwitch, resetUnifySession, handleUnifyLoadHistory, handleUnifyAbortThread, handleUnifyAbortAll, handleUnifyAbortTurn, handleUnifyVpSubscribe, handleUnifyVpCreate, handleUnifyVpUpdate, handleUnifyVpDelete, handleUnifyVpRead, handleUnifyFeatureMessage, handleUnifyFetchSummaryHistory, handleUnifyFeatureCrud, handleUnifyListGroups, handleUnifyCreateGroup, handleUnifyRenameGroup, handleUnifyUpdateGroup, handleUnifyArchiveGroup, handleUnifyDeleteGroup, handleUnifyAddMember, handleUnifyRemoveMember, handleUnifySetDefaultVp, handleUnifyDreamTrigger } from '../unify/web-bridge.js';
|
|
40
40
|
|
|
41
41
|
export async function handleMessage(msg) {
|
|
42
42
|
switch (msg.type) {
|
|
@@ -468,6 +468,9 @@ export async function handleMessage(msg) {
|
|
|
468
468
|
case 'unify_rename_group':
|
|
469
469
|
handleUnifyRenameGroup(msg);
|
|
470
470
|
break;
|
|
471
|
+
case 'unify_update_group':
|
|
472
|
+
handleUnifyUpdateGroup(msg);
|
|
473
|
+
break;
|
|
471
474
|
case 'unify_archive_group':
|
|
472
475
|
handleUnifyArchiveGroup(msg);
|
|
473
476
|
break;
|
package/package.json
CHANGED
package/unify/engine.js
CHANGED
|
@@ -563,7 +563,7 @@ export class Engine {
|
|
|
563
563
|
* @param {{user?:string, group?:string, vp?:string}} [summaries]
|
|
564
564
|
* @returns {string}
|
|
565
565
|
*/
|
|
566
|
-
#buildSystemPrompt(memory, compactSummary, prompt, memoryInjection, userProfile, vpPersona, summaries) {
|
|
566
|
+
#buildSystemPrompt(memory, compactSummary, prompt, memoryInjection, userProfile, vpPersona, summaries, groupAnnouncement) {
|
|
567
567
|
// Get relevant skill content if SkillManager is wired
|
|
568
568
|
let skillContent = '';
|
|
569
569
|
if (this.#skillManager && prompt) {
|
|
@@ -585,6 +585,7 @@ export class Engine {
|
|
|
585
585
|
userProfile,
|
|
586
586
|
vpPersona,
|
|
587
587
|
summaries,
|
|
588
|
+
groupAnnouncement,
|
|
588
589
|
// Worker-shape harness is descriptive metadata for human inspection;
|
|
589
590
|
// production prompts skip it to save tokens. Re-enable via env when
|
|
590
591
|
// diagnosing prompt structure issues.
|
|
@@ -859,7 +860,7 @@ export class Engine {
|
|
|
859
860
|
* SCENARIO_EFFORT. Unknown values fall through to 'high'.
|
|
860
861
|
* @yields {EngineEvent}
|
|
861
862
|
*/
|
|
862
|
-
async *query({ prompt, messages = [], signal, userEffort = null, scenario = 'chat', vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers, groupId, vpPlan } = {}) {
|
|
863
|
+
async *query({ prompt, messages = [], signal, userEffort = null, scenario = 'chat', vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers, groupId, vpPlan, groupAnnouncement } = {}) {
|
|
863
864
|
if (!prompt || typeof prompt !== 'string' || !prompt.trim()) {
|
|
864
865
|
yield {
|
|
865
866
|
type: 'error',
|
|
@@ -910,7 +911,7 @@ export class Engine {
|
|
|
910
911
|
const runSignal = abortCtrl.signal;
|
|
911
912
|
|
|
912
913
|
try {
|
|
913
|
-
yield* this.#runQuery({ prompt: effectivePrompt, messages, signal: runSignal, userEffort: effectiveUserEffort, scenario, vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers, groupId, vpPlan });
|
|
914
|
+
yield* this.#runQuery({ prompt: effectivePrompt, messages, signal: runSignal, userEffort: effectiveUserEffort, scenario, vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers, groupId, vpPlan, groupAnnouncement });
|
|
914
915
|
} finally {
|
|
915
916
|
if (signal) {
|
|
916
917
|
try { signal.removeEventListener('abort', onExternalAbort); } catch { /* ignore */ }
|
|
@@ -928,7 +929,7 @@ export class Engine {
|
|
|
928
929
|
* in a try/finally without indenting the whole loop.
|
|
929
930
|
* @private
|
|
930
931
|
*/
|
|
931
|
-
async *#runQuery({ prompt, messages, signal, userEffort = null, scenario = 'chat', vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers, groupId, vpPlan }) {
|
|
932
|
+
async *#runQuery({ prompt, messages, signal, userEffort = null, scenario = 'chat', vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers, groupId, vpPlan, groupAnnouncement }) {
|
|
932
933
|
|
|
933
934
|
// ─── Pre-query: FTS5 Memory Recall + Compact Summary ──
|
|
934
935
|
// Memory feed comes from two places:
|
|
@@ -1004,7 +1005,7 @@ export class Engine {
|
|
|
1004
1005
|
: amsContext.snapshotBlock;
|
|
1005
1006
|
}
|
|
1006
1007
|
|
|
1007
|
-
const systemPrompt = this.#buildSystemPrompt(undefined, compactSummary, prompt, memoryInjection, userProfile, vpPersona, summaries);
|
|
1008
|
+
const systemPrompt = this.#buildSystemPrompt(undefined, compactSummary, prompt, memoryInjection, userProfile, vpPersona, summaries, groupAnnouncement);
|
|
1008
1009
|
|
|
1009
1010
|
// Build conversation: existing messages + new user message
|
|
1010
1011
|
const conversationMessages = [
|
|
@@ -162,6 +162,26 @@ export function renameGroup(yeaftDir, groupId, newName) {
|
|
|
162
162
|
return next;
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
/**
|
|
166
|
+
* (A.2.b) Update announcement — group-wide system-prompt prefix shared by
|
|
167
|
+
* every VP in the group (CLAUDE.md-style). Empty/whitespace clears it.
|
|
168
|
+
*
|
|
169
|
+
* `text` must be a string. Trimmed before persist so leading/trailing
|
|
170
|
+
* whitespace doesn't pollute the prompt.
|
|
171
|
+
*/
|
|
172
|
+
export function updateGroupAnnouncement(yeaftDir, groupId, text) {
|
|
173
|
+
if (typeof text !== 'string') {
|
|
174
|
+
throw new GroupCrudError('invalid_announcement', groupId);
|
|
175
|
+
}
|
|
176
|
+
const announcement = text.trim();
|
|
177
|
+
const handle = requireGroup(yeaftDir, groupId);
|
|
178
|
+
const meta = handle.getMeta();
|
|
179
|
+
handle.saveMeta({ ...meta, announcement });
|
|
180
|
+
const next = handle.getMeta();
|
|
181
|
+
handle.close();
|
|
182
|
+
return next;
|
|
183
|
+
}
|
|
184
|
+
|
|
165
185
|
/**
|
|
166
186
|
* (A.3) Archive — renames the dir to `.archived-<ts>-<id>`. Directory
|
|
167
187
|
* prefix `.` keeps `listGroups` from picking it up (readdirSync filter in
|
|
@@ -126,6 +126,7 @@ export function createGroup(groupsRoot, spec) {
|
|
|
126
126
|
name: spec.name || spec.id,
|
|
127
127
|
roster,
|
|
128
128
|
defaultVpId: spec.defaultVpId || null,
|
|
129
|
+
announcement: typeof spec.announcement === 'string' ? spec.announcement : '',
|
|
129
130
|
createdAt: spec.createdAt || new Date().toISOString(),
|
|
130
131
|
};
|
|
131
132
|
h.saveMeta(meta);
|
|
@@ -140,6 +141,9 @@ export function loadGroupMeta(dir) {
|
|
|
140
141
|
const raw = readFileSync(path, 'utf8');
|
|
141
142
|
const parsed = JSON.parse(raw);
|
|
142
143
|
validateMeta(parsed);
|
|
144
|
+
// Legacy groups created before the announcement field was added are
|
|
145
|
+
// forward-compat: missing field reads back as empty string.
|
|
146
|
+
if (typeof parsed.announcement !== 'string') parsed.announcement = '';
|
|
143
147
|
return parsed;
|
|
144
148
|
} catch {
|
|
145
149
|
return null;
|
|
@@ -173,6 +177,9 @@ function validateMeta(meta) {
|
|
|
173
177
|
if (meta.defaultVpId != null && typeof meta.defaultVpId !== 'string') {
|
|
174
178
|
throw new Error('group.defaultVpId must be string|null');
|
|
175
179
|
}
|
|
180
|
+
if (meta.announcement != null && typeof meta.announcement !== 'string') {
|
|
181
|
+
throw new Error('group.announcement must be string');
|
|
182
|
+
}
|
|
176
183
|
}
|
|
177
184
|
|
|
178
185
|
/**
|
package/unify/prompts.js
CHANGED
|
@@ -283,6 +283,7 @@ export function buildSystemPrompt({
|
|
|
283
283
|
coreMemory,
|
|
284
284
|
memoryTraceAvailable = false,
|
|
285
285
|
vpPersona,
|
|
286
|
+
groupAnnouncement = '',
|
|
286
287
|
} = {}) {
|
|
287
288
|
// Fallback to English for unknown languages
|
|
288
289
|
const lang = PROMPTS[language] || PROMPTS.en;
|
|
@@ -310,6 +311,15 @@ export function buildSystemPrompt({
|
|
|
310
311
|
}
|
|
311
312
|
}
|
|
312
313
|
|
|
314
|
+
// ─── 1.5 Group Announcement (CLAUDE.md-style shared prefix) ───
|
|
315
|
+
// When a group has set an announcement, every VP in the group sees it
|
|
316
|
+
// near the top of the system prompt — before tools, memory, mode-specific
|
|
317
|
+
// instructions. Empty/whitespace = no block emitted.
|
|
318
|
+
const annText = (typeof groupAnnouncement === 'string') ? groupAnnouncement.trim() : '';
|
|
319
|
+
if (annText) {
|
|
320
|
+
parts.push(`[Group Announcement]\n${annText}`);
|
|
321
|
+
}
|
|
322
|
+
|
|
313
323
|
// ─── 2. Date Metadata ──────────────────────────────────
|
|
314
324
|
parts.push(lang.date(new Date().toISOString().split('T')[0]));
|
|
315
325
|
|
package/unify/web-bridge.js
CHANGED
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
GroupCrudError,
|
|
34
34
|
createGroupFromSpec,
|
|
35
35
|
renameGroup,
|
|
36
|
+
updateGroupAnnouncement,
|
|
36
37
|
archiveGroup,
|
|
37
38
|
deleteGroup,
|
|
38
39
|
purgeArchivedGroups,
|
|
@@ -318,6 +319,45 @@ export function handleUnifyRenameGroup(msg) {
|
|
|
318
319
|
}
|
|
319
320
|
}
|
|
320
321
|
|
|
322
|
+
/**
|
|
323
|
+
* `unify_update_group` — generalised group meta patch. Currently accepts
|
|
324
|
+
* `name` and `announcement` keys. Empty patch is rejected; an empty/
|
|
325
|
+
* whitespace-only `name` is also rejected up front rather than letting
|
|
326
|
+
* `renameGroup` raise a less-specific error deeper in the call stack.
|
|
327
|
+
*
|
|
328
|
+
* Partial-success contract: when a single patch contains BOTH `name` and
|
|
329
|
+
* `announcement`, the rename is committed first; if the announcement
|
|
330
|
+
* write throws, the rename has already persisted on disk and the client
|
|
331
|
+
* receives `ok:false` for the announcement error — i.e. the WS op is not
|
|
332
|
+
* atomic. Today's UI binds Save buttons per pane in `GroupSettingsModal`
|
|
333
|
+
* so this is theoretical; readers extending the patch shape should know
|
|
334
|
+
* the contract permits half-commits.
|
|
335
|
+
*/
|
|
336
|
+
export function handleUnifyUpdateGroup(msg) {
|
|
337
|
+
const requestId = msg && msg.requestId;
|
|
338
|
+
const groupId = msg && msg.groupId;
|
|
339
|
+
const patch = (msg && msg.patch && typeof msg.patch === 'object') ? msg.patch : null;
|
|
340
|
+
try {
|
|
341
|
+
const hasName = patch && typeof patch.name === 'string' && patch.name.trim().length > 0;
|
|
342
|
+
const hasAnnouncement = patch && typeof patch.announcement === 'string';
|
|
343
|
+
if (!patch || (!hasName && !hasAnnouncement)) {
|
|
344
|
+
throw new GroupCrudError('invalid_patch', groupId);
|
|
345
|
+
}
|
|
346
|
+
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
347
|
+
let group = null;
|
|
348
|
+
if (hasName) {
|
|
349
|
+
group = renameGroup(yeaftDir, groupId, patch.name);
|
|
350
|
+
}
|
|
351
|
+
if (hasAnnouncement) {
|
|
352
|
+
group = updateGroupAnnouncement(yeaftDir, groupId, patch.announcement);
|
|
353
|
+
}
|
|
354
|
+
sendGroupCrudResult({ op: 'update', requestId, ok: true, group });
|
|
355
|
+
sendGroupSnapshotBroadcast();
|
|
356
|
+
} catch (err) {
|
|
357
|
+
sendGroupCrudResult({ op: 'update', requestId, ok: false, error: groupErrorPayload(err) });
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
321
361
|
export function handleUnifyArchiveGroup(msg) {
|
|
322
362
|
const requestId = msg && msg.requestId;
|
|
323
363
|
const groupId = msg && msg.groupId;
|
|
@@ -912,16 +952,24 @@ export async function handleUnifyGroupChat(msg) {
|
|
|
912
952
|
* Build the per-query VP context for the Engine.
|
|
913
953
|
*/
|
|
914
954
|
export function buildVpQueryOpts({ vpId, groupCoordinator, groupId }) {
|
|
955
|
+
// Read the group meta once and reuse for both defaultVpId fallback and
|
|
956
|
+
// announcement injection. Each .getMeta() reload reads + parses the
|
|
957
|
+
// group.json file, so calling it twice per turn is wasteful — and
|
|
958
|
+
// (more importantly) opens a window where a concurrent group edit
|
|
959
|
+
// could land between the two reads, giving the engine a defaultVpId
|
|
960
|
+
// from one snapshot and an announcement from a newer one.
|
|
961
|
+
let groupMeta = null;
|
|
962
|
+
try {
|
|
963
|
+
groupMeta = groupCoordinator && groupCoordinator.group
|
|
964
|
+
&& typeof groupCoordinator.group.getMeta === 'function'
|
|
965
|
+
? groupCoordinator.group.getMeta() : null;
|
|
966
|
+
} catch { /* coordinator inspection is best-effort */ }
|
|
967
|
+
|
|
915
968
|
let resolvedVpId = vpId;
|
|
916
969
|
if (!resolvedVpId) {
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
? groupCoordinator.group.getMeta() : null;
|
|
921
|
-
if (meta && typeof meta.defaultVpId === 'string' && meta.defaultVpId) {
|
|
922
|
-
resolvedVpId = meta.defaultVpId;
|
|
923
|
-
}
|
|
924
|
-
} catch { /* coordinator inspection is best-effort */ }
|
|
970
|
+
if (groupMeta && typeof groupMeta.defaultVpId === 'string' && groupMeta.defaultVpId) {
|
|
971
|
+
resolvedVpId = groupMeta.defaultVpId;
|
|
972
|
+
}
|
|
925
973
|
}
|
|
926
974
|
if (!resolvedVpId) {
|
|
927
975
|
const cfgDefault = session?.config?.defaultVpId;
|
|
@@ -943,6 +991,12 @@ export function buildVpQueryOpts({ vpId, groupCoordinator, groupId }) {
|
|
|
943
991
|
if (typeof groupId === 'string' && groupId.trim()) {
|
|
944
992
|
out.groupId = groupId.trim();
|
|
945
993
|
}
|
|
994
|
+
// task-334-group-editor: surface the group announcement to the engine so
|
|
995
|
+
// buildWorkerPrompt can inject it as a CLAUDE.md-style shared prefix.
|
|
996
|
+
// Empty/missing reads as '' and prompts.js skips the section.
|
|
997
|
+
if (groupMeta && typeof groupMeta.announcement === 'string') {
|
|
998
|
+
out.groupAnnouncement = groupMeta.announcement;
|
|
999
|
+
}
|
|
946
1000
|
try {
|
|
947
1001
|
const vp = readVp(resolvedVpId);
|
|
948
1002
|
if (vp) {
|