dsh-plugin-soulbridge 0.0.110 → 0.0.112
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 +49 -8
- package/package.json +2 -2
package/index.js
CHANGED
|
@@ -509,6 +509,11 @@ export function buildFactHooks(text, options = {}) {
|
|
|
509
509
|
lines.push(`- ${marker}[${fact.id}] ${escapeBlockText(fact.statement)}${when ? `(${escapeBlockText(when)})` : ''}`);
|
|
510
510
|
used.push(fact.id);
|
|
511
511
|
}
|
|
512
|
+
// The open questions about this person are what make the block actionable: without
|
|
513
|
+
// them the model sees history but cannot ask "how is she now".
|
|
514
|
+
for (const gap of ensureSoul().openGapsAbout(entity.id).slice(0, 2)) {
|
|
515
|
+
lines.push(`- 未闭合:${escapeBlockText(gap.topic)}`);
|
|
516
|
+
}
|
|
512
517
|
if (lines.length > 0) {
|
|
513
518
|
parts.push(`<about name="${escapeAttr(entity.name)}">\n${lines.join('\n')}\n</about>`);
|
|
514
519
|
}
|
|
@@ -1093,6 +1098,17 @@ function bumpExtractAttempt(turnId) {
|
|
|
1093
1098
|
);
|
|
1094
1099
|
}
|
|
1095
1100
|
|
|
1101
|
+
/**
|
|
1102
|
+
* After a thread was extracted, every queued turn of that thread is already covered.
|
|
1103
|
+
* Leaving them would extract the same topic again, turn by turn (0025).
|
|
1104
|
+
*/
|
|
1105
|
+
function removeExtractItemsOfThread(threadId, turnIds) {
|
|
1106
|
+
if (!threadId) return;
|
|
1107
|
+
writeExtractQueue(
|
|
1108
|
+
readExtractQueue().filter((item) => !turnIds.includes(item.turnId)),
|
|
1109
|
+
);
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1096
1112
|
/**
|
|
1097
1113
|
* Add one turn to the extraction queue.
|
|
1098
1114
|
*
|
|
@@ -1126,7 +1142,7 @@ function hasSubstance(text) {
|
|
|
1126
1142
|
return !/^(好的?|嗯+|哦+|可以|行|ok|okay|收到|明白|继续|好)[。!!.\s]*$/i.test(trimmed);
|
|
1127
1143
|
}
|
|
1128
1144
|
|
|
1129
|
-
async function runExtraction(text, turnId, turnNo, origin = 'live') {
|
|
1145
|
+
async function runExtraction(text, turnId, turnNo, origin = 'live', thread = null) {
|
|
1130
1146
|
const config = readConfig();
|
|
1131
1147
|
if (config.extract === 'off') return 'skipped-off';
|
|
1132
1148
|
if (typeof text !== 'string' || !hasSubstance(text)) {
|
|
@@ -1148,6 +1164,10 @@ async function runExtraction(text, turnId, turnNo, origin = 'live') {
|
|
|
1148
1164
|
const previousThreadId = soul.threadBefore(turnRow.sessionId, turnNo);
|
|
1149
1165
|
if (previousThreadId) context.previousThreadId = previousThreadId;
|
|
1150
1166
|
}
|
|
1167
|
+
// 0025: the unit is the whole topic when the turn belongs to one. The turns array is
|
|
1168
|
+
// numbered the way the prompt numbers them, and each fact may name its own turn.
|
|
1169
|
+
const turns = thread?.turns ?? null;
|
|
1170
|
+
const turnIds = turns ? Object.fromEntries(turns.map((entry) => [entry.turnNo, entry.id])) : undefined;
|
|
1151
1171
|
const draft = await soul.extract(text, {
|
|
1152
1172
|
llm: useLlm ? injectedLlm : undefined,
|
|
1153
1173
|
mode: useLlm ? 'auto' : 'rules',
|
|
@@ -1157,8 +1177,9 @@ async function runExtraction(text, turnId, turnNo, origin = 'live') {
|
|
|
1157
1177
|
// The exchange, not the message (ADR 0035): my reply is where promises and
|
|
1158
1178
|
// decisions live, and it is the only way "我的承诺" can ever be extracted.
|
|
1159
1179
|
assistantText: turnRow?.assistantText ?? undefined,
|
|
1180
|
+
turns: turns ?? undefined,
|
|
1160
1181
|
});
|
|
1161
|
-
const result = soul.commitDrafts(draft, { turnId });
|
|
1182
|
+
const result = soul.commitDrafts(draft, { turnId, turnIds });
|
|
1162
1183
|
// Attribution is written even when nothing was extracted: a turn that says
|
|
1163
1184
|
// "无" (nothing worth remembering) can still be part of a thread.
|
|
1164
1185
|
const attribution = soul.applyThread(draft.thread, {
|
|
@@ -1178,6 +1199,7 @@ async function runExtraction(text, turnId, turnNo, origin = 'live') {
|
|
|
1178
1199
|
` gaps=${result.gaps.length} skipped=${result.skipped.length}` +
|
|
1179
1200
|
` slots->text=${result.droppedSlots.length} holder->world=${result.holderFallbacks.length}` +
|
|
1180
1201
|
` inferred=${inferred}` +
|
|
1202
|
+
` unit=${turns ? `${turns.length}turns` : '1turn'} threadId=${thread?.id ?? '-'}` +
|
|
1181
1203
|
` ms=${ms} turn=${turnNo} origin=${origin}` +
|
|
1182
1204
|
` thread=${attribution.threadId}${attribution.created ? '(new)' : ''}` +
|
|
1183
1205
|
` join=${attribution.joinReason} threadLine=${draft.threadLine ?? 'rules'}` +
|
|
@@ -1271,9 +1293,21 @@ export async function drainExtractionQueue() {
|
|
|
1271
1293
|
try {
|
|
1272
1294
|
const item = pickExtractionItem(queue);
|
|
1273
1295
|
let text = '';
|
|
1296
|
+
let thread = null;
|
|
1274
1297
|
try {
|
|
1275
|
-
const
|
|
1298
|
+
const soul = ensureSoul();
|
|
1299
|
+
const turn = soul.turn(item.turnId);
|
|
1276
1300
|
text = turn?.userText ?? '';
|
|
1301
|
+
// A turn is a trigger, the topic is the unit (0025). When the turn is already in a
|
|
1302
|
+
// thread, extract that thread's turns instead of this message alone.
|
|
1303
|
+
const threadId = soul.turnThreadId(item.turnId);
|
|
1304
|
+
if (threadId) {
|
|
1305
|
+
const turns = soul.threadTurns(threadId);
|
|
1306
|
+
if (turns.length > 0) {
|
|
1307
|
+
thread = { id: threadId, turns };
|
|
1308
|
+
text = turns[turns.length - 1].userText;
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1277
1311
|
} catch {
|
|
1278
1312
|
text = '';
|
|
1279
1313
|
}
|
|
@@ -1283,14 +1317,21 @@ export async function drainExtractionQueue() {
|
|
|
1283
1317
|
return;
|
|
1284
1318
|
}
|
|
1285
1319
|
|
|
1286
|
-
const outcome = await runExtraction(text, item.turnId, item.turnNo, item.origin ?? 'live');
|
|
1320
|
+
const outcome = await runExtraction(text, item.turnId, item.turnNo, item.origin ?? 'live', thread);
|
|
1287
1321
|
const ok =
|
|
1288
1322
|
outcome === 'ok' || outcome === 'skipped-off' || outcome === 'skipped-empty' || outcome === 'rules-mode';
|
|
1289
1323
|
if (ok) {
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1324
|
+
if (thread) {
|
|
1325
|
+
removeExtractItemsOfThread(thread.id, thread.turns.map((entry) => entry.id));
|
|
1326
|
+
// Promotion is decided after the facts exist; a whole thread can hold several
|
|
1327
|
+
// corrected turns, so every turn is offered.
|
|
1328
|
+
for (const entry of thread.turns) feedClassifyFromTurn(entry.id);
|
|
1329
|
+
} else {
|
|
1330
|
+
removeExtractItem(item.turnId);
|
|
1331
|
+
// Promotion is decided after the facts exist: a corrected turn's lessons enter
|
|
1332
|
+
// the classification queue here, which is the only live producer it has.
|
|
1333
|
+
feedClassifyFromTurn(item.turnId);
|
|
1334
|
+
}
|
|
1294
1335
|
trace({ event: 'extract-queue', drained: 1, remaining: readExtractQueue().length, outcome });
|
|
1295
1336
|
return true;
|
|
1296
1337
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-soulbridge",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.112",
|
|
4
4
|
"description": "Inject soulbridge context into DSH sessions and feed user turns back as observations.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -42,6 +42,6 @@
|
|
|
42
42
|
"setup": "node scripts/install-hooks.mjs"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"soulbridge": ">=0.0.
|
|
45
|
+
"soulbridge": ">=0.0.112 <0.1.0"
|
|
46
46
|
}
|
|
47
47
|
}
|