@yeaft/webchat-agent 0.1.612 → 0.1.613
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 +1 -1
- package/unify/engine.js +40 -24
package/package.json
CHANGED
package/unify/engine.js
CHANGED
|
@@ -1284,13 +1284,23 @@ export class Engine {
|
|
|
1284
1284
|
// Detach: never await. The promise outlives this query() and
|
|
1285
1285
|
// the next call will pick it up (or use the fallback stub if
|
|
1286
1286
|
// it hasn't resolved by then).
|
|
1287
|
-
|
|
1288
|
-
|
|
1287
|
+
// PR-L follow-up: latch a synchronously-readable ready flag
|
|
1288
|
+
// and result on the info record so `#applyPendingT2Reflections`
|
|
1289
|
+
// can decide ready-vs-pending without racing microtasks.
|
|
1290
|
+
const info = {
|
|
1289
1291
|
promise,
|
|
1290
1292
|
loopRange: [arcStart, arcEnd],
|
|
1291
1293
|
count: pairs.length,
|
|
1292
1294
|
originalUserMsg: prompt,
|
|
1293
|
-
|
|
1295
|
+
ready: false,
|
|
1296
|
+
result: null,
|
|
1297
|
+
error: null,
|
|
1298
|
+
};
|
|
1299
|
+
promise.then(
|
|
1300
|
+
(v) => { info.ready = true; info.result = v; },
|
|
1301
|
+
(err) => { info.ready = true; info.error = err; },
|
|
1302
|
+
);
|
|
1303
|
+
this.#pendingT2.set(queryNumber, info);
|
|
1294
1304
|
}
|
|
1295
1305
|
}
|
|
1296
1306
|
|
|
@@ -1328,10 +1338,16 @@ export class Engine {
|
|
|
1328
1338
|
// by the Anthropic / OpenAI Responses APIs stays intact. We
|
|
1329
1339
|
// don't block the call — the LLM still decides.
|
|
1330
1340
|
const dupHash = argsHashOf(tc.input);
|
|
1341
|
+
// PR-L follow-up: lookback is by user-conversation turn
|
|
1342
|
+
// (`queryNumber`), NOT by inner adapter loop iteration. Each call
|
|
1343
|
+
// to query() bumps queryNumber once, so "last 2 turns" means the
|
|
1344
|
+
// current user turn + the previous two user turns — the natural
|
|
1345
|
+
// semantic for "the model is stuck in a loop across the
|
|
1346
|
+
// conversation."
|
|
1331
1347
|
const dupInfo = this.#execLog.dupInfo({
|
|
1332
1348
|
toolName: tc.name,
|
|
1333
1349
|
argsHash: dupHash,
|
|
1334
|
-
currentTurn:
|
|
1350
|
+
currentTurn: queryNumber,
|
|
1335
1351
|
lookbackTurns: 2,
|
|
1336
1352
|
});
|
|
1337
1353
|
if (dupInfo.count + 1 >= DUP_TOOL_THRESHOLD) {
|
|
@@ -1393,7 +1409,11 @@ export class Engine {
|
|
|
1393
1409
|
// PR-L: persist this execution to the exec-log for fallback-stub
|
|
1394
1410
|
// and duplicate-call detection. Best-effort — disk failures are
|
|
1395
1411
|
// swallowed inside ExecLog.append.
|
|
1396
|
-
|
|
1412
|
+
// PR-L follow-up: persist under `queryNumber` (one entry-key per
|
|
1413
|
+
// user-conversation turn), not the inner loop's turnNumber.
|
|
1414
|
+
// Aligns exec-log layout with dup detection lookback and the
|
|
1415
|
+
// T2 fallback-stub readTurn() call below.
|
|
1416
|
+
this.#execLog.append(queryNumber, buildExecLogEntry({
|
|
1397
1417
|
loopIdx: queryToolCount,
|
|
1398
1418
|
toolName: tc.name,
|
|
1399
1419
|
args: tc.input,
|
|
@@ -1567,33 +1587,29 @@ export class Engine {
|
|
|
1567
1587
|
continue;
|
|
1568
1588
|
}
|
|
1569
1589
|
|
|
1570
|
-
//
|
|
1571
|
-
//
|
|
1572
|
-
//
|
|
1573
|
-
//
|
|
1574
|
-
const PENDING_SENTINEL = {};
|
|
1575
|
-
const settled = await Promise.race([
|
|
1576
|
-
info.promise.then((v) => ({ ok: true, content: v.content, durationMs: v.durationMs }))
|
|
1577
|
-
.catch((err) => ({ ok: false, err })),
|
|
1578
|
-
Promise.resolve(PENDING_SENTINEL),
|
|
1579
|
-
]);
|
|
1580
|
-
|
|
1590
|
+
// PR-L follow-up: deterministic readiness check. The info record
|
|
1591
|
+
// carries `ready / result / error` flags that are flipped from the
|
|
1592
|
+
// promise's then/catch handler; reading them here is purely
|
|
1593
|
+
// synchronous bookkeeping — no microtask race.
|
|
1581
1594
|
let content;
|
|
1582
1595
|
let trigger;
|
|
1583
1596
|
let durationMs = 0;
|
|
1584
|
-
if (
|
|
1585
|
-
//
|
|
1586
|
-
//
|
|
1587
|
-
|
|
1597
|
+
if (!info.ready) {
|
|
1598
|
+
// Still in flight — fall back to the exec-log stub. Detach the
|
|
1599
|
+
// unresolved promise so we don't leak it (handlers above already
|
|
1600
|
+
// swallow rejection by routing into info.error).
|
|
1588
1601
|
const entries = this.#execLog ? this.#execLog.readTurn(turnNumber) : [];
|
|
1589
1602
|
content = buildFallbackStub({ execLogEntries: entries, originalUserMsg: info.originalUserMsg || originalUserMsg });
|
|
1590
1603
|
trigger = 't2-fallback';
|
|
1591
|
-
} else if (
|
|
1592
|
-
|
|
1604
|
+
} else if (info.error) {
|
|
1605
|
+
// Promise rejected — leave history unchanged, no event.
|
|
1606
|
+
continue;
|
|
1607
|
+
} else if (info.result && typeof info.result.content === 'string' && info.result.content) {
|
|
1608
|
+
content = info.result.content;
|
|
1593
1609
|
trigger = 't2';
|
|
1594
|
-
durationMs =
|
|
1610
|
+
durationMs = info.result.durationMs || 0;
|
|
1595
1611
|
} else {
|
|
1596
|
-
//
|
|
1612
|
+
// Resolved but with no usable content — defensively skip.
|
|
1597
1613
|
continue;
|
|
1598
1614
|
}
|
|
1599
1615
|
|