@yeaft/webchat-agent 1.0.637 → 1.0.638
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/local-runtime/web/app.bundle.js +204 -183
- 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 +38 -1
- package/yeaft/query-progress.js +62 -2
- package/yeaft/web-bridge.js +18 -1
|
Binary file
|
package/package.json
CHANGED
package/yeaft/engine.js
CHANGED
|
@@ -3311,7 +3311,9 @@ export class Engine {
|
|
|
3311
3311
|
totalUsage.cacheInputDeltaTokens += cacheInputDeltaTokens;
|
|
3312
3312
|
cumulativeInputTokens += inputTokens + cacheInputDeltaTokens;
|
|
3313
3313
|
cumulativeOutputTokens += outputTokens;
|
|
3314
|
+
queryProgress.usageObserved(event);
|
|
3314
3315
|
yield event;
|
|
3316
|
+
yield queryProgress.snapshot(progressIdentity);
|
|
3315
3317
|
break;
|
|
3316
3318
|
}
|
|
3317
3319
|
case 'stop':
|
|
@@ -4856,6 +4858,7 @@ export class Engine {
|
|
|
4856
4858
|
reused: reusedReadOnlyResult || Boolean(readyParallelExecution?.shared),
|
|
4857
4859
|
skipped: skipped || needsProjectDocReload,
|
|
4858
4860
|
policy: duplicateCallPolicy,
|
|
4861
|
+
pendingOutcome: pendingPresentations.has(tc.id),
|
|
4859
4862
|
});
|
|
4860
4863
|
const toolDurationMs = readyParallelExecution?.durationMs ?? (Date.now() - toolStartTime);
|
|
4861
4864
|
|
|
@@ -4994,9 +4997,11 @@ export class Engine {
|
|
|
4994
4997
|
if (toolMessage) Object.assign(toolMessage, { content: output, isError: true });
|
|
4995
4998
|
const toolRecord = this.#persistedToolMessages.get(callId);
|
|
4996
4999
|
try { this.#conversationStore?.update?.(toolRecord, { content: output, isError: true }); } catch { /* original storage failure */ }
|
|
5000
|
+
queryProgress.finalizeOutcome(callId, true);
|
|
4997
5001
|
yield { type: 'tool_end', id: callId, name: 'PresentResponse', output, isError: true, threadId: runtimeThreadId };
|
|
4998
5002
|
continue;
|
|
4999
5003
|
}
|
|
5004
|
+
queryProgress.finalizeOutcome(callId, false);
|
|
5000
5005
|
publishedResponses.set(callId, record);
|
|
5001
5006
|
lastPersistedAssistantTextMessage = record;
|
|
5002
5007
|
// Metadata is transcript/UI-only. Providers receive the summary as a
|
|
@@ -5137,8 +5142,39 @@ export class Engine {
|
|
|
5137
5142
|
loopRange: [batchStart, batchEnd],
|
|
5138
5143
|
toolCount: pairs.length,
|
|
5139
5144
|
};
|
|
5145
|
+
// Observe only this synchronous query-owned call. Do not tap the
|
|
5146
|
+
// shared accounting adapter (also used by children), or feed these
|
|
5147
|
+
// costs into the main loop's token/dispatch budget. Capture raw usage
|
|
5148
|
+
// before reflection diagnostics normalize away cache-inclusion flags.
|
|
5149
|
+
const reflectionAdapter = typeof this.#adapter.call === 'function' ? {
|
|
5150
|
+
call: async params => {
|
|
5151
|
+
let dispatches = 0;
|
|
5152
|
+
let result;
|
|
5153
|
+
let failure;
|
|
5154
|
+
try {
|
|
5155
|
+
result = await this.#adapter.call({
|
|
5156
|
+
...params,
|
|
5157
|
+
onRequestStart: (...args) => {
|
|
5158
|
+
dispatches++;
|
|
5159
|
+
queryProgress.requestStarted();
|
|
5160
|
+
params.onRequestStart?.(...args);
|
|
5161
|
+
},
|
|
5162
|
+
});
|
|
5163
|
+
return result;
|
|
5164
|
+
} catch (error) {
|
|
5165
|
+
failure = error;
|
|
5166
|
+
throw error;
|
|
5167
|
+
} finally {
|
|
5168
|
+
// Native adapters report every dispatch, including a refreshed
|
|
5169
|
+
// credential retry. Legacy adapters without the hook count once.
|
|
5170
|
+
if (dispatches === 0) queryProgress.requestStarted();
|
|
5171
|
+
const usage = result?.usage || failure?.usage;
|
|
5172
|
+
if (usage) queryProgress.usageObserved(usage);
|
|
5173
|
+
}
|
|
5174
|
+
},
|
|
5175
|
+
} : this.#adapter;
|
|
5140
5176
|
const { content, durationMs } = await runT1Reflection({
|
|
5141
|
-
adapter:
|
|
5177
|
+
adapter: reflectionAdapter,
|
|
5142
5178
|
model: this.#config.model,
|
|
5143
5179
|
originalUserMsg: prompt,
|
|
5144
5180
|
toolPairs: pairs,
|
|
@@ -5228,6 +5264,7 @@ export class Engine {
|
|
|
5228
5264
|
// when every T1 attempt has errored.
|
|
5229
5265
|
lastT1AtLoopCount = completedToolLoops;
|
|
5230
5266
|
}
|
|
5267
|
+
yield queryProgress.snapshot(progressIdentity);
|
|
5231
5268
|
}
|
|
5232
5269
|
}
|
|
5233
5270
|
|
package/yeaft/query-progress.js
CHANGED
|
@@ -7,6 +7,7 @@ import { createHash } from 'node:crypto';
|
|
|
7
7
|
import { argsHashOf } from './tool-folding/exec-log.js';
|
|
8
8
|
|
|
9
9
|
const MAX_ENTRIES = 1024;
|
|
10
|
+
const MAX_TOOL_STATS = 128;
|
|
10
11
|
const WARNING_BATCHES = 4;
|
|
11
12
|
const RECOVERY_BATCHES = 8;
|
|
12
13
|
const REPORT_BATCHES = 12;
|
|
@@ -61,6 +62,10 @@ export class QueryProgress {
|
|
|
61
62
|
#results = new Map();
|
|
62
63
|
#mutations = new Map();
|
|
63
64
|
#failures = new Map();
|
|
65
|
+
#toolStats = new Map();
|
|
66
|
+
#pendingOutcomes = new Map();
|
|
67
|
+
#usageTotals = { inputTokens: 0, outputTokens: 0 };
|
|
68
|
+
#toolStatsTruncated = false;
|
|
64
69
|
#batch = { evidence: false, repeated: false, failed: false };
|
|
65
70
|
#revision = 0;
|
|
66
71
|
#state = {
|
|
@@ -70,6 +75,23 @@ export class QueryProgress {
|
|
|
70
75
|
|
|
71
76
|
requestStarted() { this.#state.modelRequests += 1; }
|
|
72
77
|
|
|
78
|
+
/** Query-local telemetry only: include synchronous side calls, not billing or loop budgets. */
|
|
79
|
+
usageObserved(usage) {
|
|
80
|
+
const reported = key => (!Array.isArray(usage.reportedTokenFields) || usage.reportedTokenFields.includes(key))
|
|
81
|
+
&& Number.isSafeInteger(usage[key]) && usage[key] >= 0;
|
|
82
|
+
const cacheInputDelta = usage.cacheTokensAreIncludedInInput ? 0
|
|
83
|
+
: (usage.cacheReadTokens || 0) + (usage.cacheWriteTokens || 0);
|
|
84
|
+
this.#usageTotals.inputTokens += (usage.inputTokens || 0) + cacheInputDelta;
|
|
85
|
+
this.#usageTotals.outputTokens += usage.outputTokens || 0;
|
|
86
|
+
const inputReported = reported('inputTokens') || (!usage.cacheTokensAreIncludedInInput
|
|
87
|
+
&& ['cacheReadTokens', 'cacheWriteTokens'].some(key => reported(key) && usage[key] > 0));
|
|
88
|
+
for (const [key, known] of [['inputTokens', inputReported], ['outputTokens', reported('outputTokens')]]) {
|
|
89
|
+
if (!known) continue;
|
|
90
|
+
if (Number.isSafeInteger(this.#usageTotals[key]) && this.#usageTotals[key] >= 0) this.#state[key] = this.#usageTotals[key];
|
|
91
|
+
else delete this.#state[key];
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
73
95
|
/** Caller feedback/new owned task results may invalidate prior observations. */
|
|
74
96
|
resetEvidence() {
|
|
75
97
|
this.#results.clear();
|
|
@@ -93,11 +115,33 @@ export class QueryProgress {
|
|
|
93
115
|
return `Blocked unchanged failed call to ${call.name} after ${failure.count} equivalent failures. This call was NOT executed. Use the earlier error to correct the arguments or choose a different supported path. If an earlier operation timed out or disconnected, its side effects may already have happened: verify the target state before retrying. Otherwise report the concrete blocker. Previous failed call: ${failure.callId}.`;
|
|
94
116
|
}
|
|
95
117
|
|
|
96
|
-
observe({ call, output, isError = false, readOnly = false, executed = true, reused = false, skipped = false, policy = 'warn' }) {
|
|
118
|
+
observe({ call, output, isError = false, readOnly = false, executed = true, reused = false, skipped = false, policy = 'warn', pendingOutcome = false }) {
|
|
97
119
|
if (skipped) return;
|
|
98
120
|
if (executed) this.#state.toolExecutions += 1;
|
|
99
121
|
if (reused) this.#state.reusedCalls += 1;
|
|
100
122
|
if (isError) this.#state.failedCalls += 1;
|
|
123
|
+
// Keep this bounded independently of the evidence caches. Existing rows
|
|
124
|
+
// remain cumulative even after the catalogue limit or an evidence reset.
|
|
125
|
+
const name = call.name;
|
|
126
|
+
const validName = typeof name === 'string' && /^[^\u0000-\u001f\u007f]{1,128}$/.test(name) && name.trim() === name;
|
|
127
|
+
// Never rewrite names: truncation/normalization can collide with another tool.
|
|
128
|
+
let stats = this.#toolStats.get(name);
|
|
129
|
+
if (!stats && validName && this.#toolStats.size < MAX_TOOL_STATS) {
|
|
130
|
+
stats = { name, executions: 0, failedCalls: 0, blockedCalls: 0 };
|
|
131
|
+
this.#toolStats.set(name, stats);
|
|
132
|
+
}
|
|
133
|
+
if (!stats) this.#toolStatsTruncated = true;
|
|
134
|
+
if (stats) {
|
|
135
|
+
if (executed) stats.executions += 1;
|
|
136
|
+
if (isError) {
|
|
137
|
+
stats.failedCalls += 1;
|
|
138
|
+
if (!executed) stats.blockedCalls += 1;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// Only publications with a delayed durable acknowledgement need correction.
|
|
142
|
+
// Keep it separate from evidence/failure caches so final telemetry cannot
|
|
143
|
+
// change duplicate detection, replay protection or loop-guard decisions.
|
|
144
|
+
if (pendingOutcome) this.#pendingOutcomes.set(call.id, { stats, isError, executed });
|
|
101
145
|
const key = `${call.name}:${argsHashOf(call.input)}`;
|
|
102
146
|
const hash = resultHash(output);
|
|
103
147
|
if (isError) {
|
|
@@ -166,8 +210,22 @@ export class QueryProgress {
|
|
|
166
210
|
}
|
|
167
211
|
}
|
|
168
212
|
|
|
213
|
+
/** Final acknowledgement may change status, but must never count another execution. */
|
|
214
|
+
finalizeOutcome(callId, isError) {
|
|
215
|
+
const pending = this.#pendingOutcomes.get(callId);
|
|
216
|
+
if (!pending) return;
|
|
217
|
+
this.#pendingOutcomes.delete(callId);
|
|
218
|
+
const delta = Number(isError) - Number(pending.isError);
|
|
219
|
+
this.#state.failedCalls += delta;
|
|
220
|
+
if (pending.stats) {
|
|
221
|
+
pending.stats.failedCalls += delta;
|
|
222
|
+
if (!pending.executed) pending.stats.blockedCalls += delta;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
169
226
|
/** Called only AFTER every tool result is paired and persisted. */
|
|
170
227
|
finishBatch() {
|
|
228
|
+
this.#pendingOutcomes.clear();
|
|
171
229
|
const previousStage = this.#state.stage;
|
|
172
230
|
if (this.#batch.evidence) {
|
|
173
231
|
this.#state.stagnantBatches = 0;
|
|
@@ -188,7 +246,9 @@ export class QueryProgress {
|
|
|
188
246
|
}
|
|
189
247
|
|
|
190
248
|
snapshot(identity = {}) {
|
|
191
|
-
return { type: 'query_progress', ...identity, revision: ++this.#revision, ...this.#state
|
|
249
|
+
return { type: 'query_progress', ...identity, revision: ++this.#revision, ...this.#state,
|
|
250
|
+
toolStats: [...this.#toolStats.values()].map(stats => ({ ...stats })),
|
|
251
|
+
...(this.#toolStatsTruncated ? { toolStatsTruncated: true } : {}) };
|
|
192
252
|
}
|
|
193
253
|
}
|
|
194
254
|
|
package/yeaft/web-bridge.js
CHANGED
|
@@ -4168,9 +4168,26 @@ function handleEngineEvent(event, hctx) {
|
|
|
4168
4168
|
hctx.queryProgressRevision = event.revision;
|
|
4169
4169
|
const { turnId, threadId, sessionId, vpId, revision, modelRequests, toolExecutions, reusedCalls,
|
|
4170
4170
|
repeatedCalls, failedCalls, stage, reason, lastEvidenceAt, stagnantBatches } = event;
|
|
4171
|
+
// Optional enhancements must never invalidate a legacy progress frame or
|
|
4172
|
+
// carry arbitrary provider/tool payloads across this projection boundary.
|
|
4173
|
+
const enhanced = {};
|
|
4174
|
+
for (const key of ['inputTokens', 'outputTokens']) {
|
|
4175
|
+
if (Number.isSafeInteger(event[key]) && event[key] >= 0) enhanced[key] = event[key];
|
|
4176
|
+
}
|
|
4177
|
+
if (Array.isArray(event.toolStats) && event.toolStats.length <= 128) {
|
|
4178
|
+
const names = new Set();
|
|
4179
|
+
if (event.toolStats.every(row => row && typeof row.name === 'string'
|
|
4180
|
+
&& /^[^\u0000-\u001f\u007f]{1,128}$/.test(row.name) && row.name.trim() === row.name
|
|
4181
|
+
&& !names.has(row.name) && names.add(row.name)
|
|
4182
|
+
&& ['executions', 'failedCalls', 'blockedCalls'].every(key => Number.isSafeInteger(row[key]) && row[key] >= 0)
|
|
4183
|
+
&& row.blockedCalls <= row.failedCalls)) {
|
|
4184
|
+
enhanced.toolStats = event.toolStats.map(({ name, executions, failedCalls, blockedCalls }) => ({ name, executions, failedCalls, blockedCalls }));
|
|
4185
|
+
if (typeof event.toolStatsTruncated === 'boolean') enhanced.toolStatsTruncated = event.toolStatsTruncated;
|
|
4186
|
+
}
|
|
4187
|
+
}
|
|
4171
4188
|
sendToServer({ type: 'yeaft_output', conversationId: yeaftConversationId, sessionId, vpId, turnId, threadId,
|
|
4172
4189
|
event: { type: 'query_progress', turnId, threadId, sessionId, vpId, revision, modelRequests,
|
|
4173
|
-
toolExecutions, reusedCalls, repeatedCalls, failedCalls, stage, reason, lastEvidenceAt, stagnantBatches } });
|
|
4190
|
+
toolExecutions, reusedCalls, repeatedCalls, failedCalls, stage, reason, lastEvidenceAt, stagnantBatches, ...enhanced } });
|
|
4174
4191
|
return;
|
|
4175
4192
|
}
|
|
4176
4193
|
if (event.type === 'aborted' || (event.type === 'turn_end' && event.terminal === true)) hctx.queryProgressTerminal = true;
|