@yeaft/webchat-agent 1.0.326 → 1.0.329
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/local-runtime/version.json +1 -1
- package/local-runtime/web/app.bundle.js +164 -139
- package/local-runtime/web/app.bundle.js.gz +0 -0
- package/local-runtime/web/index.html +2 -2
- package/local-runtime/web/style.bundle.css +1 -1
- package/local-runtime/web/style.bundle.css.gz +0 -0
- package/package.json +1 -1
- package/yeaft/engine.js +98 -35
- package/yeaft/memory/ams.js +18 -8
- package/yeaft/prompts.js +12 -3
- package/yeaft/sub-agent/prompt-queue.js +4 -1
- package/yeaft/sub-agent/runner.js +8 -0
- package/yeaft/tasks/manager.js +48 -11
- package/yeaft/tools/send-message.js +1 -0
- package/yeaft/web-bridge.js +11 -6
|
Binary file
|
package/package.json
CHANGED
package/yeaft/engine.js
CHANGED
|
@@ -31,6 +31,7 @@ import { evaluateCompactTriggers } from './compact/triggers.js';
|
|
|
31
31
|
import { archiveTurn } from './archive/turn-archive.js';
|
|
32
32
|
import { archiveToolResults } from './archive/tool-results.js';
|
|
33
33
|
import { readSummary as readScopeSummary } from './memory/store.js';
|
|
34
|
+
import { ActiveMemorySet } from './memory/ams.js';
|
|
34
35
|
import { runAdjust } from './memory/adjust.js';
|
|
35
36
|
import { cleanMemoryPromptText, isMemoryPromptRelevant } from './memory/prompt-cleanup.js';
|
|
36
37
|
import { isVpSeedBackfillStub } from './memory/seed-backfill.js';
|
|
@@ -44,7 +45,7 @@ import { lookupModelLimitSync } from './llm/models-dev.js';
|
|
|
44
45
|
import { countTurns } from './turn-utils.js';
|
|
45
46
|
import { attachRouterPlan, extractPriorPlan, stripMetaForWire } from './router/continuity.js';
|
|
46
47
|
import { resolveThinking } from './router/thinking.js';
|
|
47
|
-
import { approxTokens } from './memory/budget.js';
|
|
48
|
+
import { approxTokens, computeBudget } from './memory/budget.js';
|
|
48
49
|
import { COLLAB_TOOL_POLICY, isToolErrorOutput, normalizeToolOutput, truncateToolResultIfNeeded } from './tools/registry.js';
|
|
49
50
|
import { extractDisplayImages, stripDisplayImageData } from './image-assets.js';
|
|
50
51
|
import { acknowledgePendingNotifications, formatNotificationsForPrompt, peekPendingNotifications } from './sub-agent/notifications.js';
|
|
@@ -399,7 +400,7 @@ export function shouldAllowGroupReflection({
|
|
|
399
400
|
* @param {{
|
|
400
401
|
* sessionId?: string|null,
|
|
401
402
|
* ownVpId?: string|null,
|
|
402
|
-
* summaries: { user?: string, session?: string, vp?: string, topics?: Array<{scope:string, summary:string}> }
|
|
403
|
+
* summaries: { user?: string, session?: string, vp?: string, topics?: Array<{scope:string, summary:string}>, relatedSessions?: Array<{sessionId:string, summary:string}> }
|
|
403
404
|
* }} args
|
|
404
405
|
* @returns {Array<{scope: string, summary: string}>}
|
|
405
406
|
*/
|
|
@@ -445,6 +446,18 @@ export function buildResidentEntries(args) {
|
|
|
445
446
|
if (args.sessionId && args.ownVpId && vpSummary && !isVpSeedBackfillStub(vpSummary)) {
|
|
446
447
|
out.push({ scope: `sessions/${args.sessionId}/vp/${args.ownVpId}`, summary: vpSummary });
|
|
447
448
|
}
|
|
449
|
+
// Related Session experience is useful but lower-priority than every memory
|
|
450
|
+
// source owned by the active Session/VP. Append it last so the resident
|
|
451
|
+
// budget can never evict current context in favour of historical prose.
|
|
452
|
+
if (Array.isArray(summaries.relatedSessions)) {
|
|
453
|
+
for (const related of summaries.relatedSessions) {
|
|
454
|
+
const relatedSessionId = typeof related?.sessionId === 'string' ? related.sessionId.trim() : '';
|
|
455
|
+
const summary = cleanMemoryPromptText(related?.summary);
|
|
456
|
+
if (relatedSessionId && relatedSessionId !== args.sessionId && summary) {
|
|
457
|
+
out.push({ scope: `sessions/${relatedSessionId}`, summary });
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
448
461
|
return out;
|
|
449
462
|
}
|
|
450
463
|
|
|
@@ -452,6 +465,11 @@ function isZhRuntimeLanguage(language) {
|
|
|
452
465
|
return String(language || '').toLowerCase().startsWith('zh');
|
|
453
466
|
}
|
|
454
467
|
|
|
468
|
+
function sessionIdFromMemoryScope(scope) {
|
|
469
|
+
const match = /^(?:sessions|session|group)\/([^/]+)$/.exec(String(scope || ''));
|
|
470
|
+
return match ? match[1] : null;
|
|
471
|
+
}
|
|
472
|
+
|
|
455
473
|
function resolveMemoryRecallLimit(config) {
|
|
456
474
|
const raw = config?.memoryRecallLimit ?? config?.dreamMemoryRecallLimit;
|
|
457
475
|
if (!Number.isFinite(raw) || raw <= 0) return DEFAULT_MEMORY_RECALL_LIMIT;
|
|
@@ -476,6 +494,7 @@ function loadedResidentDebugEntries(entries) {
|
|
|
476
494
|
kind: 'summary',
|
|
477
495
|
score: null,
|
|
478
496
|
tags: [],
|
|
497
|
+
category: entry.category || null,
|
|
479
498
|
body: entry.summary || '',
|
|
480
499
|
})).filter(entry => entry.body);
|
|
481
500
|
}
|
|
@@ -910,13 +929,17 @@ export class Engine {
|
|
|
910
929
|
* dream tick (Phase 6) is what populates these; on a fresh install they
|
|
911
930
|
* all return ''.
|
|
912
931
|
*
|
|
913
|
-
* @param {{sessionId?: string, vpId?: string, language?: string, topicScopes?: string[]}} ctx
|
|
914
|
-
* @returns {Promise<{user:string, session:string, vp:string, topics:Array<{scope:string, summary:string}>}>}
|
|
932
|
+
* @param {{sessionId?: string, vpId?: string, language?: string, topicScopes?: string[], relatedSessionIds?: string[]}} ctx
|
|
933
|
+
* @returns {Promise<{user:string, session:string, vp:string, topics:Array<{scope:string, summary:string}>, relatedSessions:Array<{sessionId:string, summary:string}>}>}
|
|
915
934
|
*/
|
|
916
|
-
async #loadLayerASummaries({ sessionId, vpId, language, topicScopes } = {}) {
|
|
917
|
-
if (!this.#yeaftDir) return { user: '', session: '', vp: '', topics: [] };
|
|
935
|
+
async #loadLayerASummaries({ sessionId, vpId, language, topicScopes, relatedSessionIds } = {}) {
|
|
936
|
+
if (!this.#yeaftDir) return { user: '', session: '', vp: '', topics: [], relatedSessions: [] };
|
|
918
937
|
const memoryRoot = `${this.#yeaftDir}/memory`;
|
|
919
938
|
const topicScopeList = Array.isArray(topicScopes) ? topicScopes.slice(0, 12) : [];
|
|
939
|
+
const relatedIds = Array.from(new Set((Array.isArray(relatedSessionIds) ? relatedSessionIds : [])
|
|
940
|
+
.filter(id => typeof id === 'string' && id.trim() && id.trim() !== sessionId)
|
|
941
|
+
.map(id => id.trim())))
|
|
942
|
+
.slice(0, 8);
|
|
920
943
|
const tasks = [
|
|
921
944
|
readScopeSummary({ kind: 'user' }, { root: memoryRoot, language }).catch(() => ''),
|
|
922
945
|
sessionId
|
|
@@ -926,10 +949,18 @@ export class Engine {
|
|
|
926
949
|
? readScopeSummary({ kind: 'session-vp', sessionId, id: vpId }, { root: memoryRoot, language }).catch(() => '')
|
|
927
950
|
: Promise.resolve(''),
|
|
928
951
|
Promise.all(topicScopeList.map(scope => readTopicSummary(scope, { root: memoryRoot, language }))),
|
|
952
|
+
Promise.all(relatedIds.map(async relatedSessionId => ({
|
|
953
|
+
sessionId: relatedSessionId,
|
|
954
|
+
summary: await readScopeSummary(
|
|
955
|
+
{ kind: 'session', id: relatedSessionId },
|
|
956
|
+
{ root: memoryRoot, language },
|
|
957
|
+
).catch(() => ''),
|
|
958
|
+
}))),
|
|
929
959
|
];
|
|
930
|
-
const [user, session, vp, topicsRaw] = await Promise.all(tasks);
|
|
960
|
+
const [user, session, vp, topicsRaw, relatedSessionsRaw] = await Promise.all(tasks);
|
|
931
961
|
const topics = (topicsRaw || []).filter(t => t && t.summary);
|
|
932
|
-
|
|
962
|
+
const relatedSessions = (relatedSessionsRaw || []).filter(entry => entry && entry.summary);
|
|
963
|
+
return { user: user || '', session: session || '', vp: vp || '', topics, relatedSessions };
|
|
933
964
|
}
|
|
934
965
|
|
|
935
966
|
async #loadSessionTopicLabels(sessionId, limit = 8) {
|
|
@@ -967,27 +998,42 @@ export class Engine {
|
|
|
967
998
|
* } | null}
|
|
968
999
|
*/
|
|
969
1000
|
#prepareAms(args) {
|
|
970
|
-
if (!this.#amsRegistry) return null;
|
|
971
1001
|
const sessionKey = args.sessionId || 'default';
|
|
972
1002
|
const ownVpId = args.ownVpId || null;
|
|
973
|
-
|
|
1003
|
+
// Some read-only Engine entry points (notably sub-agents) intentionally do
|
|
1004
|
+
// not own the parent's persistent registry. They still need the single AMS
|
|
1005
|
+
// render outlet, otherwise FTS recall succeeds and then vanishes before the
|
|
1006
|
+
// prompt. Use an isolated per-query AMS in that case: it preserves budgets,
|
|
1007
|
+
// cleanup, and dedupe without sharing mutable parent state or writing disk.
|
|
1008
|
+
const ams = this.#amsRegistry
|
|
1009
|
+
? this.#amsRegistry.getOrCreate(sessionKey, { ownVpId })
|
|
1010
|
+
: new ActiveMemorySet({
|
|
1011
|
+
ownVpId,
|
|
1012
|
+
budget: computeBudget(this.#config?.maxContextTokens),
|
|
1013
|
+
});
|
|
974
1014
|
|
|
975
1015
|
// Prime #adjustRanBySession from disk-hydrated state on first access:
|
|
976
1016
|
// a reactivated group resumes with whatever adjustRanThisSession bit
|
|
977
1017
|
// it had on disconnect, so we don't burn a fresh adjust on every
|
|
978
1018
|
// reload. Once set true in this session we never clear it.
|
|
979
|
-
if (
|
|
1019
|
+
if (this.#amsRegistry
|
|
1020
|
+
&& !this.#adjustRanBySession.has(sessionKey)
|
|
980
1021
|
&& this.#amsRegistry.adjustRanThisSession(sessionKey)) {
|
|
981
1022
|
this.#adjustRanBySession.set(sessionKey, true);
|
|
982
1023
|
}
|
|
983
1024
|
|
|
984
1025
|
// (a) Resident: rebuild from the same scope summaries the worker
|
|
985
1026
|
// prompt is already going to see.
|
|
1027
|
+
const relatedSessionIds = new Set((args.summaries?.relatedSessions || [])
|
|
1028
|
+
.map(entry => entry?.sessionId)
|
|
1029
|
+
.filter(Boolean));
|
|
986
1030
|
const residentEntries = buildResidentEntries({
|
|
987
1031
|
sessionId: args.sessionId,
|
|
988
1032
|
ownVpId,
|
|
989
1033
|
summaries: args.summaries || {},
|
|
990
|
-
})
|
|
1034
|
+
}).map(entry => relatedSessionIds.has(entry.scope.replace(/^sessions\//, ''))
|
|
1035
|
+
? { ...entry, category: 'experience' }
|
|
1036
|
+
: { ...entry, category: 'memory' });
|
|
991
1037
|
ams.setResident(residentEntries);
|
|
992
1038
|
|
|
993
1039
|
// (b) onDemand: replace with this turn's FTS hits.
|
|
@@ -996,7 +1042,11 @@ export class Engine {
|
|
|
996
1042
|
|
|
997
1043
|
// (c) Snapshot — render the AMS layers as a single prompt block.
|
|
998
1044
|
const snapshot = ams.snapshot({ userMsg: args.userMsg || '' });
|
|
999
|
-
const snapshotBlock = this.#renderAmsSnapshot(
|
|
1045
|
+
const snapshotBlock = this.#renderAmsSnapshot(
|
|
1046
|
+
snapshot,
|
|
1047
|
+
this.#config.language || 'en',
|
|
1048
|
+
args.sessionId || null,
|
|
1049
|
+
);
|
|
1000
1050
|
|
|
1001
1051
|
const scopes = buildRelevantScopes({
|
|
1002
1052
|
sessionId: args.sessionId,
|
|
@@ -1015,33 +1065,36 @@ export class Engine {
|
|
|
1015
1065
|
* @param {string} [language]
|
|
1016
1066
|
* @returns {string}
|
|
1017
1067
|
*/
|
|
1018
|
-
#renderAmsSnapshot(snap, language = 'en') {
|
|
1068
|
+
#renderAmsSnapshot(snap, language = 'en', activeSessionId = null) {
|
|
1019
1069
|
if (!snap) return '';
|
|
1020
1070
|
const parts = [];
|
|
1021
1071
|
if (snap.resident.length === 0 && snap.recent.length === 0 && snap.onDemand.length === 0) {
|
|
1022
1072
|
return '';
|
|
1023
1073
|
}
|
|
1024
1074
|
const zh = isZhRuntimeLanguage(language);
|
|
1025
|
-
|
|
1075
|
+
const experiences = snap.resident.filter(entry => entry.category === 'experience');
|
|
1076
|
+
const residentMemory = snap.resident.filter(entry => entry.category !== 'experience');
|
|
1077
|
+
parts.push(zh ? '## 相关上下文' : '## Relevant Context');
|
|
1026
1078
|
parts.push(zh
|
|
1027
|
-
? '
|
|
1028
|
-
: '
|
|
1029
|
-
if (
|
|
1030
|
-
parts.push(zh ? '###
|
|
1031
|
-
for (const
|
|
1032
|
-
|
|
1079
|
+
? '以下内容来自持久记忆与相关 Session 的只读经验总结;只把它当作事实背景,不要把过期执行状态当成当前任务。'
|
|
1080
|
+
: 'The following text comes from persistent memory and read-only summaries of related Sessions. Treat it as factual context, not as current execution state.');
|
|
1081
|
+
if (experiences.length > 0) {
|
|
1082
|
+
parts.push(zh ? '### 过去 Session 的经验总结' : '### Experience From Past Sessions');
|
|
1083
|
+
for (const entry of experiences) {
|
|
1084
|
+
const sourceSessionId = sessionIdFromMemoryScope(entry.scope);
|
|
1085
|
+
const label = sourceSessionId && sourceSessionId !== activeSessionId
|
|
1086
|
+
? sourceSessionId
|
|
1087
|
+
: memoryScopeLabel(entry.scope);
|
|
1088
|
+
parts.push(`- **${label}**: ${entry.summary}`);
|
|
1033
1089
|
}
|
|
1034
1090
|
}
|
|
1035
|
-
if (snap.recent.length > 0) {
|
|
1036
|
-
parts.push(zh ? '###
|
|
1037
|
-
for (const
|
|
1038
|
-
parts.push(`-
|
|
1091
|
+
if (residentMemory.length > 0 || snap.recent.length > 0 || snap.onDemand.length > 0) {
|
|
1092
|
+
parts.push(zh ? '### 相关记忆' : '### Relevant Memory');
|
|
1093
|
+
for (const entry of residentMemory) {
|
|
1094
|
+
parts.push(`- **${memoryScopeLabel(entry.scope)}**: ${entry.summary}`);
|
|
1039
1095
|
}
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
parts.push(zh ? '### 按需记忆' : '### OnDemand');
|
|
1043
|
-
for (const s of snap.onDemand) {
|
|
1044
|
-
parts.push(`- (${memoryScopeLabel(s.scope)}) ${(s.body || '').trim()}`);
|
|
1096
|
+
for (const segment of [...snap.recent, ...snap.onDemand]) {
|
|
1097
|
+
parts.push(`- (${memoryScopeLabel(segment.scope)}) ${(segment.body || '').trim()}`);
|
|
1045
1098
|
}
|
|
1046
1099
|
}
|
|
1047
1100
|
return parts.join('\n');
|
|
@@ -1148,13 +1201,14 @@ export class Engine {
|
|
|
1148
1201
|
* @param {object} [args.activeScope] — DESIGN-PROMPT §3 ④ structured scope summary
|
|
1149
1202
|
* @param {string} [args.sessionAnnouncement]
|
|
1150
1203
|
* @param {string} [args.projectInstruction] — server-managed instruction shared by Project Sessions
|
|
1204
|
+
* @param {string} [args.projectLabel] — current Project name and id for prompt attribution
|
|
1151
1205
|
* @param {string} [args.workCenterInstructions] — frozen Agent-level Work Center policy
|
|
1152
1206
|
* @param {string} [args.projectDoc] — resolved CLAUDE.md / AGENTS.md text (already truncated)
|
|
1153
1207
|
* @param {object} [args.taskCtx] — legacy task-context sub-block (optional)
|
|
1154
1208
|
* @param {string} [args.explicitSkillName] — leading /skill:<name> command, if present
|
|
1155
1209
|
* @returns {string}
|
|
1156
1210
|
*/
|
|
1157
|
-
#buildSystemPrompt({ prompt, memoryInjection, vpPersona, activeScope, sessionAnnouncement, projectInstruction, workCenterInstructions, projectDoc, taskCtx, activeTasks, explicitSkillName, resolvedSkillContent = null } = {}) {
|
|
1211
|
+
#buildSystemPrompt({ prompt, memoryInjection, vpPersona, activeScope, sessionAnnouncement, projectInstruction, projectLabel, workCenterInstructions, projectDoc, taskCtx, activeTasks, explicitSkillName, resolvedSkillContent = null } = {}) {
|
|
1158
1212
|
// Skill selection is normally resolved once by #runQuery so the prompt and
|
|
1159
1213
|
// emitted protocol events describe the exact same skills. Keep the local
|
|
1160
1214
|
// fallback for internal callers that do not need selection events.
|
|
@@ -1182,6 +1236,7 @@ export class Engine {
|
|
|
1182
1236
|
activeScope,
|
|
1183
1237
|
sessionAnnouncement,
|
|
1184
1238
|
projectInstruction,
|
|
1239
|
+
projectLabel,
|
|
1185
1240
|
workCenterInstructions,
|
|
1186
1241
|
projectDoc,
|
|
1187
1242
|
runtimePlatform: getRuntimePlatformInfo(),
|
|
@@ -1367,6 +1422,9 @@ export class Engine {
|
|
|
1367
1422
|
projectSessionIds: Array.isArray(vpCtx?.projectSessionIds)
|
|
1368
1423
|
? vpCtx.projectSessionIds.slice()
|
|
1369
1424
|
: [],
|
|
1425
|
+
projectLabel: typeof vpCtx?.projectLabel === 'string'
|
|
1426
|
+
? vpCtx.projectLabel
|
|
1427
|
+
: '',
|
|
1370
1428
|
projectInstruction: typeof vpCtx?.projectInstruction === 'string'
|
|
1371
1429
|
? vpCtx.projectInstruction
|
|
1372
1430
|
: '',
|
|
@@ -1914,7 +1972,7 @@ export class Engine {
|
|
|
1914
1972
|
}
|
|
1915
1973
|
}
|
|
1916
1974
|
|
|
1917
|
-
async *#queryLifecycle({ prompt, promptParts = null, messages = [], signal, userEffort = null, scenario = 'chat', vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers, sessionId, sessionMembers, projectSessionIds = null, projectInstruction = '', sessionTopics = null, vpPlan, sessionAnnouncement, workCenterInstructions, workDir, userAlreadyPersisted = false, getCurrentTodos = null, setCurrentTodos = null, askUser = null, threadId = MAIN_THREAD_ID, vpTurnId = null, drainPendingUserMessages = null, prepareProviderRequest = null, startProviderRequest = null, finishProviderRequest = null, failProviderRequest = null, closePendingUserInput = null, collabToolPolicy = null } = {}) {
|
|
1975
|
+
async *#queryLifecycle({ prompt, promptParts = null, messages = [], signal, userEffort = null, scenario = 'chat', vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers, sessionId, sessionMembers, projectSessionIds = null, projectInstruction = '', projectLabel = '', sessionTopics = null, vpPlan, sessionAnnouncement, workCenterInstructions, workDir, userAlreadyPersisted = false, getCurrentTodos = null, setCurrentTodos = null, askUser = null, threadId = MAIN_THREAD_ID, vpTurnId = null, drainPendingUserMessages = null, prepareProviderRequest = null, startProviderRequest = null, finishProviderRequest = null, failProviderRequest = null, closePendingUserInput = null, collabToolPolicy = null } = {}) {
|
|
1918
1976
|
if (!prompt || typeof prompt !== 'string' || !prompt.trim()) {
|
|
1919
1977
|
const error = new Error('prompt is required and must be a non-empty string');
|
|
1920
1978
|
yield {
|
|
@@ -2000,7 +2058,7 @@ export class Engine {
|
|
|
2000
2058
|
};
|
|
2001
2059
|
try {
|
|
2002
2060
|
this.#currentThreadId = threadId || MAIN_THREAD_ID;
|
|
2003
|
-
yield* this.#runQuery({ prompt: effectivePrompt, promptParts: effectivePromptParts, messages, signal: runSignal, userEffort: effectiveUserEffort, scenario, vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers, sessionId, sessionMembers, projectSessionIds, projectInstruction, sessionTopics, vpPlan, sessionAnnouncement, workCenterInstructions, workDir, userAlreadyPersisted, getCurrentTodos, setCurrentTodos, askUser, threadId: this.#currentThreadId, vpTurnId, drainPendingUserMessages, prepareProviderRequest, startProviderRequest, finishProviderRequest, failProviderRequest, closePendingUserInput, collabToolPolicy: effectiveCollabToolPolicy, explicitSkillName: parsedSkill.skillName, retryLifecycle });
|
|
2061
|
+
yield* this.#runQuery({ prompt: effectivePrompt, promptParts: effectivePromptParts, messages, signal: runSignal, userEffort: effectiveUserEffort, scenario, vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers, sessionId, sessionMembers, projectSessionIds, projectInstruction, projectLabel, sessionTopics, vpPlan, sessionAnnouncement, workCenterInstructions, workDir, userAlreadyPersisted, getCurrentTodos, setCurrentTodos, askUser, threadId: this.#currentThreadId, vpTurnId, drainPendingUserMessages, prepareProviderRequest, startProviderRequest, finishProviderRequest, failProviderRequest, closePendingUserInput, collabToolPolicy: effectiveCollabToolPolicy, explicitSkillName: parsedSkill.skillName, retryLifecycle });
|
|
2004
2062
|
} finally {
|
|
2005
2063
|
// Closing the async generator at a visible retry boundary means the
|
|
2006
2064
|
// continuation never reached a provider. Keep it out of history and
|
|
@@ -2048,7 +2106,7 @@ export class Engine {
|
|
|
2048
2106
|
* in a try/finally without indenting the whole loop.
|
|
2049
2107
|
* @private
|
|
2050
2108
|
*/
|
|
2051
|
-
async *#runQuery({ prompt, promptParts = null, messages, signal, userEffort = null, scenario = 'chat', vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers, sessionId, sessionMembers, projectSessionIds = null, projectInstruction = '', sessionTopics = null, vpPlan, sessionAnnouncement, workCenterInstructions, workDir, userAlreadyPersisted = false, getCurrentTodos = null, setCurrentTodos = null, askUser = null, threadId = MAIN_THREAD_ID, vpTurnId = null, drainPendingUserMessages = null, prepareProviderRequest = null, startProviderRequest = null, finishProviderRequest = null, failProviderRequest = null, closePendingUserInput = null, collabToolPolicy = null, explicitSkillName = null, retryLifecycle }) {
|
|
2109
|
+
async *#runQuery({ prompt, promptParts = null, messages, signal, userEffort = null, scenario = 'chat', vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers, sessionId, sessionMembers, projectSessionIds = null, projectInstruction = '', projectLabel = '', sessionTopics = null, vpPlan, sessionAnnouncement, workCenterInstructions, workDir, userAlreadyPersisted = false, getCurrentTodos = null, setCurrentTodos = null, askUser = null, threadId = MAIN_THREAD_ID, vpTurnId = null, drainPendingUserMessages = null, prepareProviderRequest = null, startProviderRequest = null, finishProviderRequest = null, failProviderRequest = null, closePendingUserInput = null, collabToolPolicy = null, explicitSkillName = null, retryLifecycle }) {
|
|
2052
2110
|
|
|
2053
2111
|
const effectiveCollabToolPolicy = collabToolPolicy === COLLAB_TOOL_POLICY.SINGLE_VP || collabToolPolicy === COLLAB_TOOL_POLICY.MULTI_VP
|
|
2054
2112
|
? collabToolPolicy
|
|
@@ -2146,6 +2204,7 @@ export class Engine {
|
|
|
2146
2204
|
: (typeof senderVpId === 'string' ? senderVpId : undefined),
|
|
2147
2205
|
language: this.#config.language || 'en',
|
|
2148
2206
|
topicScopes: topicScopesForResident,
|
|
2207
|
+
relatedSessionIds: projectSessionIds,
|
|
2149
2208
|
});
|
|
2150
2209
|
|
|
2151
2210
|
// ─── AMS: populate + snapshot ───────────────────────────────
|
|
@@ -2211,7 +2270,9 @@ export class Engine {
|
|
|
2211
2270
|
|
|
2212
2271
|
const projectDoc = this.#getProjectDocBlock(workDir);
|
|
2213
2272
|
const activeTasks = this.#taskManager
|
|
2214
|
-
? this.#taskManager.renderActiveTasksForPrompt(runtimeSessionId
|
|
2273
|
+
? this.#taskManager.renderActiveTasksForPrompt(runtimeSessionId, {
|
|
2274
|
+
language: this.#config.language || 'en',
|
|
2275
|
+
})
|
|
2215
2276
|
: '';
|
|
2216
2277
|
let resolvedSkillContent = '';
|
|
2217
2278
|
let resolvedSkills = [];
|
|
@@ -2247,6 +2308,7 @@ export class Engine {
|
|
|
2247
2308
|
activeScope,
|
|
2248
2309
|
sessionAnnouncement,
|
|
2249
2310
|
projectInstruction,
|
|
2311
|
+
projectLabel,
|
|
2250
2312
|
workCenterInstructions,
|
|
2251
2313
|
projectDoc,
|
|
2252
2314
|
activeTasks,
|
|
@@ -3650,6 +3712,7 @@ export class Engine {
|
|
|
3650
3712
|
sessionId: runtimeSessionId,
|
|
3651
3713
|
projectSessionIds,
|
|
3652
3714
|
projectInstruction,
|
|
3715
|
+
projectLabel,
|
|
3653
3716
|
threadId: runtimeThreadId,
|
|
3654
3717
|
inboundEnvelope,
|
|
3655
3718
|
taskId,
|
package/yeaft/memory/ams.js
CHANGED
|
@@ -29,14 +29,14 @@ const RECENT_DEFAULT_CAPACITY = 64;
|
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* @typedef {object} AmsLayers
|
|
32
|
-
* @property {Map<string, string>}
|
|
32
|
+
* @property {Map<string, { summary: string, category?: string }>} resident
|
|
33
33
|
* @property {Array<{ id: string, seg: import('./segment.js').Segment, ts: number }>} recent
|
|
34
34
|
* @property {Map<string, import('./segment.js').Segment>} onDemand
|
|
35
35
|
*/
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
38
|
* @typedef {object} AmsSnapshot
|
|
39
|
-
* @property {Array<{ scope: string, summary: string }>} resident
|
|
39
|
+
* @property {Array<{ scope: string, summary: string, category?: string }>} resident
|
|
40
40
|
* @property {import('./segment.js').Segment[]} recent
|
|
41
41
|
* @property {import('./segment.js').Segment[]} onDemand
|
|
42
42
|
* @property {{ resident: number, recent: number, onDemand: number, total: number }} usage
|
|
@@ -55,8 +55,8 @@ export class ActiveMemorySet {
|
|
|
55
55
|
this.ownVpId = opts.ownVpId || null;
|
|
56
56
|
this.budget = opts.budget;
|
|
57
57
|
this.recentCapacity = opts.recentCapacity || RECENT_DEFAULT_CAPACITY;
|
|
58
|
-
/** @type {Map<string, string>} */
|
|
59
|
-
this._resident = new Map(); // scope →
|
|
58
|
+
/** @type {Map<string, { summary: string, category?: string }>} */
|
|
59
|
+
this._resident = new Map(); // scope → prompt-facing summary metadata
|
|
60
60
|
/** @type {Map<string, { seg: import('./segment.js').Segment, ts: number }>} */
|
|
61
61
|
this._recent = new Map(); // segId → entry (insertion-order is LRU order)
|
|
62
62
|
/** @type {Map<string, import('./segment.js').Segment>} */
|
|
@@ -69,7 +69,7 @@ export class ActiveMemorySet {
|
|
|
69
69
|
* Replace the resident layer with a fresh set of scope→summary
|
|
70
70
|
* pairs. Foreign VP scopes are silently dropped.
|
|
71
71
|
*
|
|
72
|
-
* @param {Array<{ scope: string, summary: string }>} entries
|
|
72
|
+
* @param {Array<{ scope: string, summary: string, category?: string }>} entries
|
|
73
73
|
*/
|
|
74
74
|
setResident(entries) {
|
|
75
75
|
this._resident.clear();
|
|
@@ -77,7 +77,10 @@ export class ActiveMemorySet {
|
|
|
77
77
|
if (this._isForeignVp(e.scope)) continue;
|
|
78
78
|
const summary = cleanMemoryPromptText(e.summary);
|
|
79
79
|
if (!summary) continue;
|
|
80
|
-
this._resident.set(e.scope,
|
|
80
|
+
this._resident.set(e.scope, {
|
|
81
|
+
summary,
|
|
82
|
+
...(typeof e.category === 'string' && e.category ? { category: e.category } : {}),
|
|
83
|
+
});
|
|
81
84
|
}
|
|
82
85
|
}
|
|
83
86
|
|
|
@@ -159,8 +162,15 @@ export class ActiveMemorySet {
|
|
|
159
162
|
// Resident: pack scopes by priority order (caller provides via insert
|
|
160
163
|
// order — current group's own vp first, then user, etc.).
|
|
161
164
|
const { picked: resPicked, cost: resCost } = pickMemoryItems({
|
|
162
|
-
items: [...this._resident.entries()].map(([scope,
|
|
163
|
-
scope,
|
|
165
|
+
items: [...this._resident.entries()].map(([scope, entry]) => ({
|
|
166
|
+
scope,
|
|
167
|
+
// Related-Session summaries are explicitly historical context. Keep the
|
|
168
|
+
// bounded prose intact and label it as experience instead of dropping
|
|
169
|
+
// the whole paragraph because it mentions an old PR/tag/task state.
|
|
170
|
+
summary: entry.category === 'experience'
|
|
171
|
+
? cleanMemoryPromptText(entry.summary)
|
|
172
|
+
: filterMemoryPromptTextForPrompt(entry.summary, userMsg),
|
|
173
|
+
...(entry.category ? { category: entry.category } : {}),
|
|
164
174
|
})),
|
|
165
175
|
budget: this.budget.resident,
|
|
166
176
|
seen: seenPromptText,
|
package/yeaft/prompts.js
CHANGED
|
@@ -205,7 +205,9 @@ const PROMPTS = {
|
|
|
205
205
|
multiVpRoutingHeader: '## multi_vp_routing',
|
|
206
206
|
sessionAnnouncementHeader: '[Session Announcement]',
|
|
207
207
|
projectInstructionHeader: '[Project Instruction]',
|
|
208
|
-
projectInstructionIntro:
|
|
208
|
+
projectInstructionIntro: (projectLabel) => projectLabel
|
|
209
|
+
? `The current Session belongs to Project ${projectLabel}. The unified instruction for this Project is:`
|
|
210
|
+
: 'The current Session belongs to the current Project. The unified instruction for this Project is:',
|
|
209
211
|
workCenterInstructionsHeader: '[Work Center Agent Instructions]',
|
|
210
212
|
workCenterInstructionsIntro: 'These Agent-level instructions apply to every Action in this WorkItem. Follow them unless they conflict with system/tool safety rules, the authoritative project document, or the WorkItem contract.',
|
|
211
213
|
// Project-doc (CLAUDE.md / AGENTS.md) header + one-liner intro. Both
|
|
@@ -229,7 +231,9 @@ const PROMPTS = {
|
|
|
229
231
|
multiVpRoutingHeader: '## multi_vp_routing',
|
|
230
232
|
sessionAnnouncementHeader: '[会话公告]',
|
|
231
233
|
projectInstructionHeader: '[Project 指令]',
|
|
232
|
-
projectInstructionIntro:
|
|
234
|
+
projectInstructionIntro: (projectLabel) => projectLabel
|
|
235
|
+
? `当前 Session 隶属于 Project ${projectLabel}。当前 Project 的统一 instruction 是:`
|
|
236
|
+
: '当前 Session 隶属于当前 Project。当前 Project 的统一 instruction 是:',
|
|
233
237
|
workCenterInstructionsHeader: '[Work Center Agent 指令]',
|
|
234
238
|
workCenterInstructionsIntro: '这些 Agent 级指令作用于当前 Work Item 的每个 Action。除非与系统/工具安全规则、权威项目文档或 Work Item 契约冲突,否则必须遵循。',
|
|
235
239
|
// 项目文档块:CLAUDE.md / AGENTS.md(与 Codex 通用命名兼容)。
|
|
@@ -302,6 +306,7 @@ export function normalizePromptLanguage(language) {
|
|
|
302
306
|
* vpPersona?: object,
|
|
303
307
|
* sessionAnnouncement?: string,
|
|
304
308
|
* projectInstruction?: string,
|
|
309
|
+
* projectLabel?: string,
|
|
305
310
|
* workCenterInstructions?: string,
|
|
306
311
|
* projectDoc?: string,
|
|
307
312
|
* }} params
|
|
@@ -317,6 +322,7 @@ export function buildSystemPrompt({
|
|
|
317
322
|
vpPersona,
|
|
318
323
|
sessionAnnouncement = '',
|
|
319
324
|
projectInstruction = '',
|
|
325
|
+
projectLabel = '',
|
|
320
326
|
workCenterInstructions = '',
|
|
321
327
|
projectDoc = '',
|
|
322
328
|
runtimePlatform,
|
|
@@ -366,7 +372,10 @@ export function buildSystemPrompt({
|
|
|
366
372
|
const projectInstructionText = typeof projectInstruction === 'string' ? projectInstruction.trim() : '';
|
|
367
373
|
if (projectInstructionText) {
|
|
368
374
|
const header = lang.projectInstructionHeader || '[Project Instruction]';
|
|
369
|
-
const
|
|
375
|
+
const normalizedProjectLabel = typeof projectLabel === 'string' ? projectLabel.trim() : '';
|
|
376
|
+
const intro = typeof lang.projectInstructionIntro === 'function'
|
|
377
|
+
? lang.projectInstructionIntro(normalizedProjectLabel)
|
|
378
|
+
: '';
|
|
370
379
|
parts.push(`${header}\n${intro ? `${intro}\n\n` : ''}${projectInstructionText}`);
|
|
371
380
|
}
|
|
372
381
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @param {object} agent
|
|
8
8
|
* @param {string} prompt
|
|
9
|
-
* @param {{ projectSessionIds?: string[], projectInstruction?: string }} projectContext
|
|
9
|
+
* @param {{ projectSessionIds?: string[], projectLabel?: string, projectInstruction?: string }} projectContext
|
|
10
10
|
*/
|
|
11
11
|
export function enqueueSubAgentPrompt(agent, prompt, projectContext = {}) {
|
|
12
12
|
if (!Array.isArray(agent.pendingPrompts)) agent.pendingPrompts = [];
|
|
@@ -15,6 +15,9 @@ export function enqueueSubAgentPrompt(agent, prompt, projectContext = {}) {
|
|
|
15
15
|
projectSessionIds: Array.isArray(projectContext.projectSessionIds)
|
|
16
16
|
? projectContext.projectSessionIds.slice()
|
|
17
17
|
: [],
|
|
18
|
+
projectLabel: typeof projectContext.projectLabel === 'string'
|
|
19
|
+
? projectContext.projectLabel
|
|
20
|
+
: '',
|
|
18
21
|
projectInstruction: typeof projectContext.projectInstruction === 'string'
|
|
19
22
|
? projectContext.projectInstruction
|
|
20
23
|
: '',
|
|
@@ -125,6 +125,7 @@ export function isRestrictedToolName(name) {
|
|
|
125
125
|
* parentVpId?: string,
|
|
126
126
|
* parentSessionId?: string|null,
|
|
127
127
|
* projectSessionIds?: string[],
|
|
128
|
+
* projectLabel?: string,
|
|
128
129
|
* projectInstruction?: string,
|
|
129
130
|
* parentThreadId?: string|null,
|
|
130
131
|
* parentVpPersona?: object,
|
|
@@ -325,6 +326,9 @@ async function driveSubAgent(agent, subEngine, vpPersona, deps) {
|
|
|
325
326
|
projectSessionIds: Array.isArray(deps.projectSessionIds)
|
|
326
327
|
? deps.projectSessionIds.slice()
|
|
327
328
|
: [],
|
|
329
|
+
projectLabel: typeof deps.projectLabel === 'string'
|
|
330
|
+
? deps.projectLabel
|
|
331
|
+
: '',
|
|
328
332
|
projectInstruction: typeof deps.projectInstruction === 'string'
|
|
329
333
|
? deps.projectInstruction
|
|
330
334
|
: '',
|
|
@@ -336,6 +340,9 @@ async function driveSubAgent(agent, subEngine, vpPersona, deps) {
|
|
|
336
340
|
projectSessionIds: Array.isArray(entry.projectSessionIds)
|
|
337
341
|
? entry.projectSessionIds.slice()
|
|
338
342
|
: [],
|
|
343
|
+
projectLabel: typeof entry.projectLabel === 'string'
|
|
344
|
+
? entry.projectLabel
|
|
345
|
+
: '',
|
|
339
346
|
projectInstruction: typeof entry.projectInstruction === 'string'
|
|
340
347
|
? entry.projectInstruction
|
|
341
348
|
: '',
|
|
@@ -407,6 +414,7 @@ async function driveSubAgent(agent, subEngine, vpPersona, deps) {
|
|
|
407
414
|
vpPersona,
|
|
408
415
|
sessionId: agent.parentSessionId || deps.parentSessionId || null,
|
|
409
416
|
projectSessionIds: queuedPrompt.projectSessionIds,
|
|
417
|
+
projectLabel: queuedPrompt.projectLabel,
|
|
410
418
|
projectInstruction: queuedPrompt.projectInstruction,
|
|
411
419
|
});
|
|
412
420
|
for await (const evt of stream) {
|
package/yeaft/tasks/manager.js
CHANGED
|
@@ -20,6 +20,7 @@ import { getRuntimePlatformInfo } from '../runtime-platform.js';
|
|
|
20
20
|
const LOG_PREVIEW_BYTES = 4096;
|
|
21
21
|
const SUB_AGENT_LOG_PREVIEW_BYTES = 1024 * 1024;
|
|
22
22
|
const DEFAULT_CANCEL_ESCALATION_MS = 2000;
|
|
23
|
+
const PROMPT_TASK_LIMIT = 5;
|
|
23
24
|
|
|
24
25
|
function logPreviewBytesFor(task) {
|
|
25
26
|
return task?.kind === 'sub_agent' ? SUB_AGENT_LOG_PREVIEW_BYTES : LOG_PREVIEW_BYTES;
|
|
@@ -54,9 +55,35 @@ function publicSnapshot(task) {
|
|
|
54
55
|
};
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
function
|
|
58
|
-
const
|
|
59
|
-
|
|
58
|
+
function taskKindLabel(kind, language) {
|
|
59
|
+
const zh = String(language || '').toLowerCase().startsWith('zh');
|
|
60
|
+
if (kind === 'sub_agent') return zh ? '子 Agent' : 'sub-agent';
|
|
61
|
+
if (kind === 'shell') return zh ? '后台命令' : 'background command';
|
|
62
|
+
return zh ? '后台任务' : 'background task';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function safeTaskName(task) {
|
|
66
|
+
const name = typeof task?.runtime?.name === 'string' ? task.runtime.name.trim() : '';
|
|
67
|
+
return /^[\p{L}\p{N}][\p{L}\p{N}._-]{0,63}$/u.test(name) ? name : '';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function promptTaskLabel(task, language) {
|
|
71
|
+
const name = task?.kind === 'sub_agent' ? safeTaskName(task) : '';
|
|
72
|
+
if (!name) return taskKindLabel(task?.kind, language);
|
|
73
|
+
return String(language || '').toLowerCase().startsWith('zh')
|
|
74
|
+
? `子 Agent ${name}`
|
|
75
|
+
: `sub-agent ${name}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function taskStatusLabel(status, language) {
|
|
79
|
+
const zh = String(language || '').toLowerCase().startsWith('zh');
|
|
80
|
+
if (!zh) return String(status || 'running').replace(/_/g, ' ');
|
|
81
|
+
const labels = {
|
|
82
|
+
running: '运行中',
|
|
83
|
+
queued: '等待中',
|
|
84
|
+
cancelling: '正在取消',
|
|
85
|
+
};
|
|
86
|
+
return labels[status] || String(status || '运行中').replace(/_/g, ' ');
|
|
60
87
|
}
|
|
61
88
|
|
|
62
89
|
export class TaskManager {
|
|
@@ -358,17 +385,27 @@ export class TaskManager {
|
|
|
358
385
|
return publicSnapshot(task);
|
|
359
386
|
}
|
|
360
387
|
|
|
361
|
-
renderActiveTasksForPrompt(sessionId = null) {
|
|
388
|
+
renderActiveTasksForPrompt(sessionId = null, { language = 'en', limit = PROMPT_TASK_LIMIT } = {}) {
|
|
362
389
|
const tasks = this.listActiveTasks(sessionId);
|
|
363
390
|
if (tasks.length === 0) return '';
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
391
|
+
const zh = String(language || '').toLowerCase().startsWith('zh');
|
|
392
|
+
const maxTasks = Number.isFinite(limit) && limit > 0 ? Math.floor(limit) : PROMPT_TASK_LIMIT;
|
|
393
|
+
const visible = tasks.slice(0, maxTasks);
|
|
394
|
+
const lines = [zh ? '## 可能相关的任务' : '## Possibly Relevant Tasks'];
|
|
395
|
+
lines.push(zh
|
|
396
|
+
? '以下任务仍在后台运行。需要进度或完整输出时使用任务工具查询;不要把它们当成记忆事实。'
|
|
397
|
+
: 'These tasks are still running in the background. Use the task tools for progress or full output; do not treat them as memory facts.');
|
|
398
|
+
for (const task of visible) {
|
|
399
|
+
const title = promptTaskLabel(task, language);
|
|
400
|
+
const detail = zh
|
|
401
|
+
? `${taskKindLabel(task.kind, language)},${taskStatusLabel(task.status, language)}`
|
|
402
|
+
: `${taskKindLabel(task.kind, language)}, ${taskStatusLabel(task.status, language)}`;
|
|
403
|
+
lines.push(`- ${title} (${detail})`);
|
|
404
|
+
}
|
|
405
|
+
if (tasks.length > visible.length) {
|
|
406
|
+
const remaining = tasks.length - visible.length;
|
|
407
|
+
lines.push(zh ? `- 另有 ${remaining} 个运行中任务,可用任务列表查看。` : `- ${remaining} more running task${remaining === 1 ? '' : 's'}; use the task list to inspect them.`);
|
|
370
408
|
}
|
|
371
|
-
lines.push('</active_tasks>');
|
|
372
409
|
return lines.join('\n');
|
|
373
410
|
}
|
|
374
411
|
}
|
|
@@ -114,6 +114,7 @@ SpawnAgent -> (PromptAgent <-> WaitAgent)+ -> CloseAgent -> 最终回复给用
|
|
|
114
114
|
// driver out of its idle wait and starts a new turn.
|
|
115
115
|
enqueueSubAgentPrompt(agent, message, {
|
|
116
116
|
projectSessionIds: ctx?.parentEngineDeps?.projectSessionIds,
|
|
117
|
+
projectLabel: ctx?.parentEngineDeps?.projectLabel,
|
|
117
118
|
projectInstruction: ctx?.parentEngineDeps?.projectInstruction,
|
|
118
119
|
});
|
|
119
120
|
agent.messages.push({
|
package/yeaft/web-bridge.js
CHANGED
|
@@ -5276,13 +5276,15 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
|
|
|
5276
5276
|
|| legacyProjectContext(ctx.CONFIG?.yeaftDir, sessionId);
|
|
5277
5277
|
const projectSessionIds = projectContext?.sessionIds || [];
|
|
5278
5278
|
queryOpts.projectSessionIds = projectSessionIds;
|
|
5279
|
+
queryOpts.projectLabel = projectContext?.projectName
|
|
5280
|
+
? `${projectContext.projectName} (${projectContext.projectId})`
|
|
5281
|
+
: (projectContext?.projectId || '');
|
|
5279
5282
|
queryOpts.projectInstruction = projectContext?.projectInstruction || '';
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
const sharedBlock = buildProjectSharedBlock(projectContext, projectSummaries);
|
|
5283
|
+
// Related Session summaries now enter through Engine's single AMS
|
|
5284
|
+
// memory outlet. Keep this announcement limited to Project identity and
|
|
5285
|
+
// sharing boundaries so parent VP prompts do not duplicate the same prose
|
|
5286
|
+
// that sub-agents receive through memory.
|
|
5287
|
+
const sharedBlock = buildProjectSharedBlock(projectContext);
|
|
5286
5288
|
if (sharedBlock) {
|
|
5287
5289
|
queryOpts.sessionAnnouncement = queryOpts.sessionAnnouncement
|
|
5288
5290
|
? `${queryOpts.sessionAnnouncement}\n\n${sharedBlock}`
|
|
@@ -6528,6 +6530,9 @@ export function handleYeaftSubAgentPrompt(msg) {
|
|
|
6528
6530
|
|| legacyProjectContext(ctx.CONFIG?.yeaftDir, sessionId);
|
|
6529
6531
|
enqueueSubAgentPrompt(agent, message, {
|
|
6530
6532
|
projectSessionIds: projectContext?.sessionIds,
|
|
6533
|
+
projectLabel: projectContext?.projectName
|
|
6534
|
+
? `${projectContext.projectName} (${projectContext.projectId})`
|
|
6535
|
+
: (projectContext?.projectId || ''),
|
|
6531
6536
|
projectInstruction: projectContext?.projectInstruction,
|
|
6532
6537
|
});
|
|
6533
6538
|
if (!Array.isArray(agent.messages)) agent.messages = [];
|