@yeaft/webchat-agent 1.0.299 → 1.0.300
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/server/database.js +1 -0
- package/local-runtime/server/db/connection.js +86 -0
- package/local-runtime/server/db/yeaft-project-db.js +225 -0
- package/local-runtime/server/handlers/agent-output.js +41 -5
- package/local-runtime/server/handlers/client-conversation.js +73 -0
- package/local-runtime/server/ws-utils.js +5 -1
- package/local-runtime/version.json +1 -1
- package/local-runtime/web/app.bundle.js +73 -78
- 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/conversation/search.js +13 -7
- package/yeaft/engine.js +21 -4
- package/yeaft/sub-agent/runner.js +5 -0
- package/yeaft/tools/history-search.js +12 -3
- package/yeaft/tools/types.js +1 -0
- package/yeaft/web-bridge.js +90 -6
|
Binary file
|
package/package.json
CHANGED
|
@@ -152,7 +152,7 @@ function sessionConversationDirs(dir) {
|
|
|
152
152
|
* @param {string} dir — Yeaft root directory (e.g. ~/.yeaft)
|
|
153
153
|
* @param {string} keyword — search terms
|
|
154
154
|
* @param {number} [limit=10] — max results
|
|
155
|
-
* @param {{telemetry?: {scannedFiles?: number, scannedBytes?: number, scannedMessages?: number}}} [options]
|
|
155
|
+
* @param {{telemetry?: {scannedFiles?: number, scannedBytes?: number, scannedMessages?: number}, sessionIds?: string[]}} [options]
|
|
156
156
|
* @returns {object[]} — matching messages, newest first
|
|
157
157
|
*/
|
|
158
158
|
export function searchMessages(dir, keyword, limit = 10, options = {}) {
|
|
@@ -167,18 +167,24 @@ export function searchMessages(dir, keyword, limit = 10, options = {}) {
|
|
|
167
167
|
telemetry.scannedMessages = 0;
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
170
|
+
const requestedSessionIds = Array.isArray(options.sessionIds)
|
|
171
|
+
? new Set(options.sessionIds.filter(id => typeof id === 'string' && id.trim()).map(id => id.trim()))
|
|
172
|
+
: null;
|
|
173
|
+
const scopedSessionDirs = sessionConversationDirs(dir)
|
|
174
|
+
.filter(source => !requestedSessionIds || requestedSessionIds.has(source.sessionId));
|
|
175
|
+
const conversationDirs = requestedSessionIds
|
|
176
|
+
? scopedSessionDirs
|
|
177
|
+
: [{ dir: join(dir, 'chat'), sessionId: null, kind: 'chat' }, ...scopedSessionDirs];
|
|
174
178
|
|
|
175
179
|
const markdownDirs = [
|
|
176
180
|
...conversationDirs.flatMap(source => [
|
|
177
181
|
{ ...source, dir: join(source.dir, 'messages') },
|
|
178
182
|
{ ...source, dir: join(source.dir, 'cold') },
|
|
179
183
|
]),
|
|
180
|
-
|
|
181
|
-
|
|
184
|
+
...(!requestedSessionIds ? [
|
|
185
|
+
{ dir: join(dir, 'conversation', 'messages'), sessionId: null, kind: 'legacy-conversation' },
|
|
186
|
+
{ dir: join(dir, 'conversation', 'cold'), sessionId: null, kind: 'legacy-conversation' },
|
|
187
|
+
] : []),
|
|
182
188
|
];
|
|
183
189
|
const segmentDirs = conversationDirs.map(source => ({ ...source, dir: join(source.dir, 'segments') }));
|
|
184
190
|
|
package/yeaft/engine.js
CHANGED
|
@@ -1279,6 +1279,9 @@ export class Engine {
|
|
|
1279
1279
|
config: this.#config,
|
|
1280
1280
|
taskManager: this.#taskManager,
|
|
1281
1281
|
sessionId: vpCtx?.sessionId || this.#sessionId || null,
|
|
1282
|
+
projectSessionIds: Array.isArray(vpCtx?.projectSessionIds)
|
|
1283
|
+
? vpCtx.projectSessionIds.slice()
|
|
1284
|
+
: [],
|
|
1282
1285
|
threadId: vpCtx?.threadId || this.#currentThreadId || MAIN_THREAD_ID,
|
|
1283
1286
|
currentVpId: vpCtx?.senderVpId || this.#vpId || null,
|
|
1284
1287
|
// task-704b: per-tool-result hard cap derives from this. Threaded
|
|
@@ -1342,6 +1345,9 @@ export class Engine {
|
|
|
1342
1345
|
parentVpId: vpCtx?.senderVpId || null,
|
|
1343
1346
|
parentVpPersona: vpCtx?.vpPersona || null,
|
|
1344
1347
|
parentSessionId: vpCtx?.sessionId || null,
|
|
1348
|
+
projectSessionIds: Array.isArray(vpCtx?.projectSessionIds)
|
|
1349
|
+
? vpCtx.projectSessionIds.slice()
|
|
1350
|
+
: [],
|
|
1345
1351
|
parentThreadId: vpCtx?.threadId || this.#currentThreadId || MAIN_THREAD_ID,
|
|
1346
1352
|
onEvent: this.#subAgentEventSink || null,
|
|
1347
1353
|
language: this.#config?.language || 'en',
|
|
@@ -1838,7 +1844,7 @@ export class Engine {
|
|
|
1838
1844
|
* string-prompt shape (no regression for existing callers).
|
|
1839
1845
|
* @yields {EngineEvent}
|
|
1840
1846
|
*/
|
|
1841
|
-
async *query({ prompt, promptParts = null, messages = [], signal, userEffort = null, scenario = 'chat', vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers, sessionId, sessionMembers, 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 } = {}) {
|
|
1847
|
+
async *query({ prompt, promptParts = null, messages = [], signal, userEffort = null, scenario = 'chat', vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers, sessionId, sessionMembers, projectSessionIds = null, 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 } = {}) {
|
|
1842
1848
|
if (!prompt || typeof prompt !== 'string' || !prompt.trim()) {
|
|
1843
1849
|
yield {
|
|
1844
1850
|
type: 'error',
|
|
@@ -1915,7 +1921,7 @@ export class Engine {
|
|
|
1915
1921
|
};
|
|
1916
1922
|
try {
|
|
1917
1923
|
this.#currentThreadId = threadId || MAIN_THREAD_ID;
|
|
1918
|
-
yield* this.#runQuery({ prompt: effectivePrompt, promptParts: effectivePromptParts, messages, signal: runSignal, userEffort: effectiveUserEffort, scenario, vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers, sessionId, sessionMembers, 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 });
|
|
1924
|
+
yield* this.#runQuery({ prompt: effectivePrompt, promptParts: effectivePromptParts, messages, signal: runSignal, userEffort: effectiveUserEffort, scenario, vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers, sessionId, sessionMembers, projectSessionIds, 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 });
|
|
1919
1925
|
} finally {
|
|
1920
1926
|
// Closing the async generator at a visible retry boundary means the
|
|
1921
1927
|
// continuation never reached a provider. Keep it out of history and
|
|
@@ -1963,7 +1969,7 @@ export class Engine {
|
|
|
1963
1969
|
* in a try/finally without indenting the whole loop.
|
|
1964
1970
|
* @private
|
|
1965
1971
|
*/
|
|
1966
|
-
async *#runQuery({ prompt, promptParts = null, messages, signal, userEffort = null, scenario = 'chat', vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers, sessionId, sessionMembers, 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 }) {
|
|
1972
|
+
async *#runQuery({ prompt, promptParts = null, messages, signal, userEffort = null, scenario = 'chat', vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers, sessionId, sessionMembers, projectSessionIds = null, 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 }) {
|
|
1967
1973
|
|
|
1968
1974
|
const effectiveCollabToolPolicy = collabToolPolicy === COLLAB_TOOL_POLICY.SINGLE_VP || collabToolPolicy === COLLAB_TOOL_POLICY.MULTI_VP
|
|
1969
1975
|
? collabToolPolicy
|
|
@@ -2022,12 +2028,22 @@ export class Engine {
|
|
|
2022
2028
|
let recallEntryCount = 0;
|
|
2023
2029
|
|
|
2024
2030
|
const topicScopesForMemory = await this.#loadSessionTopicScopes(sessionId);
|
|
2031
|
+
const projectScopesForMemory = Array.isArray(projectSessionIds)
|
|
2032
|
+
? projectSessionIds.flatMap(id => [
|
|
2033
|
+
`sessions/${id}`,
|
|
2034
|
+
`sessions/${id}/user`,
|
|
2035
|
+
`session/${id}`,
|
|
2036
|
+
`session/${id}/user`,
|
|
2037
|
+
`group/${id}`,
|
|
2038
|
+
`group/${id}/user`,
|
|
2039
|
+
])
|
|
2040
|
+
: [];
|
|
2025
2041
|
const recallResult = await this.#recallMemory(prompt, {
|
|
2026
2042
|
sessionId,
|
|
2027
2043
|
vpId: vpPersona && typeof vpPersona === 'object' && typeof vpPersona.vpId === 'string'
|
|
2028
2044
|
? vpPersona.vpId
|
|
2029
2045
|
: (typeof senderVpId === 'string' ? senderVpId : undefined),
|
|
2030
|
-
extraScopes: topicScopesForMemory,
|
|
2046
|
+
extraScopes: [...topicScopesForMemory, ...projectScopesForMemory],
|
|
2031
2047
|
});
|
|
2032
2048
|
recallEntryCount = recallResult && Array.isArray(recallResult.entries)
|
|
2033
2049
|
? recallResult.entries.length
|
|
@@ -3521,6 +3537,7 @@ export class Engine {
|
|
|
3521
3537
|
router,
|
|
3522
3538
|
senderVpId,
|
|
3523
3539
|
sessionId: runtimeSessionId,
|
|
3540
|
+
projectSessionIds,
|
|
3524
3541
|
threadId: runtimeThreadId,
|
|
3525
3542
|
inboundEnvelope,
|
|
3526
3543
|
taskId,
|
|
@@ -123,6 +123,7 @@ export function isRestrictedToolName(name) {
|
|
|
123
123
|
* parentName?: string,
|
|
124
124
|
* parentVpId?: string,
|
|
125
125
|
* parentSessionId?: string|null,
|
|
126
|
+
* projectSessionIds?: string[],
|
|
126
127
|
* parentThreadId?: string|null,
|
|
127
128
|
* parentVpPersona?: object,
|
|
128
129
|
* toolStats?: object,
|
|
@@ -378,6 +379,10 @@ async function driveSubAgent(agent, subEngine, vpPersona, deps) {
|
|
|
378
379
|
signal: agent.abortController?.signal,
|
|
379
380
|
scenario: 'chat',
|
|
380
381
|
vpPersona,
|
|
382
|
+
sessionId: agent.parentSessionId || deps.parentSessionId || null,
|
|
383
|
+
projectSessionIds: Array.isArray(deps.projectSessionIds)
|
|
384
|
+
? deps.projectSessionIds
|
|
385
|
+
: null,
|
|
381
386
|
});
|
|
382
387
|
for await (const evt of stream) {
|
|
383
388
|
// Liveness — update first so even listener throws don't lose
|
|
@@ -138,12 +138,12 @@ export default defineTool({
|
|
|
138
138
|
en: `Search through past conversation history.
|
|
139
139
|
|
|
140
140
|
Searches message content for all whitespace-separated terms (case-insensitive).
|
|
141
|
-
Tool-result messages are excluded. Useful for finding previous discussions, decisions, or code snippets.
|
|
141
|
+
Inside a Session, search is limited to that Session plus sibling Sessions in the same Project on this Agent. Tool-result messages are excluded. Useful for finding previous discussions, decisions, or code snippets.
|
|
142
142
|
|
|
143
143
|
Results are returned newest-first with a bounded matching snippet and source metadata.`,
|
|
144
144
|
zh: `搜索历史对话记录。
|
|
145
145
|
|
|
146
|
-
|
|
146
|
+
在已持久化消息的正文中搜索全部空格分隔的关键词(不区分大小写)。在 Session 内仅搜索当前 Session,以及同一 Agent 上同 Project 的兄弟 Session;排除工具结果消息。用于查找之前的讨论、决策或代码片段。
|
|
147
147
|
|
|
148
148
|
结果按最新优先返回,包含有界的命中片段和来源信息。`
|
|
149
149
|
},
|
|
@@ -180,7 +180,16 @@ Results are returned newest-first with a bounded matching snippet and source met
|
|
|
180
180
|
|
|
181
181
|
try {
|
|
182
182
|
const telemetry = {};
|
|
183
|
-
const
|
|
183
|
+
const projectSessionIds = Array.isArray(ctx?.projectSessionIds)
|
|
184
|
+
? ctx.projectSessionIds.filter(id => typeof id === 'string' && id.trim()).map(id => id.trim())
|
|
185
|
+
: [];
|
|
186
|
+
const scopedSessionIds = ctx?.sessionId
|
|
187
|
+
? Array.from(new Set([ctx.sessionId, ...projectSessionIds]))
|
|
188
|
+
: null;
|
|
189
|
+
const results = searchMessages(yeaftDir, keyword, limit, {
|
|
190
|
+
telemetry,
|
|
191
|
+
...(scopedSessionIds ? { sessionIds: scopedSessionIds } : {}),
|
|
192
|
+
});
|
|
184
193
|
const searchTelemetry = {
|
|
185
194
|
resultCount: results.length,
|
|
186
195
|
scannedFiles: telemetry.scannedFiles || 0,
|
package/yeaft/tools/types.js
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
* @property {object} [config] — engine config
|
|
24
24
|
* @property {import('../tasks/manager.js').TaskManager} [taskManager] — Session task manager
|
|
25
25
|
* @property {string} [sessionId] — current Session id
|
|
26
|
+
* @property {string[]} [projectSessionIds] — same-Agent sibling Session ids in the current Project
|
|
26
27
|
* @property {string} [threadId] — current Session thread id
|
|
27
28
|
* @property {string} [currentVpId] — R6: VP id of the caller (set in multi-VP groups)
|
|
28
29
|
* @property {string} [currentGroupId] — R6: group id of the caller's RoleInstance
|
package/yeaft/web-bridge.js
CHANGED
|
@@ -589,6 +589,54 @@ const vpCurrentTodos = new Map();
|
|
|
589
589
|
* sessionHandle: object }>}
|
|
590
590
|
*/
|
|
591
591
|
const sessionContexts = new Map();
|
|
592
|
+
/** Latest server-authoritative Project identity and same-Agent siblings per Session. */
|
|
593
|
+
const projectContextBySession = new Map();
|
|
594
|
+
|
|
595
|
+
function normalizeProjectContext(value, sessionId) {
|
|
596
|
+
if (!value || typeof value !== 'object' || !Array.isArray(value.sessionIds)) return null;
|
|
597
|
+
const currentSessionId = typeof sessionId === 'string' ? sessionId.trim() : '';
|
|
598
|
+
return {
|
|
599
|
+
projectId: typeof value.projectId === 'string' && value.projectId.trim()
|
|
600
|
+
? value.projectId.trim()
|
|
601
|
+
: null,
|
|
602
|
+
projectName: typeof value.projectName === 'string' && value.projectName.trim()
|
|
603
|
+
? value.projectName.trim()
|
|
604
|
+
: null,
|
|
605
|
+
sessionIds: Array.from(new Set(value.sessionIds
|
|
606
|
+
.filter(id => typeof id === 'string' && id.trim())
|
|
607
|
+
.map(id => id.trim())
|
|
608
|
+
.filter(id => id !== currentSessionId))),
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function legacyProjectContext(yeaftDir, sessionId) {
|
|
613
|
+
const project = loadProjects(yeaftDir).find(row => row.sessionIds.includes(sessionId));
|
|
614
|
+
if (!project) return null;
|
|
615
|
+
return normalizeProjectContext({
|
|
616
|
+
projectId: project.id,
|
|
617
|
+
projectName: project.name,
|
|
618
|
+
sessionIds: project.sessionIds,
|
|
619
|
+
}, sessionId);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function buildProjectSharedBlock(projectContext, summaries = '') {
|
|
623
|
+
const context = normalizeProjectContext(projectContext, null);
|
|
624
|
+
const body = typeof summaries === 'string' ? summaries.trim() : '';
|
|
625
|
+
if (!context?.projectId && !body) return '';
|
|
626
|
+
const lines = ['[Project Shared Context]'];
|
|
627
|
+
if (context?.projectId) {
|
|
628
|
+
const label = context.projectName
|
|
629
|
+
? `${context.projectName} (${context.projectId})`
|
|
630
|
+
: context.projectId;
|
|
631
|
+
lines.push(`Project: ${label}`);
|
|
632
|
+
lines.push('Sharing boundary: sibling Sessions in this Project on this Agent only.');
|
|
633
|
+
} else {
|
|
634
|
+
lines.push('Sharing boundary: sibling Sessions in the same Project on this Agent only.');
|
|
635
|
+
}
|
|
636
|
+
lines.push('Read-only memory summaries preserve each source Session identity.');
|
|
637
|
+
if (body) lines.push('', body);
|
|
638
|
+
return lines.join('\n');
|
|
639
|
+
}
|
|
592
640
|
|
|
593
641
|
function vpKey(sessionId, vpId) {
|
|
594
642
|
return `${sessionId}::${vpId}`;
|
|
@@ -2297,6 +2345,7 @@ export async function __testResetVpState() {
|
|
|
2297
2345
|
asyncTaskOwners.clear();
|
|
2298
2346
|
vpAborts.clear();
|
|
2299
2347
|
sessionContexts.clear();
|
|
2348
|
+
projectContextBySession.clear();
|
|
2300
2349
|
vpCurrentTodos.clear();
|
|
2301
2350
|
threadClassifier = defaultClassifyThread;
|
|
2302
2351
|
if (_vpUnsubscribe) {
|
|
@@ -3028,8 +3077,14 @@ const PROJECT_CONTEXT_MAX_TOKENS = 4096;
|
|
|
3028
3077
|
const PROJECT_CONTEXT_TRUNCATION_NOTICE = '\n[Summary truncated to Project context budget]';
|
|
3029
3078
|
|
|
3030
3079
|
async function sharedProjectContext(yeaftDir, sessionId, options = {}) {
|
|
3031
|
-
const
|
|
3032
|
-
|
|
3080
|
+
const requestedSiblingIds = Array.isArray(options.sessionIds)
|
|
3081
|
+
? options.sessionIds.filter(id => typeof id === 'string' && id.trim()).map(id => id.trim())
|
|
3082
|
+
: null;
|
|
3083
|
+
const project = requestedSiblingIds === null
|
|
3084
|
+
? loadProjects(yeaftDir).find(row => row.sessionIds.includes(sessionId))
|
|
3085
|
+
: null;
|
|
3086
|
+
const sourceSessionIds = requestedSiblingIds || project?.sessionIds || [];
|
|
3087
|
+
if (sourceSessionIds.length === 0) return '';
|
|
3033
3088
|
const memoryRoot = join(yeaftDir, 'memory');
|
|
3034
3089
|
const language = options.language || 'en';
|
|
3035
3090
|
const configuredBudget = Number.isFinite(options.tokenBudget) && options.tokenBudget > 0
|
|
@@ -3037,7 +3092,7 @@ async function sharedProjectContext(yeaftDir, sessionId, options = {}) {
|
|
|
3037
3092
|
: PROJECT_CONTEXT_MAX_TOKENS;
|
|
3038
3093
|
const tokenBudget = Math.min(configuredBudget, PROJECT_CONTEXT_MAX_TOKENS);
|
|
3039
3094
|
let context = '';
|
|
3040
|
-
const siblingIds =
|
|
3095
|
+
const siblingIds = sourceSessionIds
|
|
3041
3096
|
.filter(id => id !== sessionId)
|
|
3042
3097
|
.slice(0, PROJECT_CONTEXT_MAX_SIBLINGS);
|
|
3043
3098
|
for (const siblingId of siblingIds) {
|
|
@@ -3341,6 +3396,7 @@ export function handleYeaftArchiveSession(msg) {
|
|
|
3341
3396
|
try {
|
|
3342
3397
|
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
3343
3398
|
const result = archiveSession(yeaftDir, sessionId);
|
|
3399
|
+
projectContextBySession.delete(sessionId);
|
|
3344
3400
|
invalidateGroupContext(sessionId);
|
|
3345
3401
|
sendSessionCrudResult({
|
|
3346
3402
|
op: 'archive',
|
|
@@ -3362,6 +3418,7 @@ export function handleYeaftDeleteSession(msg) {
|
|
|
3362
3418
|
try {
|
|
3363
3419
|
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
3364
3420
|
const result = deleteSession(yeaftDir, sessionId);
|
|
3421
|
+
projectContextBySession.delete(sessionId);
|
|
3365
3422
|
removeSessionFromProjects(yeaftDir, sessionId);
|
|
3366
3423
|
ctx.assetOutbox?.removeSession(sessionId);
|
|
3367
3424
|
// Cascade: remove every persisted message stamped with this group id.
|
|
@@ -4495,6 +4552,20 @@ async function runYeaftSessionSend(msg) {
|
|
|
4495
4552
|
},
|
|
4496
4553
|
});
|
|
4497
4554
|
|
|
4555
|
+
const hasInboundProjectContext = Object.prototype.hasOwnProperty.call(msg, 'projectContext');
|
|
4556
|
+
const inboundProjectContext = normalizeProjectContext(msg.projectContext, sessionId);
|
|
4557
|
+
if (hasInboundProjectContext) {
|
|
4558
|
+
projectContextBySession.set(sessionId, inboundProjectContext || {
|
|
4559
|
+
projectId: null,
|
|
4560
|
+
projectName: null,
|
|
4561
|
+
sessionIds: [],
|
|
4562
|
+
});
|
|
4563
|
+
} else {
|
|
4564
|
+
// An old Server does not know this field. Drop any context cached from a
|
|
4565
|
+
// newer Server before falling back to this Agent's legacy projects.json.
|
|
4566
|
+
projectContextBySession.delete(sessionId);
|
|
4567
|
+
}
|
|
4568
|
+
|
|
4498
4569
|
// Ingest user text. The coordinator persists, applies mention/fanout
|
|
4499
4570
|
// rules, and calls deliver() (== enqueueForVp) for each chosen VP —
|
|
4500
4571
|
// which both (a) emits vp_typing_start and (b) ensures a driver runs.
|
|
@@ -4519,6 +4590,9 @@ async function runYeaftSessionSend(msg) {
|
|
|
4519
4590
|
_promptParts: attachmentBundle.promptParts,
|
|
4520
4591
|
_promptSuffix: attachmentBundle.promptSuffix,
|
|
4521
4592
|
_perfTraceId: perfTraceId,
|
|
4593
|
+
_projectContext: msg.projectContext && typeof msg.projectContext === 'object'
|
|
4594
|
+
? msg.projectContext
|
|
4595
|
+
: null,
|
|
4522
4596
|
});
|
|
4523
4597
|
} catch (err) {
|
|
4524
4598
|
console.warn('[Yeaft] yeaft_session_chat: coord.ingest failed', err?.message || err);
|
|
@@ -5159,12 +5233,19 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
|
|
|
5159
5233
|
threadId,
|
|
5160
5234
|
});
|
|
5161
5235
|
if (queryOpts) {
|
|
5162
|
-
const
|
|
5236
|
+
const envelopeProjectContext = normalizeProjectContext(inboundEnvelope?._projectContext, sessionId);
|
|
5237
|
+
const projectContext = envelopeProjectContext
|
|
5238
|
+
|| projectContextBySession.get(sessionId)
|
|
5239
|
+
|| legacyProjectContext(ctx.CONFIG?.yeaftDir, sessionId);
|
|
5240
|
+
const projectSessionIds = projectContext?.sessionIds || [];
|
|
5241
|
+
queryOpts.projectSessionIds = projectSessionIds;
|
|
5242
|
+
const projectSummaries = await sharedProjectContext(ctx.CONFIG?.yeaftDir, sessionId, {
|
|
5243
|
+
sessionIds: projectSessionIds,
|
|
5163
5244
|
language: session?.config?.language,
|
|
5164
5245
|
tokenBudget: Math.max(512, Math.floor((session?.config?.messageTokenBudget || 32768) / 8)),
|
|
5165
5246
|
});
|
|
5166
|
-
|
|
5167
|
-
|
|
5247
|
+
const sharedBlock = buildProjectSharedBlock(projectContext, projectSummaries);
|
|
5248
|
+
if (sharedBlock) {
|
|
5168
5249
|
queryOpts.sessionAnnouncement = queryOpts.sessionAnnouncement
|
|
5169
5250
|
? `${queryOpts.sessionAnnouncement}\n\n${sharedBlock}`
|
|
5170
5251
|
: sharedBlock;
|
|
@@ -7007,6 +7088,7 @@ export async function resetYeaftSession() {
|
|
|
7007
7088
|
vpEngineConfigKeys.clear();
|
|
7008
7089
|
asyncTaskOwners.clear();
|
|
7009
7090
|
sessionContexts.clear();
|
|
7091
|
+
projectContextBySession.clear();
|
|
7010
7092
|
vpCurrentTodos.clear();
|
|
7011
7093
|
threadClassifier = defaultClassifyThread;
|
|
7012
7094
|
// History-dedup cache is keyed by per-session coordinator msg ids;
|
|
@@ -7289,6 +7371,8 @@ export async function handleYeaftMcpReload(msg = {}) {
|
|
|
7289
7371
|
export const __testHooks = {
|
|
7290
7372
|
loadProjects,
|
|
7291
7373
|
sharedProjectContext,
|
|
7374
|
+
buildProjectSharedBlock,
|
|
7375
|
+
normalizeProjectContext,
|
|
7292
7376
|
loadVisibleGroupHistoryPage,
|
|
7293
7377
|
projectVisibleHistoryChunkMessages,
|
|
7294
7378
|
persistInboundMessageOnceByMsgId,
|