@yeaft/webchat-agent 1.0.384 → 1.0.385
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/package.json +1 -1
- package/yeaft/cli-session-runner.js +137 -21
- package/yeaft/cli.js +248 -16
- package/yeaft/conversation/persist.js +2 -0
- package/yeaft/engine.js +31 -4
- package/yeaft/routing/router.js +6 -5
- package/yeaft/sessions/coordinator.js +41 -16
- package/yeaft/stdio-protocol.js +365 -242
- package/yeaft/sub-agent/public-event.js +131 -0
- package/yeaft/sub-agent/runner.js +11 -1
- package/yeaft/tasks/result-delivery.js +122 -0
- package/yeaft/tasks/result-format.js +28 -0
- package/yeaft/web-bridge.js +1 -23
package/yeaft/engine.js
CHANGED
|
@@ -694,6 +694,9 @@ export class Engine {
|
|
|
694
694
|
/** Wire turn id of the active query, used by late async completion rows. */
|
|
695
695
|
#currentQueryTurnId = null;
|
|
696
696
|
|
|
697
|
+
/** Durable formal-CLI root identity inherited by every row in this query. */
|
|
698
|
+
#currentCausalRootId = null;
|
|
699
|
+
|
|
697
700
|
/**
|
|
698
701
|
* Identity-bound hook into the active query's local read-only result cache.
|
|
699
702
|
* Async task terminal events can arrive while an adapter stream is already
|
|
@@ -1538,8 +1541,17 @@ export class Engine {
|
|
|
1538
1541
|
return Boolean(this.#conversationStore) && !this.#config._readOnly;
|
|
1539
1542
|
}
|
|
1540
1543
|
|
|
1541
|
-
#conversationRecord(message, { sessionId, turnId, model, incomplete = false, stopReason = null, executionOrigin = null } = {}) {
|
|
1544
|
+
#conversationRecord(message, { sessionId, turnId, causalRootId = undefined, model, incomplete = false, stopReason = null, executionOrigin = null } = {}) {
|
|
1542
1545
|
const effectiveTurnId = turnId || message.turnId || null;
|
|
1546
|
+
const normalizeId = value => (typeof value === 'string' && value.trim() ? value.trim() : null);
|
|
1547
|
+
// `null` is an explicit override used by a carried T2 reflection whose
|
|
1548
|
+
// originating query predates causal-root metadata. `undefined` inherits the
|
|
1549
|
+
// active query, which keeps every ordinary write path centralized here.
|
|
1550
|
+
const effectiveCausalRootId = causalRootId === null
|
|
1551
|
+
? null
|
|
1552
|
+
: (normalizeId(causalRootId)
|
|
1553
|
+
|| normalizeId(message.causalRootId)
|
|
1554
|
+
|| this.#currentCausalRootId);
|
|
1543
1555
|
const effectiveVpId = message.speakerVpId || this.#vpId || null;
|
|
1544
1556
|
const record = {
|
|
1545
1557
|
role: message.role,
|
|
@@ -1569,6 +1581,7 @@ export class Engine {
|
|
|
1569
1581
|
if (effectiveTurnId && (message.role === 'assistant' || message.role === 'tool' || message.internal === true)) {
|
|
1570
1582
|
record.turnId = effectiveTurnId;
|
|
1571
1583
|
}
|
|
1584
|
+
if (effectiveCausalRootId) record.causalRootId = effectiveCausalRootId;
|
|
1572
1585
|
if (executionOrigin === 'route_forward' && (message.role === 'assistant' || message.role === 'tool')) {
|
|
1573
1586
|
record.executionOrigin = executionOrigin;
|
|
1574
1587
|
}
|
|
@@ -1997,6 +2010,8 @@ export class Engine {
|
|
|
1997
2010
|
* user-message content; the string `prompt` is then only used for
|
|
1998
2011
|
* logging / history. When omitted the engine falls back to the
|
|
1999
2012
|
* string-prompt shape (no regression for existing callers).
|
|
2013
|
+
* @param {string|null} [params.causalRootId] - Stable durable identity for
|
|
2014
|
+
* every row generated as part of one externally accepted causal root.
|
|
2000
2015
|
* @yields {EngineEvent}
|
|
2001
2016
|
*/
|
|
2002
2017
|
async *query(params = {}) {
|
|
@@ -2045,7 +2060,7 @@ export class Engine {
|
|
|
2045
2060
|
}
|
|
2046
2061
|
}
|
|
2047
2062
|
|
|
2048
|
-
async *#queryLifecycle({ prompt, promptParts = null, messages = [], signal, userEffort = null, scenario = 'chat', vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers, sessionId, sessionMembers, projectSessionIds = null, projectInstruction = '', projectLabel = '', 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 } = {}) {
|
|
2063
|
+
async *#queryLifecycle({ prompt, promptParts = null, messages = [], signal, userEffort = null, scenario = 'chat', vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers, sessionId, sessionMembers, projectSessionIds = null, projectInstruction = '', projectLabel = '', vpPlan, sessionAnnouncement, workCenterInstructions, workDir, userAlreadyPersisted = false, causalRootId = null, 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 } = {}) {
|
|
2049
2064
|
if (!prompt || typeof prompt !== 'string' || !prompt.trim()) {
|
|
2050
2065
|
const error = new Error('prompt is required and must be a non-empty string');
|
|
2051
2066
|
yield {
|
|
@@ -2091,6 +2106,8 @@ export class Engine {
|
|
|
2091
2106
|
const effectiveCollabToolPolicy = collabToolPolicy === COLLAB_TOOL_POLICY.SINGLE_VP || collabToolPolicy === COLLAB_TOOL_POLICY.MULTI_VP
|
|
2092
2107
|
? collabToolPolicy
|
|
2093
2108
|
: null;
|
|
2109
|
+
const effectiveCausalRootId = typeof causalRootId === 'string' && causalRootId.trim()
|
|
2110
|
+
? causalRootId.trim() : null;
|
|
2094
2111
|
|
|
2095
2112
|
// ─── task-325a: engine-owned AbortController ─────────────
|
|
2096
2113
|
// We create our own controller for this query run so `engine.abort()`
|
|
@@ -2134,7 +2151,8 @@ export class Engine {
|
|
|
2134
2151
|
};
|
|
2135
2152
|
try {
|
|
2136
2153
|
this.#currentThreadId = threadId || MAIN_THREAD_ID;
|
|
2137
|
-
|
|
2154
|
+
this.#currentCausalRootId = effectiveCausalRootId;
|
|
2155
|
+
yield* this.#runQuery({ prompt: effectivePrompt, promptParts: effectivePromptParts, messages, signal: runSignal, userEffort: explicitUserEffort, scenario, vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers, sessionId, sessionMembers, projectSessionIds, projectInstruction, projectLabel, vpPlan, sessionAnnouncement, workCenterInstructions, workDir, userAlreadyPersisted, causalRootId: effectiveCausalRootId, getCurrentTodos, setCurrentTodos, askUser, threadId: this.#currentThreadId, vpTurnId, drainPendingUserMessages, prepareProviderRequest, startProviderRequest, finishProviderRequest, failProviderRequest, closePendingUserInput, collabToolPolicy: effectiveCollabToolPolicy, explicitSkillName: parsedSkill.skillName, retryLifecycle });
|
|
2138
2156
|
} finally {
|
|
2139
2157
|
// Closing the async generator at a visible retry boundary means the
|
|
2140
2158
|
// continuation never reached a provider. Keep it out of history and
|
|
@@ -2163,6 +2181,7 @@ export class Engine {
|
|
|
2163
2181
|
this.#currentAbortCtrl = null;
|
|
2164
2182
|
this.#abortReason = null;
|
|
2165
2183
|
this.#currentQueryTurnId = null;
|
|
2184
|
+
this.#currentCausalRootId = null;
|
|
2166
2185
|
this.#currentThreadId = MAIN_THREAD_ID;
|
|
2167
2186
|
this.#pendingUserMessages.length = 0;
|
|
2168
2187
|
this.#externalUserWakePending = false;
|
|
@@ -2188,7 +2207,7 @@ export class Engine {
|
|
|
2188
2207
|
* in a try/finally without indenting the whole loop.
|
|
2189
2208
|
* @private
|
|
2190
2209
|
*/
|
|
2191
|
-
async *#runQuery({ prompt, promptParts = null, messages, signal, userEffort = null, scenario = 'chat', vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers, sessionId, sessionMembers, projectSessionIds = null, projectInstruction = '', projectLabel = '', 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 }) {
|
|
2210
|
+
async *#runQuery({ prompt, promptParts = null, messages, signal, userEffort = null, scenario = 'chat', vpPersona, router, senderVpId, inboundEnvelope, taskId, taskMembers, sessionId, sessionMembers, projectSessionIds = null, projectInstruction = '', projectLabel = '', vpPlan, sessionAnnouncement, workCenterInstructions, workDir, userAlreadyPersisted = false, causalRootId = null, 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 }) {
|
|
2192
2211
|
|
|
2193
2212
|
const effectiveCollabToolPolicy = collabToolPolicy === COLLAB_TOOL_POLICY.SINGLE_VP || collabToolPolicy === COLLAB_TOOL_POLICY.MULTI_VP
|
|
2194
2213
|
? collabToolPolicy
|
|
@@ -3961,6 +3980,7 @@ export class Engine {
|
|
|
3961
3980
|
count: pairs.length,
|
|
3962
3981
|
originalUserMsg: prompt,
|
|
3963
3982
|
originatingTurnId: queryTurnId,
|
|
3983
|
+
causalRootId,
|
|
3964
3984
|
executionOrigin,
|
|
3965
3985
|
ready: false,
|
|
3966
3986
|
result: null,
|
|
@@ -4782,6 +4802,7 @@ export class Engine {
|
|
|
4782
4802
|
reflectionMessage,
|
|
4783
4803
|
{
|
|
4784
4804
|
...context,
|
|
4805
|
+
causalRootId: info.causalRootId || null,
|
|
4785
4806
|
executionOrigin: info.executionOrigin === 'route_forward' ? 'route_forward' : null,
|
|
4786
4807
|
},
|
|
4787
4808
|
);
|
|
@@ -4827,6 +4848,12 @@ export class Engine {
|
|
|
4827
4848
|
return this.#currentThreadId || MAIN_THREAD_ID;
|
|
4828
4849
|
}
|
|
4829
4850
|
|
|
4851
|
+
/** Stable owner scope for external exactly-once task coordinators. */
|
|
4852
|
+
get sessionId() { return this.#sessionId; }
|
|
4853
|
+
|
|
4854
|
+
/** Stable owner scope for external exactly-once task coordinators. */
|
|
4855
|
+
get vpId() { return this.#vpId; }
|
|
4856
|
+
|
|
4830
4857
|
/**
|
|
4831
4858
|
* Append a user message into the currently running query. The loop consumes
|
|
4832
4859
|
* it only at adapter boundaries, never mid-token and never between an
|
package/yeaft/routing/router.js
CHANGED
|
@@ -89,6 +89,7 @@ export function createRouter(deps = {}) {
|
|
|
89
89
|
}
|
|
90
90
|
const meta = coordinator.group.getMeta();
|
|
91
91
|
if (!meta) return { ok: false, error: 'group_not_initialised' };
|
|
92
|
+
const claimedVpIds = args.inboundEnvelope?._cliTurnContext?.claimedVpIds;
|
|
92
93
|
|
|
93
94
|
// Roster membership — `all` is reserved broadcast sentinel handled by
|
|
94
95
|
// coordinator; anything else must resolve to a real member so we fail fast
|
|
@@ -101,6 +102,9 @@ export function createRouter(deps = {}) {
|
|
|
101
102
|
if (targetVpId === from) {
|
|
102
103
|
return { ok: false, error: 'self_forward_rejected' };
|
|
103
104
|
}
|
|
105
|
+
if (targetVpId !== 'all' && claimedVpIds?.has?.(targetVpId)) {
|
|
106
|
+
return { ok: false, error: 'target_already_claimed' };
|
|
107
|
+
}
|
|
104
108
|
|
|
105
109
|
// Build the causedBy chain BEFORE constructing the synthetic user-like
|
|
106
110
|
// message. We don't know the new msgId yet (coordinator mints it on
|
|
@@ -133,15 +137,11 @@ export function createRouter(deps = {}) {
|
|
|
133
137
|
// stamp + `synthetic` marker let Coordinator's `selectRespondingVps`
|
|
134
138
|
// still treat this like a routed turn (target VPs need to respond) even
|
|
135
139
|
// though role is now 'assistant'.
|
|
136
|
-
const injectText = targetVpId === 'all'
|
|
137
|
-
? `@all ${text}`
|
|
138
|
-
: `@${targetVpId} ${text}`;
|
|
139
|
-
|
|
140
140
|
const report = coordinator.ingest(
|
|
141
141
|
{
|
|
142
142
|
from, // real VP id — preserved for provenance
|
|
143
143
|
role: 'assistant', // VP-authored — persists as assistant turn
|
|
144
|
-
text
|
|
144
|
+
text,
|
|
145
145
|
taskId: args.taskId ?? null,
|
|
146
146
|
// route_forward is already visible as the source VP's tool action.
|
|
147
147
|
// Persist the synthetic handoff for audit/dispatch, but keep it out
|
|
@@ -151,6 +151,7 @@ export function createRouter(deps = {}) {
|
|
|
151
151
|
meta: {
|
|
152
152
|
synthetic: true,
|
|
153
153
|
injectedBy: 'route_forward',
|
|
154
|
+
routeForwardTarget: targetVpId,
|
|
154
155
|
senderVpId: from,
|
|
155
156
|
reason: args.reason || null,
|
|
156
157
|
causedBy: chain,
|
|
@@ -74,11 +74,25 @@ export function createCoordinator(group, options = {}) {
|
|
|
74
74
|
// treat it as "user-like" for dispatch purposes only. Persistence still
|
|
75
75
|
// honours the caller's `role` field so the on-disk record correctly
|
|
76
76
|
// attributes the turn to the sending VP, not to the user.
|
|
77
|
-
const
|
|
77
|
+
const inputMeta = input?.meta && typeof input.meta === 'object' ? input.meta : {};
|
|
78
|
+
const routingIntent = input?._routingIntent && typeof input._routingIntent === 'object'
|
|
79
|
+
? input._routingIntent
|
|
80
|
+
: null;
|
|
81
|
+
const isRouteForwardInjection = inputMeta.injectedBy === 'route_forward';
|
|
82
|
+
const isTaskResultInjection = inputMeta.injectedBy === 'task_result';
|
|
78
83
|
const fromUser = input.from === 'user'
|
|
79
84
|
|| input.role === 'user'
|
|
80
|
-
|| isRouteForwardInjection
|
|
81
|
-
|
|
85
|
+
|| isRouteForwardInjection
|
|
86
|
+
|| isTaskResultInjection;
|
|
87
|
+
const forcedRouteTarget = (isRouteForwardInjection || isTaskResultInjection)
|
|
88
|
+
&& typeof inputMeta.routeTargetVpId === 'string'
|
|
89
|
+
? inputMeta.routeTargetVpId.trim()
|
|
90
|
+
: (isRouteForwardInjection && typeof inputMeta.routeForwardTarget === 'string'
|
|
91
|
+
? inputMeta.routeForwardTarget.trim()
|
|
92
|
+
: '');
|
|
93
|
+
const mentions = routingIntent
|
|
94
|
+
? (routingIntent.broadcast === true ? ['all'] : routingIntent.targetVpIds.slice())
|
|
95
|
+
: (forcedRouteTarget ? [forcedRouteTarget] : parseMentions(input.text));
|
|
82
96
|
|
|
83
97
|
// Persist first — audit log / replay works even if dispatch has bugs.
|
|
84
98
|
//
|
|
@@ -137,38 +151,49 @@ export function createCoordinator(group, options = {}) {
|
|
|
137
151
|
};
|
|
138
152
|
}
|
|
139
153
|
|
|
154
|
+
const deliverSelected = (vpIds, trigger) => {
|
|
155
|
+
const dispatched = [];
|
|
156
|
+
const errors = [];
|
|
157
|
+
for (const vpId of vpIds) {
|
|
158
|
+
const outcome = deliver(vpId, makeEnvelope(stored, meta, trigger, ephemeral));
|
|
159
|
+
if (outcome === false || outcome?.ok === false) {
|
|
160
|
+
errors.push({ vpId, error: outcome?.error || 'delivery_rejected' });
|
|
161
|
+
} else {
|
|
162
|
+
dispatched.push(vpId);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return { dispatched, errors };
|
|
166
|
+
};
|
|
167
|
+
|
|
140
168
|
if (selection.reason === 'broadcast') {
|
|
141
|
-
const
|
|
142
|
-
for (const vpId of selection.dispatched) deliver(vpId, envelope);
|
|
169
|
+
const delivered = deliverSelected(selection.dispatched, 'broadcast');
|
|
143
170
|
return {
|
|
144
171
|
message: stored,
|
|
145
|
-
dispatched:
|
|
172
|
+
dispatched: delivered.dispatched,
|
|
146
173
|
fallback: null,
|
|
147
|
-
errors: selection.errors,
|
|
174
|
+
errors: [...selection.errors, ...delivered.errors],
|
|
148
175
|
broadcast: true,
|
|
149
176
|
truncatedAtFanOutCap: !!selection.truncatedAtFanOutCap,
|
|
150
177
|
};
|
|
151
178
|
}
|
|
152
179
|
|
|
153
180
|
if (selection.reason === 'mention') {
|
|
154
|
-
|
|
155
|
-
deliver(vpId, makeEnvelope(stored, meta, 'mention', ephemeral));
|
|
156
|
-
}
|
|
181
|
+
const delivered = deliverSelected(selection.dispatched, 'mention');
|
|
157
182
|
return {
|
|
158
183
|
message: stored,
|
|
159
|
-
dispatched:
|
|
184
|
+
dispatched: delivered.dispatched,
|
|
160
185
|
fallback: null,
|
|
161
|
-
errors: selection.errors,
|
|
186
|
+
errors: [...selection.errors, ...delivered.errors],
|
|
162
187
|
};
|
|
163
188
|
}
|
|
164
189
|
|
|
165
190
|
if (selection.reason === 'fallback' && selection.fallback) {
|
|
166
|
-
|
|
191
|
+
const delivered = deliverSelected([selection.fallback], 'fallback');
|
|
167
192
|
return {
|
|
168
193
|
message: stored,
|
|
169
|
-
dispatched:
|
|
170
|
-
fallback: selection.fallback,
|
|
171
|
-
errors: selection.errors,
|
|
194
|
+
dispatched: delivered.dispatched,
|
|
195
|
+
fallback: delivered.dispatched.includes(selection.fallback) ? selection.fallback : null,
|
|
196
|
+
errors: [...selection.errors, ...delivered.errors],
|
|
172
197
|
};
|
|
173
198
|
}
|
|
174
199
|
|