@yeaft/webchat-agent 1.0.294 → 1.0.296
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/index.js +1 -2
- package/local-runtime/server/handlers/client-work-center.js +1 -1
- package/local-runtime/version.json +1 -1
- package/local-runtime/web/app.bundle.js +181 -173
- 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 +27 -3
- package/yeaft/work-center/bridge.js +5 -1
- package/yeaft/work-center/controller.js +6 -2
- package/yeaft/work-center/coordinator.js +51 -11
- package/yeaft/work-center/durable-model.js +618 -0
- package/yeaft/work-center/projection.js +8 -1
- package/yeaft/work-center/runner.js +101 -15
- package/yeaft/work-center/service.js +55 -0
- package/yeaft/work-center/store.js +774 -23
|
Binary file
|
package/package.json
CHANGED
package/yeaft/engine.js
CHANGED
|
@@ -1838,7 +1838,7 @@ export class Engine {
|
|
|
1838
1838
|
* string-prompt shape (no regression for existing callers).
|
|
1839
1839
|
* @yields {EngineEvent}
|
|
1840
1840
|
*/
|
|
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, closePendingUserInput = null, collabToolPolicy = null } = {}) {
|
|
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 } = {}) {
|
|
1842
1842
|
if (!prompt || typeof prompt !== 'string' || !prompt.trim()) {
|
|
1843
1843
|
yield {
|
|
1844
1844
|
type: 'error',
|
|
@@ -1915,7 +1915,7 @@ export class Engine {
|
|
|
1915
1915
|
};
|
|
1916
1916
|
try {
|
|
1917
1917
|
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, closePendingUserInput, collabToolPolicy: effectiveCollabToolPolicy, explicitSkillName: parsedSkill.skillName, retryLifecycle });
|
|
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 });
|
|
1919
1919
|
} finally {
|
|
1920
1920
|
// Closing the async generator at a visible retry boundary means the
|
|
1921
1921
|
// continuation never reached a provider. Keep it out of history and
|
|
@@ -1963,7 +1963,7 @@ export class Engine {
|
|
|
1963
1963
|
* in a try/finally without indenting the whole loop.
|
|
1964
1964
|
* @private
|
|
1965
1965
|
*/
|
|
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, closePendingUserInput = null, collabToolPolicy = null, explicitSkillName = null, retryLifecycle }) {
|
|
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 }) {
|
|
1967
1967
|
|
|
1968
1968
|
const effectiveCollabToolPolicy = collabToolPolicy === COLLAB_TOOL_POLICY.SINGLE_VP || collabToolPolicy === COLLAB_TOOL_POLICY.MULTI_VP
|
|
1969
1969
|
? collabToolPolicy
|
|
@@ -2367,6 +2367,7 @@ export class Engine {
|
|
|
2367
2367
|
let currentModel = this.#config.model;
|
|
2368
2368
|
let cumulativeInputTokens = 0;
|
|
2369
2369
|
let cumulativeOutputTokens = 0;
|
|
2370
|
+
let activeProviderRequest = null;
|
|
2370
2371
|
// task-707: tool-callable end-turn signal. Tools (currently only
|
|
2371
2372
|
// `route_forward`) can set this via toolCtx.requestEndTurn(reason)
|
|
2372
2373
|
// to break out of the tool-loop after the current batch finishes
|
|
@@ -2645,6 +2646,16 @@ export class Engine {
|
|
|
2645
2646
|
retryLifecycle.pendingContinuation = null;
|
|
2646
2647
|
}
|
|
2647
2648
|
|
|
2649
|
+
activeProviderRequest = typeof prepareProviderRequest === 'function'
|
|
2650
|
+
? prepareProviderRequest({
|
|
2651
|
+
turnNumber,
|
|
2652
|
+
entries: appendedBeforeStream,
|
|
2653
|
+
system: systemPrompt,
|
|
2654
|
+
messages: wireMessages.map(mapDebugMessage),
|
|
2655
|
+
model: currentModel,
|
|
2656
|
+
}) || null
|
|
2657
|
+
: null;
|
|
2658
|
+
|
|
2648
2659
|
// Snapshot task results carried by this exact request. Request start
|
|
2649
2660
|
// is not delivery: fetch may remain pending and then be aborted before
|
|
2650
2661
|
// the provider processes anything. Ack only after a normal stream end
|
|
@@ -2661,6 +2672,7 @@ export class Engine {
|
|
|
2661
2672
|
effortSource: userEffort ? 'user' : 'auto',
|
|
2662
2673
|
signal,
|
|
2663
2674
|
onRawExchange: captureRawExchange,
|
|
2675
|
+
onRequestStart: () => startProviderRequest?.(activeProviderRequest),
|
|
2664
2676
|
})) {
|
|
2665
2677
|
// task-325a (abort-stop fix): per-event abort short-circuit.
|
|
2666
2678
|
// The adapter is expected to throw AbortError when fetch's
|
|
@@ -2768,6 +2780,14 @@ export class Engine {
|
|
|
2768
2780
|
// escrow so retry or final rescue can deliver the payload.
|
|
2769
2781
|
if (sawProviderStop) {
|
|
2770
2782
|
this.#confirmAsyncTaskResults(requestAsyncTaskIds);
|
|
2783
|
+
const completedProviderRequest = activeProviderRequest;
|
|
2784
|
+
activeProviderRequest = null;
|
|
2785
|
+
finishProviderRequest?.(completedProviderRequest, {
|
|
2786
|
+
responseText,
|
|
2787
|
+
stopReason,
|
|
2788
|
+
toolCalls: toolCalls.map(tc => ({ id: tc.id, name: tc.name, input: tc.input })),
|
|
2789
|
+
thinkingBlocks,
|
|
2790
|
+
});
|
|
2771
2791
|
}
|
|
2772
2792
|
traceRequest('llm.request_complete', {
|
|
2773
2793
|
durationMs: perfNowMs() - requestPerfStart,
|
|
@@ -2785,6 +2805,10 @@ export class Engine {
|
|
|
2785
2805
|
consecutiveRetryableErrors = 0;
|
|
2786
2806
|
} catch (err) {
|
|
2787
2807
|
const latencyMs = Date.now() - startTime;
|
|
2808
|
+
if (activeProviderRequest) {
|
|
2809
|
+
failProviderRequest?.(activeProviderRequest, err);
|
|
2810
|
+
activeProviderRequest = null;
|
|
2811
|
+
}
|
|
2788
2812
|
|
|
2789
2813
|
const endAttemptTrace = (attemptStopReason) => {
|
|
2790
2814
|
this.#trace.endTurn(turnId, {
|
|
@@ -20,7 +20,7 @@ let shutdownPromise = null;
|
|
|
20
20
|
let serviceFactory = null;
|
|
21
21
|
|
|
22
22
|
const BROWSER_DETAIL_OPS = new Set([
|
|
23
|
-
'get', 'create', 'update', 'start', 'cancel', 'resume', 'action_input', 'retry_action', 'guide', 'retry',
|
|
23
|
+
'get', 'create', 'update', 'start', 'cancel', 'resume', 'post_work_item_message', 'action_input', 'retry_action', 'guide', 'retry',
|
|
24
24
|
]);
|
|
25
25
|
const BROWSER_ACTION_DEBUG_OPS = new Set(['get_action_messages', 'get_action_requests', 'get_action_request']);
|
|
26
26
|
// `files` is an internal server-to-Agent field. The browser relay rejects any
|
|
@@ -29,6 +29,10 @@ const BROWSER_FILE_FIELDS = Object.freeze({
|
|
|
29
29
|
create: [
|
|
30
30
|
'title', 'goal', 'acceptanceCriteria', 'workItemType', 'workDir', 'reuseMemory', 'files', 'start',
|
|
31
31
|
],
|
|
32
|
+
post_work_item_message: [
|
|
33
|
+
'id', 'clientMessageId', 'text', 'target', 'revision', 'planRevision', 'ledgerRevision',
|
|
34
|
+
'coordinatorRevision', 'files',
|
|
35
|
+
],
|
|
32
36
|
work_item_message: [
|
|
33
37
|
'id', 'text', 'revision', 'planRevision', 'ledgerRevision', 'coordinatorRevision', 'files',
|
|
34
38
|
],
|
|
@@ -227,6 +227,8 @@ export class WorkflowController {
|
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
input(id, input = {}) {
|
|
230
|
+
const existingClientMessage = this.store.hasActionInputClientMessage(id, input.actionId, input.clientMessageId);
|
|
231
|
+
if (existingClientMessage) return this.store.getWorkItemDetail(id);
|
|
230
232
|
const text = typeof input.text === 'string' ? input.text.trim().slice(0, 8_000) : '';
|
|
231
233
|
const addedAttachmentCount = Math.max(0, Number(input.addedAttachmentCount) || 0);
|
|
232
234
|
if (!text && addedAttachmentCount === 0) throw new Error('Action input or attachments are required');
|
|
@@ -253,7 +255,7 @@ export class WorkflowController {
|
|
|
253
255
|
actionId: input.actionId,
|
|
254
256
|
generation: expectedGeneration,
|
|
255
257
|
revision: input.revision,
|
|
256
|
-
}, input.attachments, input.addedAttachments);
|
|
258
|
+
}, input.attachments, input.addedAttachments, input.clientMessageId);
|
|
257
259
|
}
|
|
258
260
|
if (!['waiting', 'failed'].includes(targetAction.status)) {
|
|
259
261
|
throw new Error(`Action in ${targetAction.status} cannot accept input`);
|
|
@@ -264,7 +266,9 @@ export class WorkflowController {
|
|
|
264
266
|
expected: { actionId: input.actionId, generation: input.generation, revision: input.revision },
|
|
265
267
|
attachments: input.attachments,
|
|
266
268
|
inputEvent: {
|
|
267
|
-
inputId: randomUUID(),
|
|
269
|
+
inputId: input.clientMessageId || randomUUID(),
|
|
270
|
+
clientMessageId: input.clientMessageId || null,
|
|
271
|
+
targetActionId: input.actionId,
|
|
268
272
|
text: text || `The user added ${addedAttachmentCount} attachment(s) as additional context for this Action.`,
|
|
269
273
|
attachments: input.addedAttachments,
|
|
270
274
|
},
|
|
@@ -558,8 +558,12 @@ export class WorkItemCoordinator {
|
|
|
558
558
|
}, {
|
|
559
559
|
attachments: input.attachments,
|
|
560
560
|
addedAttachments,
|
|
561
|
+
clientMessageId: input.clientMessageId,
|
|
561
562
|
});
|
|
562
563
|
if (!started) throw new Error(`WorkItem not found: ${id}`);
|
|
564
|
+
if (started.duplicate) {
|
|
565
|
+
return { detail: started.detail, task: Promise.resolve(started.detail), duplicate: true };
|
|
566
|
+
}
|
|
563
567
|
options.onUpdate?.('coordinator.turn_started', started.detail);
|
|
564
568
|
return this.#scheduleTurn(started, {
|
|
565
569
|
text: promptText,
|
|
@@ -569,6 +573,18 @@ export class WorkItemCoordinator {
|
|
|
569
573
|
});
|
|
570
574
|
}
|
|
571
575
|
|
|
576
|
+
resume(started, options = {}) {
|
|
577
|
+
if (!started?.turnId || !started?.detail || !started?.fence) {
|
|
578
|
+
throw new Error('Coordinator provider recovery target is invalid');
|
|
579
|
+
}
|
|
580
|
+
return this.#scheduleTurn(started, {
|
|
581
|
+
text: typeof options.text === 'string' ? options.text : '',
|
|
582
|
+
recovery: options.recovery === true,
|
|
583
|
+
addedAttachments: Array.isArray(options.addedAttachments) ? options.addedAttachments : [],
|
|
584
|
+
options,
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
|
|
572
588
|
recover(id, options = {}) {
|
|
573
589
|
if (this.shuttingDown) throw new Error('Work Center Coordinator is shutting down');
|
|
574
590
|
const detail = this.store.getWorkItemDetail(id);
|
|
@@ -689,19 +705,42 @@ export class WorkItemCoordinator {
|
|
|
689
705
|
const content = attachmentContext.promptParts.length > 0
|
|
690
706
|
? [{ type: 'text', text: latestMessage }, ...attachmentContext.promptParts]
|
|
691
707
|
: latestMessage;
|
|
692
|
-
|
|
708
|
+
const requestBody = {
|
|
709
|
+
model: resolved.model,
|
|
710
|
+
system: coordinatorSystemPrompt(language),
|
|
711
|
+
messages: [{ role: 'user', content }],
|
|
712
|
+
maxTokens: Math.min(
|
|
713
|
+
resolveMaxOutputTokens(resolved.model, runtime.config),
|
|
714
|
+
COORDINATOR_MAX_OUTPUT_TOKENS,
|
|
715
|
+
),
|
|
716
|
+
effort: resolved.effort,
|
|
717
|
+
effortSource: resolved.source,
|
|
718
|
+
effortContext: { scenario: 'work-center-coordinator' },
|
|
719
|
+
};
|
|
720
|
+
const providerTurn = this.store.prepareCoordinatorProviderTurn(
|
|
721
|
+
started.detail.id, started.turnId, attemptCount, requestBody,
|
|
722
|
+
);
|
|
723
|
+
if (providerTurn.status === 'unknown') {
|
|
724
|
+
throw new Error('Coordinator provider dispatch outcome is unknown and requires review');
|
|
725
|
+
}
|
|
726
|
+
if (providerTurn.status === 'responded') {
|
|
727
|
+
result = providerTurn.response;
|
|
728
|
+
} else {
|
|
729
|
+
result = await Promise.race([
|
|
693
730
|
runtime.adapter.call({
|
|
694
|
-
|
|
695
|
-
system: coordinatorSystemPrompt(language),
|
|
696
|
-
messages: [{ role: 'user', content }],
|
|
697
|
-
maxTokens: Math.min(
|
|
698
|
-
resolveMaxOutputTokens(resolved.model, runtime.config),
|
|
699
|
-
COORDINATOR_MAX_OUTPUT_TOKENS,
|
|
700
|
-
),
|
|
701
|
-
effort: resolved.effort,
|
|
702
|
-
effortSource: resolved.source,
|
|
703
|
-
effortContext: { scenario: 'work-center-coordinator' },
|
|
731
|
+
...requestBody,
|
|
704
732
|
signal: abortController.signal,
|
|
733
|
+
onRequestStart: () => {
|
|
734
|
+
if (!this.store.dispatchCoordinatorProviderTurn(providerTurn.id)) {
|
|
735
|
+
throw new Error('Coordinator provider turn lost its dispatch fence');
|
|
736
|
+
}
|
|
737
|
+
},
|
|
738
|
+
}).then(response => {
|
|
739
|
+
const persisted = this.store.respondCoordinatorProviderTurn(
|
|
740
|
+
providerTurn.id, providerTurn.requestHash, response,
|
|
741
|
+
);
|
|
742
|
+
if (!persisted) throw new Error('Coordinator provider response lost its CAS fence');
|
|
743
|
+
return response;
|
|
705
744
|
}),
|
|
706
745
|
new Promise((_, reject) => {
|
|
707
746
|
abortController.signal.addEventListener('abort', () => {
|
|
@@ -709,6 +748,7 @@ export class WorkItemCoordinator {
|
|
|
709
748
|
}, { once: true });
|
|
710
749
|
}),
|
|
711
750
|
]);
|
|
751
|
+
}
|
|
712
752
|
} catch (error) {
|
|
713
753
|
if (abortController.signal.aborted || this.shuttingDown) throw error;
|
|
714
754
|
throw coordinatorExecutionError(error, 'provider', language);
|