@yeaft/webchat-agent 1.0.308 → 1.0.310

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "1.0.308",
3
+ "version": "1.0.310",
4
4
  "description": "Remote worker agent for Yeaft Web Code Agent — connects the native Yeaft engine, CLI providers, and workbench tools",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/yeaft/engine.js CHANGED
@@ -1147,13 +1147,14 @@ export class Engine {
1147
1147
  * @param {object} [args.vpPersona]
1148
1148
  * @param {object} [args.activeScope] — DESIGN-PROMPT §3 ④ structured scope summary
1149
1149
  * @param {string} [args.sessionAnnouncement]
1150
+ * @param {string} [args.projectInstruction] — server-managed instruction shared by Project Sessions
1150
1151
  * @param {string} [args.workCenterInstructions] — frozen Agent-level Work Center policy
1151
1152
  * @param {string} [args.projectDoc] — resolved CLAUDE.md / AGENTS.md text (already truncated)
1152
1153
  * @param {object} [args.taskCtx] — legacy task-context sub-block (optional)
1153
1154
  * @param {string} [args.explicitSkillName] — leading /skill:<name> command, if present
1154
1155
  * @returns {string}
1155
1156
  */
1156
- #buildSystemPrompt({ prompt, memoryInjection, vpPersona, activeScope, sessionAnnouncement, workCenterInstructions, projectDoc, taskCtx, activeTasks, explicitSkillName, resolvedSkillContent = null } = {}) {
1157
+ #buildSystemPrompt({ prompt, memoryInjection, vpPersona, activeScope, sessionAnnouncement, projectInstruction, workCenterInstructions, projectDoc, taskCtx, activeTasks, explicitSkillName, resolvedSkillContent = null } = {}) {
1157
1158
  // Skill selection is normally resolved once by #runQuery so the prompt and
1158
1159
  // emitted protocol events describe the exact same skills. Keep the local
1159
1160
  // fallback for internal callers that do not need selection events.
@@ -1180,6 +1181,7 @@ export class Engine {
1180
1181
  vpPersona,
1181
1182
  activeScope,
1182
1183
  sessionAnnouncement,
1184
+ projectInstruction,
1183
1185
  workCenterInstructions,
1184
1186
  projectDoc,
1185
1187
  runtimePlatform: getRuntimePlatformInfo(),
@@ -1351,6 +1353,7 @@ export class Engine {
1351
1353
  adapter: this.#adapter,
1352
1354
  trace: this.#trace,
1353
1355
  config: this.#config,
1356
+ memoryIndex: this.#memoryIndex,
1354
1357
  parentToolRegistry: this.#toolRegistry,
1355
1358
  skillManager: this.#skillManager,
1356
1359
  mcpManager: this.#mcpManager,
@@ -1363,6 +1366,9 @@ export class Engine {
1363
1366
  projectSessionIds: Array.isArray(vpCtx?.projectSessionIds)
1364
1367
  ? vpCtx.projectSessionIds.slice()
1365
1368
  : [],
1369
+ projectInstruction: typeof vpCtx?.projectInstruction === 'string'
1370
+ ? vpCtx.projectInstruction
1371
+ : '',
1366
1372
  parentThreadId: vpCtx?.threadId || this.#currentThreadId || MAIN_THREAD_ID,
1367
1373
  onEvent: this.#subAgentEventSink || null,
1368
1374
  language: this.#config?.language || 'en',
@@ -1859,7 +1865,7 @@ export class Engine {
1859
1865
  * string-prompt shape (no regression for existing callers).
1860
1866
  * @yields {EngineEvent}
1861
1867
  */
1862
- 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 } = {}) {
1868
+ async *query({ 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 } = {}) {
1863
1869
  if (!prompt || typeof prompt !== 'string' || !prompt.trim()) {
1864
1870
  yield {
1865
1871
  type: 'error',
@@ -1936,7 +1942,7 @@ export class Engine {
1936
1942
  };
1937
1943
  try {
1938
1944
  this.#currentThreadId = threadId || MAIN_THREAD_ID;
1939
- 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 });
1945
+ 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 });
1940
1946
  } finally {
1941
1947
  // Closing the async generator at a visible retry boundary means the
1942
1948
  // continuation never reached a provider. Keep it out of history and
@@ -1984,7 +1990,7 @@ export class Engine {
1984
1990
  * in a try/finally without indenting the whole loop.
1985
1991
  * @private
1986
1992
  */
1987
- 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 }) {
1993
+ 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 }) {
1988
1994
 
1989
1995
  const effectiveCollabToolPolicy = collabToolPolicy === COLLAB_TOOL_POLICY.SINGLE_VP || collabToolPolicy === COLLAB_TOOL_POLICY.MULTI_VP
1990
1996
  ? collabToolPolicy
@@ -2182,6 +2188,7 @@ export class Engine {
2182
2188
  vpPersona,
2183
2189
  activeScope,
2184
2190
  sessionAnnouncement,
2191
+ projectInstruction,
2185
2192
  workCenterInstructions,
2186
2193
  projectDoc,
2187
2194
  activeTasks,
@@ -3582,6 +3589,7 @@ export class Engine {
3582
3589
  senderVpId,
3583
3590
  sessionId: runtimeSessionId,
3584
3591
  projectSessionIds,
3592
+ projectInstruction,
3585
3593
  threadId: runtimeThreadId,
3586
3594
  inboundEnvelope,
3587
3595
  taskId,
@@ -37,6 +37,7 @@ function normalizeProject(row, index = 0) {
37
37
  return {
38
38
  id: row.id,
39
39
  name,
40
+ instruction: typeof row.instruction === 'string' ? row.instruction.trim() : '',
40
41
  sessionIds: normalizeSessionIds(row.sessionIds),
41
42
  sortOrder: Number.isFinite(row.sortOrder) ? row.sortOrder : index,
42
43
  createdAt: typeof row.createdAt === 'string' ? row.createdAt : '',
@@ -92,6 +93,7 @@ export function createProject(yeaftDir, name) {
92
93
  const project = {
93
94
  id: `project-${randomUUID().slice(0, 8)}`,
94
95
  name: requireName(name),
96
+ instruction: '',
95
97
  sessionIds: [],
96
98
  sortOrder: projects.length,
97
99
  createdAt: now,
@@ -110,6 +112,22 @@ export function renameProject(yeaftDir, projectId, name) {
110
112
  return project;
111
113
  }
112
114
 
115
+ export function updateProjectInstruction(yeaftDir, projectId, instruction) {
116
+ if (typeof instruction !== 'string') {
117
+ throw new ProjectStoreError('invalid_instruction', 'Project instruction must be a string');
118
+ }
119
+ const nextInstruction = instruction.trim();
120
+ if (nextInstruction.length > 20_000) {
121
+ throw new ProjectStoreError('instruction_too_long', 'Project instruction must not exceed 20000 characters');
122
+ }
123
+ const projects = loadProjects(yeaftDir);
124
+ const project = requireProject(projects, projectId);
125
+ project.instruction = nextInstruction;
126
+ project.updatedAt = new Date().toISOString();
127
+ saveProjects(yeaftDir, projects);
128
+ return project;
129
+ }
130
+
113
131
  export function deleteProject(yeaftDir, projectId) {
114
132
  const projects = loadProjects(yeaftDir);
115
133
  requireProject(projects, projectId);
package/yeaft/prompts.js CHANGED
@@ -204,6 +204,8 @@ const PROMPTS = {
204
204
  activeScopeEnvelopeLabel: 'Handoff',
205
205
  multiVpRoutingHeader: '## multi_vp_routing',
206
206
  sessionAnnouncementHeader: '[Session Announcement]',
207
+ projectInstructionHeader: '[Project Instruction]',
208
+ projectInstructionIntro: 'This server-managed instruction applies to every Session in the current Project.',
207
209
  workCenterInstructionsHeader: '[Work Center Agent Instructions]',
208
210
  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.',
209
211
  // Project-doc (CLAUDE.md / AGENTS.md) header + one-liner intro. Both
@@ -226,6 +228,8 @@ const PROMPTS = {
226
228
  activeScopeEnvelopeLabel: '转交消息',
227
229
  multiVpRoutingHeader: '## multi_vp_routing',
228
230
  sessionAnnouncementHeader: '[会话公告]',
231
+ projectInstructionHeader: '[Project 指令]',
232
+ projectInstructionIntro: '这是服务器管理的 Project 级指令,适用于当前 Project 内的所有 Session。',
229
233
  workCenterInstructionsHeader: '[Work Center Agent 指令]',
230
234
  workCenterInstructionsIntro: '这些 Agent 级指令作用于当前 Work Item 的每个 Action。除非与系统/工具安全规则、权威项目文档或 Work Item 契约冲突,否则必须遵循。',
231
235
  // 项目文档块:CLAUDE.md / AGENTS.md(与 Codex 通用命名兼容)。
@@ -297,6 +301,7 @@ export function normalizePromptLanguage(language) {
297
301
  * activeScope?: object,
298
302
  * vpPersona?: object,
299
303
  * sessionAnnouncement?: string,
304
+ * projectInstruction?: string,
300
305
  * workCenterInstructions?: string,
301
306
  * projectDoc?: string,
302
307
  * }} params
@@ -311,6 +316,7 @@ export function buildSystemPrompt({
311
316
  activeScope,
312
317
  vpPersona,
313
318
  sessionAnnouncement = '',
319
+ projectInstruction = '',
314
320
  workCenterInstructions = '',
315
321
  projectDoc = '',
316
322
  runtimePlatform,
@@ -357,6 +363,13 @@ export function buildSystemPrompt({
357
363
  parts.push(`${docHeader}\n${introLine}${docText}`);
358
364
  }
359
365
 
366
+ const projectInstructionText = typeof projectInstruction === 'string' ? projectInstruction.trim() : '';
367
+ if (projectInstructionText) {
368
+ const header = lang.projectInstructionHeader || '[Project Instruction]';
369
+ const intro = lang.projectInstructionIntro || '';
370
+ parts.push(`${header}\n${intro ? `${intro}\n\n` : ''}${projectInstructionText}`);
371
+ }
372
+
360
373
  // ─── 1.5 Session Announcement (CLAUDE.md-style shared prefix) ───
361
374
  // When a session has set an announcement, every VP in the session sees it
362
375
  // near the top of the system prompt — before tools, memory, mode-specific
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Queue a sub-agent continuation with the Project context that is authoritative
3
+ * for the parent turn which submitted it. Keeping the snapshot on the queue
4
+ * entry preserves ordering when multiple continuations are submitted before
5
+ * the child consumes them. Empty values are an explicit Project-context clear.
6
+ *
7
+ * @param {object} agent
8
+ * @param {string} prompt
9
+ * @param {{ projectSessionIds?: string[], projectInstruction?: string }} projectContext
10
+ */
11
+ export function enqueueSubAgentPrompt(agent, prompt, projectContext = {}) {
12
+ if (!Array.isArray(agent.pendingPrompts)) agent.pendingPrompts = [];
13
+ agent.pendingPrompts.push({
14
+ prompt,
15
+ projectSessionIds: Array.isArray(projectContext.projectSessionIds)
16
+ ? projectContext.projectSessionIds.slice()
17
+ : [],
18
+ projectInstruction: typeof projectContext.projectInstruction === 'string'
19
+ ? projectContext.projectInstruction
20
+ : '',
21
+ });
22
+ }
@@ -114,6 +114,7 @@ export function isRestrictedToolName(name) {
114
114
  * trace: object,
115
115
  * config: object,
116
116
  * conversationStore?: object,
117
+ * memoryIndex?: object,
117
118
  * memoryStore?: object,
118
119
  * memoryShardStore?: object,
119
120
  * parentToolRegistry?: ToolRegistry,
@@ -124,6 +125,7 @@ export function isRestrictedToolName(name) {
124
125
  * parentVpId?: string,
125
126
  * parentSessionId?: string|null,
126
127
  * projectSessionIds?: string[],
128
+ * projectInstruction?: string,
127
129
  * parentThreadId?: string|null,
128
130
  * parentVpPersona?: object,
129
131
  * toolStats?: object,
@@ -152,6 +154,7 @@ export function startSubAgent(agent, deps = {}) {
152
154
  trace: deps.trace,
153
155
  config: { ...deps.config, _readOnly: true },
154
156
  conversationStore: null,
157
+ memoryIndex: deps.memoryIndex || null,
155
158
  memoryStore: deps.memoryStore || null,
156
159
  memoryShardStore: deps.memoryShardStore || null,
157
160
  toolRegistry: childRegistry,
@@ -314,7 +317,29 @@ async function driveSubAgent(agent, subEngine, vpPersona, deps) {
314
317
 
315
318
  const dequeueNextUserPrompt = () => {
316
319
  if (!Array.isArray(agent.pendingPrompts)) agent.pendingPrompts = [];
317
- return agent.pendingPrompts.shift() || null;
320
+ const entry = agent.pendingPrompts.shift();
321
+ if (!entry) return null;
322
+ if (typeof entry === 'string') {
323
+ return {
324
+ prompt: entry,
325
+ projectSessionIds: Array.isArray(deps.projectSessionIds)
326
+ ? deps.projectSessionIds.slice()
327
+ : [],
328
+ projectInstruction: typeof deps.projectInstruction === 'string'
329
+ ? deps.projectInstruction
330
+ : '',
331
+ };
332
+ }
333
+ if (!entry || typeof entry !== 'object' || typeof entry.prompt !== 'string') return null;
334
+ return {
335
+ prompt: entry.prompt,
336
+ projectSessionIds: Array.isArray(entry.projectSessionIds)
337
+ ? entry.projectSessionIds.slice()
338
+ : [],
339
+ projectInstruction: typeof entry.projectInstruction === 'string'
340
+ ? entry.projectInstruction
341
+ : '',
342
+ };
318
343
  };
319
344
 
320
345
  try {
@@ -329,8 +354,8 @@ async function driveSubAgent(agent, subEngine, vpPersona, deps) {
329
354
  emit({ type: 'sub_agent_status', status: STATUS.RUNNING });
330
355
 
331
356
  while (!isTerminalAgentStatus(agent.status)) {
332
- const prompt = dequeueNextUserPrompt();
333
- if (!prompt) {
357
+ const queuedPrompt = dequeueNextUserPrompt();
358
+ if (!queuedPrompt) {
334
359
  // No queued work — go idle and wait for PromptAgent / CloseAgent /
335
360
  // watchdog.
336
361
  agent.status = STATUS.IDLE;
@@ -375,15 +400,14 @@ async function driveSubAgent(agent, subEngine, vpPersona, deps) {
375
400
  let turnUsageTokens = 0;
376
401
  try {
377
402
  const stream = subEngine.query({
378
- prompt,
403
+ prompt: queuedPrompt.prompt,
379
404
  messages: agent.engineMessages,
380
405
  signal: agent.abortController?.signal,
381
406
  scenario: 'chat',
382
407
  vpPersona,
383
408
  sessionId: agent.parentSessionId || deps.parentSessionId || null,
384
- projectSessionIds: Array.isArray(deps.projectSessionIds)
385
- ? deps.projectSessionIds
386
- : null,
409
+ projectSessionIds: queuedPrompt.projectSessionIds,
410
+ projectInstruction: queuedPrompt.projectInstruction,
387
411
  });
388
412
  for await (const evt of stream) {
389
413
  // Liveness — update first so even listener throws don't lose
@@ -446,7 +470,7 @@ async function driveSubAgent(agent, subEngine, vpPersona, deps) {
446
470
 
447
471
  // Persist the turn into the local message buffer so subsequent
448
472
  // PromptAgent continuations see context.
449
- agent.engineMessages.push({ role: 'user', content: prompt });
473
+ agent.engineMessages.push({ role: 'user', content: queuedPrompt.prompt });
450
474
  if (assistantText) {
451
475
  agent.engineMessages.push({ role: 'assistant', content: assistantText });
452
476
  }
@@ -7,6 +7,7 @@
7
7
 
8
8
  import { defineTool } from './types.js';
9
9
  import { agentBelongsToCaller, getAgentRegistry } from './agent.js';
10
+ import { enqueueSubAgentPrompt } from '../sub-agent/prompt-queue.js';
10
11
  import { isTerminalAgentStatus, isPromptableAgentStatus, STATUS, describeAgentStatus } from '../sub-agent/status.js';
11
12
 
12
13
  export default defineTool({
@@ -111,8 +112,10 @@ SpawnAgent -> (PromptAgent <-> WaitAgent)+ -> CloseAgent -> 最终回复给用
111
112
 
112
113
  // Queue as a pending prompt the driver will pull. This wakes the
113
114
  // driver out of its idle wait and starts a new turn.
114
- if (!Array.isArray(agent.pendingPrompts)) agent.pendingPrompts = [];
115
- agent.pendingPrompts.push(message);
115
+ enqueueSubAgentPrompt(agent, message, {
116
+ projectSessionIds: ctx?.parentEngineDeps?.projectSessionIds,
117
+ projectInstruction: ctx?.parentEngineDeps?.projectInstruction,
118
+ });
116
119
  agent.messages.push({
117
120
  role: 'user',
118
121
  content: message,
@@ -72,6 +72,7 @@ import {
72
72
  moveSessionToProject,
73
73
  removeSessionFromProjects,
74
74
  renameProject,
75
+ updateProjectInstruction,
75
76
  } from './projects/store.js';
76
77
  import { readSummary as readScopeSummary } from './memory/store.js';
77
78
  import { estimateTokens } from './dream/segment.js';
@@ -84,6 +85,7 @@ import { classifyThread as defaultClassifyThread, fallbackTitle } from './vp/thr
84
85
  import { listMcpServers, upsertMcpServer, removeMcpServer } from './config-api.js';
85
86
  import { buildMcpFlattenedTools } from './tools/mcp-tools.js';
86
87
  import { getAgentRegistry, agentBelongsToScope } from './tools/agent.js';
88
+ import { enqueueSubAgentPrompt } from './sub-agent/prompt-queue.js';
87
89
  import { isPromptableAgentStatus } from './sub-agent/status.js';
88
90
  import { perfNowMs, recordAgentPerfTrace } from './perf-trace.js';
89
91
  import { recordAgentSessionCreated, recordAgentTurn } from '../metrics.js';
@@ -602,6 +604,9 @@ function normalizeProjectContext(value, sessionId) {
602
604
  projectName: typeof value.projectName === 'string' && value.projectName.trim()
603
605
  ? value.projectName.trim()
604
606
  : null,
607
+ projectInstruction: typeof value.projectInstruction === 'string'
608
+ ? value.projectInstruction.trim()
609
+ : '',
605
610
  sessionIds: Array.from(new Set(value.sessionIds
606
611
  .filter(id => typeof id === 'string' && id.trim())
607
612
  .map(id => id.trim())
@@ -615,6 +620,7 @@ function legacyProjectContext(yeaftDir, sessionId) {
615
620
  return normalizeProjectContext({
616
621
  projectId: project.id,
617
622
  projectName: project.name,
623
+ projectInstruction: project.instruction || '',
618
624
  sessionIds: project.sessionIds,
619
625
  }, sessionId);
620
626
  }
@@ -3198,6 +3204,20 @@ export function handleYeaftListSessions(msg) {
3198
3204
  }
3199
3205
  }
3200
3206
 
3207
+ export function handleYeaftProjectContextSync(msg) {
3208
+ for (const row of Array.isArray(msg?.contexts) ? msg.contexts : []) {
3209
+ const sessionId = typeof row?.sessionId === 'string' ? row.sessionId.trim() : '';
3210
+ if (!sessionId) continue;
3211
+ const projectContext = normalizeProjectContext(row.projectContext, sessionId);
3212
+ projectContextBySession.set(sessionId, projectContext || {
3213
+ projectId: null,
3214
+ projectName: null,
3215
+ projectInstruction: '',
3216
+ sessionIds: [],
3217
+ });
3218
+ }
3219
+ }
3220
+
3201
3221
  export function handleYeaftProjectMutation(msg) {
3202
3222
  const requestId = msg && msg.requestId;
3203
3223
  const op = msg && msg.op;
@@ -3206,7 +3226,9 @@ export function handleYeaftProjectMutation(msg) {
3206
3226
  let result = null;
3207
3227
  if (op === 'create') result = createProject(yeaftDir, msg.name);
3208
3228
  else if (op === 'rename') result = renameProject(yeaftDir, msg.projectId, msg.name);
3209
- else if (op === 'delete') result = deleteProject(yeaftDir, msg.projectId);
3229
+ else if (op === 'update_instruction') {
3230
+ result = updateProjectInstruction(yeaftDir, msg.projectId, msg.instruction);
3231
+ } else if (op === 'delete') result = deleteProject(yeaftDir, msg.projectId);
3210
3232
  else if (op === 'move_session') {
3211
3233
  if (!snapshotSessions(yeaftDir).some(row => row.id === msg.sessionId)) {
3212
3234
  throw new ProjectStoreError('session_not_found', 'Session not found');
@@ -4568,6 +4590,7 @@ async function runYeaftSessionSend(msg) {
4568
4590
  projectContextBySession.set(sessionId, inboundProjectContext || {
4569
4591
  projectId: null,
4570
4592
  projectName: null,
4593
+ projectInstruction: '',
4571
4594
  sessionIds: [],
4572
4595
  });
4573
4596
  } else {
@@ -5250,6 +5273,7 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
5250
5273
  || legacyProjectContext(ctx.CONFIG?.yeaftDir, sessionId);
5251
5274
  const projectSessionIds = projectContext?.sessionIds || [];
5252
5275
  queryOpts.projectSessionIds = projectSessionIds;
5276
+ queryOpts.projectInstruction = projectContext?.projectInstruction || '';
5253
5277
  const projectSummaries = await sharedProjectContext(ctx.CONFIG?.yeaftDir, sessionId, {
5254
5278
  sessionIds: projectSessionIds,
5255
5279
  language: session?.config?.language,
@@ -6497,8 +6521,12 @@ export function handleYeaftSubAgentPrompt(msg) {
6497
6521
  return;
6498
6522
  }
6499
6523
 
6500
- if (!Array.isArray(agent.pendingPrompts)) agent.pendingPrompts = [];
6501
- agent.pendingPrompts.push(message);
6524
+ const projectContext = projectContextBySession.get(sessionId)
6525
+ || legacyProjectContext(ctx.CONFIG?.yeaftDir, sessionId);
6526
+ enqueueSubAgentPrompt(agent, message, {
6527
+ projectSessionIds: projectContext?.sessionIds,
6528
+ projectInstruction: projectContext?.projectInstruction,
6529
+ });
6502
6530
  if (!Array.isArray(agent.messages)) agent.messages = [];
6503
6531
  agent.messages.push({ role: 'user', content: message, timestamp: Date.now() });
6504
6532
  if (agent.status === 'idle' || agent.status === 'created') agent.status = 'running';
@@ -7385,6 +7413,12 @@ export const __testHooks = {
7385
7413
  sharedProjectContext,
7386
7414
  buildProjectSharedBlock,
7387
7415
  normalizeProjectContext,
7416
+ handleProjectContextSyncForTest(msg) {
7417
+ handleYeaftProjectContextSync(msg);
7418
+ },
7419
+ projectContextForSessionForTest(sessionId) {
7420
+ return projectContextBySession.get(sessionId) || null;
7421
+ },
7388
7422
  loadVisibleGroupHistoryPage,
7389
7423
  projectVisibleHistoryChunkMessages,
7390
7424
  persistInboundMessageOnceByMsgId,