dsh-plugin-soulbridge 0.0.118 → 0.0.119
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 +52 -28
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1053,42 +1053,64 @@ export async function runStageChain(stages) {
|
|
|
1053
1053
|
return false;
|
|
1054
1054
|
}
|
|
1055
1055
|
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1056
|
+
/**
|
|
1057
|
+
* The handoff consumer: at most one NON-EMPTY block per session.
|
|
1058
|
+
*
|
|
1059
|
+
* Deliberately once per session and only when the topic belongs to a
|
|
1060
|
+
* different session — repeating it every turn would make it wallpaper, and
|
|
1061
|
+
* re-reading the previous session's own topic is not a handoff.
|
|
1062
|
+
*
|
|
1063
|
+
* The read is injected so tests can drive the consumption contract without a
|
|
1064
|
+
* database. An EMPTY read does NOT consume the slot: a new session's first
|
|
1065
|
+
* turn often reads empty because the previous segment has not been flushed
|
|
1066
|
+
* yet (0028 decision 4 — handoff is a render, not a one-shot trigger), and
|
|
1067
|
+
* forfeiting then would drop the one chance this session has to be told where
|
|
1068
|
+
* it came from. A throwing read also leaves the slot open, as before.
|
|
1069
|
+
*/
|
|
1070
|
+
export function createHandoffTaker(readShot, logger, tracer) {
|
|
1071
|
+
/** Sessions that already received a non-empty handoff block. */
|
|
1060
1072
|
const handedOff = new Set();
|
|
1061
|
-
|
|
1062
|
-
/**
|
|
1063
|
-
* The "where were we" block: the last topic that finished.
|
|
1064
|
-
*
|
|
1065
|
-
* Deliberately once per session and only when that topic belongs to a
|
|
1066
|
-
* different session — repeating it every turn would make it wallpaper, and
|
|
1067
|
-
* re-reading the previous session's own topic is not a handoff.
|
|
1068
|
-
*/
|
|
1069
|
-
function takeHandoff(sessionId) {
|
|
1073
|
+
return (sessionId) => {
|
|
1070
1074
|
if (handedOff.has(sessionId)) return '';
|
|
1071
1075
|
try {
|
|
1072
|
-
const
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
if (block) {
|
|
1078
|
-
log(`handoff: "${last.thread.topic}" summary=${last.conclusion ? 'yes' : 'no'}`);
|
|
1079
|
-
trace({
|
|
1076
|
+
const shot = readShot(sessionId);
|
|
1077
|
+
if (shot.block) {
|
|
1078
|
+
handedOff.add(sessionId);
|
|
1079
|
+
logger(`handoff: "${shot.topic}" summary=${shot.summary ? 'yes' : 'no'}`);
|
|
1080
|
+
tracer({
|
|
1080
1081
|
event: 'handoff',
|
|
1081
|
-
thread:
|
|
1082
|
-
theme:
|
|
1083
|
-
hasSummary:
|
|
1082
|
+
thread: shot.thread,
|
|
1083
|
+
theme: shot.topic,
|
|
1084
|
+
hasSummary: shot.summary,
|
|
1084
1085
|
});
|
|
1085
1086
|
}
|
|
1086
|
-
return block;
|
|
1087
|
+
return shot.block;
|
|
1087
1088
|
} catch (error) {
|
|
1088
|
-
|
|
1089
|
+
logger(`handoff failed (will retry next turn): ${error instanceof Error ? error.message : String(error)}`);
|
|
1089
1090
|
return '';
|
|
1090
1091
|
}
|
|
1091
|
-
}
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
export function createPreStepHandler(deps) {
|
|
1096
|
+
/** sessionId -> last turn that received an injection */
|
|
1097
|
+
const injectedTurn = new Map();
|
|
1098
|
+
|
|
1099
|
+
// "Where were we": one non-empty block per session, retried until it ships.
|
|
1100
|
+
// The reader goes through ensureSoul() so the live store is read at call time.
|
|
1101
|
+
const takeHandoff = createHandoffTaker(
|
|
1102
|
+
(sessionId) => {
|
|
1103
|
+
const last = ensureSoul().lastClosedThread();
|
|
1104
|
+
return {
|
|
1105
|
+
block: renderHandoff(last, sessionId),
|
|
1106
|
+
thread: last?.thread?.id,
|
|
1107
|
+
topic: last?.thread?.topic,
|
|
1108
|
+
summary: Boolean(last?.conclusion),
|
|
1109
|
+
};
|
|
1110
|
+
},
|
|
1111
|
+
log,
|
|
1112
|
+
trace,
|
|
1113
|
+
);
|
|
1092
1114
|
|
|
1093
1115
|
return async (payload, next) => {
|
|
1094
1116
|
const decision = await next();
|
|
@@ -1495,8 +1517,10 @@ async function runExtraction(text, turnId, turnNo, origin = 'live', thread = nul
|
|
|
1495
1517
|
// Provenance is observable (ADR 0035): the fact count alone cannot tell whether
|
|
1496
1518
|
// anything came from my side of the exchange.
|
|
1497
1519
|
const inferred = result.facts.filter((fact) => fact.source === 'inferred').length;
|
|
1520
|
+
// "done", not "ok": the caller decides the queue outcome next (rules:model-failed
|
|
1521
|
+
// is NOT ok), and the old prefix read as success in every log review.
|
|
1498
1522
|
log(
|
|
1499
|
-
`extract
|
|
1523
|
+
`extract done: by=${draft.by} reason=${draft.reason ?? '-'} entities=${result.entities.length} facts=${result.facts.length}` +
|
|
1500
1524
|
` gaps=${result.gaps.length} skipped=${result.skipped.length} draftSkipped=${(draft.skipped ?? []).length}` +
|
|
1501
1525
|
` slots->text=${result.droppedSlots.length} holder->world=${result.holderFallbacks.length}` +
|
|
1502
1526
|
` inferred=${inferred}` +
|