@yeaft/webchat-agent 1.0.159 → 1.0.161
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 +5 -1
- package/package.json +4 -4
- package/yeaft/engine.js +5 -3
- package/yeaft/tools/ask-user.js +7 -14
- package/yeaft/tools/types.js +2 -0
- package/yeaft/web-bridge.js +68 -0
|
@@ -29,7 +29,7 @@ import { loadMcpServers, updateMcpConfig } from '../mcp.js';
|
|
|
29
29
|
import { getLlmConfig, updateLlmConfig, getYeaftSettings, updateYeaftSettings, getSearchSettings, updateSearchSettings, fetchTavilyUsage } from '../yeaft/config-api.js';
|
|
30
30
|
import { discoverLlmModels } from '../llm-model-discovery.js';
|
|
31
31
|
import { fetchModelsDev } from '../yeaft/llm/models-dev.js';
|
|
32
|
-
import { handleYeaftSessionSend, handleYeaftSubAgentPrompt, handleYeaftTaskCancel, handleYeaftModeSwitch, handleYeaftModelSwitch, resetYeaftSession, handleYeaftLoadHistory, handleYeaftLoadMoreHistory, handleYeaftAbortThread, handleYeaftAbortAll, handleYeaftAbortTurn, handleYeaftVpSubscribe, handleYeaftVpCreate, handleYeaftVpUpdate, handleYeaftVpDelete, handleYeaftVpRead, handleYeaftListSessions, handleYeaftCreateSession, handleYeaftRenameSession, handleYeaftUpdateSession, handleYeaftUpdateSessionConfig, handleYeaftArchiveSession, handleYeaftDeleteSession, handleYeaftSessionAddMember, handleYeaftSessionRemoveMember, handleYeaftSessionSetDefaultVp, handleYeaftScanWorkdirSessions, handleYeaftRestoreSession, handleYeaftDreamTrigger, handleYeaftFetchToolStats, handleYeaftFetchDebugHistory, handleYeaftMcpList, handleYeaftMcpAdd, handleYeaftMcpRemove, handleYeaftMcpReload, broadcastLanguageChange, broadcastYeaftSessionSnapshotEager, preloadYeaftSkillSlashCommands } from '../yeaft/web-bridge.js';
|
|
32
|
+
import { handleYeaftSessionSend, handleYeaftAskUserAnswer, handleYeaftSubAgentPrompt, handleYeaftTaskCancel, handleYeaftModeSwitch, handleYeaftModelSwitch, resetYeaftSession, handleYeaftLoadHistory, handleYeaftLoadMoreHistory, handleYeaftAbortThread, handleYeaftAbortAll, handleYeaftAbortTurn, handleYeaftVpSubscribe, handleYeaftVpCreate, handleYeaftVpUpdate, handleYeaftVpDelete, handleYeaftVpRead, handleYeaftListSessions, handleYeaftCreateSession, handleYeaftRenameSession, handleYeaftUpdateSession, handleYeaftUpdateSessionConfig, handleYeaftArchiveSession, handleYeaftDeleteSession, handleYeaftSessionAddMember, handleYeaftSessionRemoveMember, handleYeaftSessionSetDefaultVp, handleYeaftScanWorkdirSessions, handleYeaftRestoreSession, handleYeaftDreamTrigger, handleYeaftFetchToolStats, handleYeaftFetchDebugHistory, handleYeaftMcpList, handleYeaftMcpAdd, handleYeaftMcpRemove, handleYeaftMcpReload, broadcastLanguageChange, broadcastYeaftSessionSnapshotEager, preloadYeaftSkillSlashCommands } from '../yeaft/web-bridge.js';
|
|
33
33
|
import { startYeaftStatusRefresh, forceRefreshYeaftStatus } from '../yeaft/status-cache.js';
|
|
34
34
|
import { handleWorkCenterRequest } from '../yeaft/work-center/bridge.js';
|
|
35
35
|
|
|
@@ -282,6 +282,10 @@ export async function handleMessage(msg) {
|
|
|
282
282
|
handleAskUserAnswer(msg);
|
|
283
283
|
break;
|
|
284
284
|
|
|
285
|
+
case 'yeaft_ask_user_answer':
|
|
286
|
+
handleYeaftAskUserAnswer(msg);
|
|
287
|
+
break;
|
|
288
|
+
|
|
285
289
|
|
|
286
290
|
// Port proxy
|
|
287
291
|
case 'proxy_request':
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yeaft/webchat-agent",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.161",
|
|
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",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"license": "MIT",
|
|
42
42
|
"author": "Yeaft",
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"nodemon": "^3.
|
|
44
|
+
"nodemon": "^3.1.14"
|
|
45
45
|
},
|
|
46
46
|
"nodemonConfig": {
|
|
47
47
|
"watch": [
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"dotenv": "^16.3.1",
|
|
61
61
|
"tweetnacl": "^1.0.3",
|
|
62
62
|
"tweetnacl-util": "^0.15.1",
|
|
63
|
-
"uuid": "^11.1.
|
|
64
|
-
"ws": "^8.
|
|
63
|
+
"uuid": "^11.1.1",
|
|
64
|
+
"ws": "^8.21.0"
|
|
65
65
|
}
|
|
66
66
|
}
|
package/yeaft/engine.js
CHANGED
|
@@ -1185,6 +1185,7 @@ export class Engine {
|
|
|
1185
1185
|
// Null in non-VP / test contexts — tools tolerate missing slots.
|
|
1186
1186
|
getCurrentTodos: vpCtx?.getCurrentTodos || null,
|
|
1187
1187
|
setCurrentTodos: vpCtx?.setCurrentTodos || null,
|
|
1188
|
+
askUser: vpCtx?.askUser || null,
|
|
1188
1189
|
// task-707: tool-callable end-turn signal. The engine threads this
|
|
1189
1190
|
// setter when constructing toolCtx so a tool (e.g. route_forward)
|
|
1190
1191
|
// can mark "after this batch, end the turn — do NOT call adapter
|
|
@@ -1649,7 +1650,7 @@ export class Engine {
|
|
|
1649
1650
|
* string-prompt shape (no regression for existing callers).
|
|
1650
1651
|
* @yields {EngineEvent}
|
|
1651
1652
|
*/
|
|
1652
|
-
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, threadId = MAIN_THREAD_ID, vpTurnId = null, drainPendingUserMessages = null, collabToolPolicy = null } = {}) {
|
|
1653
|
+
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, collabToolPolicy = null } = {}) {
|
|
1653
1654
|
if (!prompt || typeof prompt !== 'string' || !prompt.trim()) {
|
|
1654
1655
|
yield {
|
|
1655
1656
|
type: 'error',
|
|
@@ -1717,7 +1718,7 @@ export class Engine {
|
|
|
1717
1718
|
|
|
1718
1719
|
try {
|
|
1719
1720
|
this.#currentThreadId = threadId || MAIN_THREAD_ID;
|
|
1720
|
-
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, threadId: this.#currentThreadId, vpTurnId, drainPendingUserMessages, collabToolPolicy: effectiveCollabToolPolicy, explicitSkillName: parsedSkill.skillName });
|
|
1721
|
+
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, collabToolPolicy: effectiveCollabToolPolicy, explicitSkillName: parsedSkill.skillName });
|
|
1721
1722
|
} finally {
|
|
1722
1723
|
if (signal) {
|
|
1723
1724
|
try { signal.removeEventListener('abort', onExternalAbort); } catch { /* ignore */ }
|
|
@@ -1761,7 +1762,7 @@ export class Engine {
|
|
|
1761
1762
|
* in a try/finally without indenting the whole loop.
|
|
1762
1763
|
* @private
|
|
1763
1764
|
*/
|
|
1764
|
-
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, threadId = MAIN_THREAD_ID, vpTurnId = null, drainPendingUserMessages = null, collabToolPolicy = null, explicitSkillName = null }) {
|
|
1765
|
+
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, collabToolPolicy = null, explicitSkillName = null }) {
|
|
1765
1766
|
|
|
1766
1767
|
const effectiveCollabToolPolicy = collabToolPolicy === COLLAB_TOOL_POLICY.SINGLE_VP || collabToolPolicy === COLLAB_TOOL_POLICY.MULTI_VP
|
|
1767
1768
|
? collabToolPolicy
|
|
@@ -3086,6 +3087,7 @@ export class Engine {
|
|
|
3086
3087
|
contextWindow: currentContextWindow,
|
|
3087
3088
|
getCurrentTodos,
|
|
3088
3089
|
setCurrentTodos,
|
|
3090
|
+
askUser,
|
|
3089
3091
|
workDir,
|
|
3090
3092
|
currentToolCall: () => currentToolCallForAsyncTask ? { ...currentToolCallForAsyncTask } : null,
|
|
3091
3093
|
requestEndTurn: (reason) => {
|
package/yeaft/tools/ask-user.js
CHANGED
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { defineTool } from './types.js';
|
|
12
|
-
import { randomUUID } from 'crypto';
|
|
13
12
|
|
|
14
13
|
export default defineTool({
|
|
15
14
|
name: 'AskUser',
|
|
@@ -57,22 +56,16 @@ Guidelines:
|
|
|
57
56
|
},
|
|
58
57
|
isConcurrencySafe: () => false,
|
|
59
58
|
isReadOnly: () => true,
|
|
59
|
+
// This is an intentionally user-driven wait, not a stalled tool call.
|
|
60
|
+
timeoutMs: 0,
|
|
60
61
|
async execute(input, ctx) {
|
|
61
62
|
const { question, options } = input;
|
|
62
63
|
if (!question) return JSON.stringify({ error: 'question is required' });
|
|
64
|
+
if (typeof ctx?.askUser !== 'function') {
|
|
65
|
+
return JSON.stringify({ error: 'Interactive user input is unavailable in this runtime.' });
|
|
66
|
+
}
|
|
63
67
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
// In a full web-bridge integration, this would send an ask_user event
|
|
68
|
-
// and await the answer. For now, return a formatted prompt that the
|
|
69
|
-
// LLM can see — the web-bridge handles the ask flow externally.
|
|
70
|
-
return JSON.stringify({
|
|
71
|
-
type: 'ask_user',
|
|
72
|
-
requestId,
|
|
73
|
-
question,
|
|
74
|
-
...(options ? { options } : {}),
|
|
75
|
-
message: `Question sent to user: "${question}"${options ? ` [Options: ${options.join(', ')}]` : ''}`,
|
|
76
|
-
});
|
|
68
|
+
const answers = await ctx.askUser({ question, options });
|
|
69
|
+
return JSON.stringify({ question, answers });
|
|
77
70
|
},
|
|
78
71
|
});
|
package/yeaft/tools/types.js
CHANGED
|
@@ -32,6 +32,8 @@
|
|
|
32
32
|
* @property {number} [contextWindow] — current model's context window in
|
|
33
33
|
* tokens (used by ToolRegistry.execute to cap a single tool result at a
|
|
34
34
|
* fraction of the window so one runaway grep can't blow the wire).
|
|
35
|
+
* @property {(input: {question:string, options?:string[]}) => Promise<object>} [askUser]
|
|
36
|
+
* — host-provided interactive prompt. Resolves only after the user answers.
|
|
35
37
|
* @property {(reason?: string|object) => void} [requestEndTurn]
|
|
36
38
|
* — tool-callable signal that the current engine turn should end after
|
|
37
39
|
* this batch of tool calls completes (no follow-up adapter.stream call).
|
package/yeaft/web-bridge.js
CHANGED
|
@@ -79,6 +79,9 @@ const YEAFT_SKILL_COMMAND_PREFIX = 'yeaft-skills:';
|
|
|
79
79
|
const PROJECT_SKILL_TIERS = new Set(['project', 'project-claude', 'project-codex']);
|
|
80
80
|
const BASE_RUNTIME_KEY = '__base__';
|
|
81
81
|
|
|
82
|
+
/** @type {Map<string, {resolve:Function, sessionId:string, vpId:string, threadId:string, turnId:string, signal?:AbortSignal, onAbort?:Function}>} */
|
|
83
|
+
const pendingUserPrompts = new Map();
|
|
84
|
+
|
|
82
85
|
/** @type {import('./session.js').Session | null} */
|
|
83
86
|
let session = null;
|
|
84
87
|
|
|
@@ -4361,6 +4364,44 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
|
|
|
4361
4364
|
// each write a copy of the user message, and history replay
|
|
4362
4365
|
// would render the user's prompt N times.
|
|
4363
4366
|
userAlreadyPersisted: true,
|
|
4367
|
+
askUser: ({ question, options }) => new Promise((resolve, reject) => {
|
|
4368
|
+
const requestId = `ask_${randomUUID()}`;
|
|
4369
|
+
const signal = vpAbort.signal;
|
|
4370
|
+
// User think time is not engine silence. Pause the query watchdog until
|
|
4371
|
+
// the card is answered; the normal tool/LLM budget resumes afterward.
|
|
4372
|
+
if (queryTimer) {
|
|
4373
|
+
clearTimeout(queryTimer);
|
|
4374
|
+
queryTimer = null;
|
|
4375
|
+
}
|
|
4376
|
+
const onAbort = () => {
|
|
4377
|
+
pendingUserPrompts.delete(requestId);
|
|
4378
|
+
reject(new Error('aborted'));
|
|
4379
|
+
};
|
|
4380
|
+
pendingUserPrompts.set(requestId, {
|
|
4381
|
+
resolve: answers => {
|
|
4382
|
+
resetQueryTimer();
|
|
4383
|
+
resolve(answers);
|
|
4384
|
+
},
|
|
4385
|
+
sessionId,
|
|
4386
|
+
vpId,
|
|
4387
|
+
threadId,
|
|
4388
|
+
turnId,
|
|
4389
|
+
signal,
|
|
4390
|
+
onAbort,
|
|
4391
|
+
});
|
|
4392
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
4393
|
+
sendSessionEvent({
|
|
4394
|
+
type: 'ask_user_question',
|
|
4395
|
+
requestId,
|
|
4396
|
+
questions: [{
|
|
4397
|
+
question,
|
|
4398
|
+
options: Array.isArray(options)
|
|
4399
|
+
? options.map(label => ({ label, description: '' }))
|
|
4400
|
+
: [],
|
|
4401
|
+
multiSelect: false,
|
|
4402
|
+
}],
|
|
4403
|
+
}, envelope);
|
|
4404
|
+
}),
|
|
4364
4405
|
threadId,
|
|
4365
4406
|
vpTurnId: turnId,
|
|
4366
4407
|
drainPendingUserMessages: () => {
|
|
@@ -5387,6 +5428,24 @@ export async function handleYeaftFetchDebugHistory(msg = {}) {
|
|
|
5387
5428
|
});
|
|
5388
5429
|
}
|
|
5389
5430
|
|
|
5431
|
+
/** Resolve a pending Yeaft AskUser prompt from the web UI. */
|
|
5432
|
+
export function handleYeaftAskUserAnswer(msg) {
|
|
5433
|
+
const requestId = typeof msg?.requestId === 'string' ? msg.requestId : '';
|
|
5434
|
+
const pending = pendingUserPrompts.get(requestId);
|
|
5435
|
+
if (!pending) return false;
|
|
5436
|
+
if (msg.sessionId && msg.sessionId !== pending.sessionId) return false;
|
|
5437
|
+
if (msg.vpId && msg.vpId !== pending.vpId) return false;
|
|
5438
|
+
if (msg.turnId && msg.turnId !== pending.turnId) return false;
|
|
5439
|
+
if (msg.threadId && msg.threadId !== pending.threadId) return false;
|
|
5440
|
+
|
|
5441
|
+
pendingUserPrompts.delete(requestId);
|
|
5442
|
+
if (pending.signal && pending.onAbort) {
|
|
5443
|
+
try { pending.signal.removeEventListener('abort', pending.onAbort); } catch { /* ignore */ }
|
|
5444
|
+
}
|
|
5445
|
+
pending.resolve(msg.answers || {});
|
|
5446
|
+
return true;
|
|
5447
|
+
}
|
|
5448
|
+
|
|
5390
5449
|
export async function handleYeaftSessionSend(msg) {
|
|
5391
5450
|
return runYeaftSessionSend(msg);
|
|
5392
5451
|
}
|
|
@@ -6207,6 +6266,15 @@ export const __testHooks = {
|
|
|
6207
6266
|
vpAborts.clear();
|
|
6208
6267
|
vpInboxes.clear();
|
|
6209
6268
|
},
|
|
6269
|
+
seedPendingUserPrompt({ requestId = 'ask-test', sessionId = 'session-test', vpId = 'vp-test', threadId = 'main', turnId = 'turn-test' } = {}) {
|
|
6270
|
+
let resolved;
|
|
6271
|
+
const promise = new Promise(resolve => { resolved = resolve; });
|
|
6272
|
+
pendingUserPrompts.set(requestId, { resolve: resolved, sessionId, vpId, threadId, turnId });
|
|
6273
|
+
return promise;
|
|
6274
|
+
},
|
|
6275
|
+
resetPendingUserPrompts() {
|
|
6276
|
+
pendingUserPrompts.clear();
|
|
6277
|
+
},
|
|
6210
6278
|
resetVpStatusBroker() {
|
|
6211
6279
|
if (vpStatusBroker) vpStatusBroker.reset();
|
|
6212
6280
|
},
|