dsh-plugin-soulbridge 0.0.120 → 0.0.121

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.
Files changed (2) hide show
  1. package/index.js +41 -9
  2. package/package.json +2 -2
package/index.js CHANGED
@@ -1677,15 +1677,30 @@ async function runExtraction(text, turnId, turnNo, origin = 'live', unit = null)
1677
1677
  });
1678
1678
  const result = soul.commitDrafts(draft, { turnId, turnIds });
1679
1679
  // Attribution is written even when nothing was extracted: a turn that says
1680
- // "无" (nothing worth remembering) can still be part of a thread.
1681
- const attribution = soul.applyThread(draft.thread, {
1680
+ // "无" (nothing worth remembering) can still be part of a thread. The unit is
1681
+ // fanned over per turn (0028 ③): every turn of the segment gets attached, a
1682
+ // turn no span covers chains to the previous one, and the continue/new/
1683
+ // fallback chain inside applyThread is untouched (ADR 0034).
1684
+ const spanTurns = (turns ?? [{ turnNo: 1, id: turnId, userText: text }]).map((entry) => ({
1685
+ promptNo: entry.turnNo,
1686
+ turnNo: entry.hostTurnNo ?? turnNo,
1687
+ turnId: entry.id,
1688
+ at: entry.startedAt ?? Date.now(),
1689
+ fallbackTopic: firstSentence(entry.userText ?? text),
1690
+ }));
1691
+ const applied = soul.applyThreadSpans(draft.spans, {
1682
1692
  sessionId: turnRow?.sessionId ?? 'unknown',
1683
- turnNo,
1684
- turnId,
1685
- at: Date.now(),
1686
- fallbackTopic: firstSentence(text),
1693
+ turns: spanTurns,
1687
1694
  previousThreadId: context.previousThreadId,
1688
1695
  });
1696
+ const attribution = applied[0];
1697
+ // One thread keeps the old line's thread part byte for byte (`thread=th_xxx(new)`);
1698
+ // a split segment lists every thread it produced, and `join=` is the unit's
1699
+ // per-turn reasons joined with `+` (one value only when they agree) — the head
1700
+ // alone no longer describes what the rest of the segment did.
1701
+ const threadIds = [...new Set(applied.map((entry) => entry.threadId))];
1702
+ const createdNew = applied.filter((entry) => entry.created).length;
1703
+ const joins = [...new Set(applied.map((entry) => entry.joinReason))].join('+');
1689
1704
  const ms = Date.now() - started;
1690
1705
  // Provenance is observable (ADR 0035): the fact count alone cannot tell whether
1691
1706
  // anything came from my side of the exchange.
@@ -1699,9 +1714,10 @@ async function runExtraction(text, turnId, turnNo, origin = 'live', unit = null)
1699
1714
  ` inferred=${inferred}` +
1700
1715
  ` unit=${turns ? `${turns.length}turns` : '1turn'}` +
1701
1716
  ` ms=${ms} turn=${turnNo} origin=${origin}` +
1702
- ` thread=${attribution.threadId}${attribution.created ? '(new)' : ''}` +
1703
- ` join=${attribution.joinReason} threadLine=${draft.threadLine ?? 'rules'}` +
1704
- `${attribution.topicUpdated ? ' topic=updated' : ''}`,
1717
+ ` thread=${threadIds.join(',')}` +
1718
+ `${createdNew > 0 ? (threadIds.length === 1 ? '(new)' : `(${createdNew} new)`) : ''}` +
1719
+ ` join=${joins} threadLine=${draft.threadLine ?? 'rules'}` +
1720
+ `${applied.some((entry) => entry.topicUpdated) ? ' topic=updated' : ''}`,
1705
1721
  );
1706
1722
  for (const skipped of result.skipped.slice(0, 3)) {
1707
1723
  log(` extract skipped: ${skipped.what} (${skipped.reason})`);
@@ -1732,6 +1748,18 @@ async function runExtraction(text, turnId, turnNo, origin = 'live', unit = null)
1732
1748
  threadTopic: attribution.topic,
1733
1749
  threadLine: draft.threadLine ?? null,
1734
1750
  topicUpdated: attribution.topicUpdated,
1751
+ // Only when the segment actually split: one thread is the old shape, and the
1752
+ // per-turn runs are what makes a wrong split diagnosable later (0028 ③).
1753
+ ...(applied.length > 1
1754
+ ? {
1755
+ threadSpans: applied.map((entry, index) => ({
1756
+ span: spanTurns[index].promptNo,
1757
+ thread: entry.threadId,
1758
+ join: entry.joinReason,
1759
+ created: entry.created,
1760
+ })),
1761
+ }
1762
+ : {}),
1735
1763
  origin,
1736
1764
  joinReason: attribution.joinReason,
1737
1765
  ms,
@@ -1851,7 +1879,11 @@ export async function drainExtractionQueue(options = {}) {
1851
1879
  // turn, so a fact's 轮次 lands on source_turn_id.
1852
1880
  const turns = taken.map((entry, index) => ({
1853
1881
  id: entry.item.turnId,
1882
+ // Prompt numbering (1..n, what the model's spans match) and the host's real
1883
+ // turn number (threads count turns that jump by 10–30) are different numbers;
1884
+ // both ride along so attribution never has to guess which is which.
1854
1885
  turnNo: index + 1,
1886
+ hostTurnNo: entry.row.turn,
1855
1887
  userText: entry.row.userText,
1856
1888
  assistantText: entry.row.assistantText,
1857
1889
  startedAt: entry.row.at,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-soulbridge",
3
- "version": "0.0.120",
3
+ "version": "0.0.121",
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.116 <0.1.0"
45
+ "soulbridge": ">=0.0.117 <0.1.0"
46
46
  }
47
47
  }