soulbridge 0.0.18 → 0.0.20

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.
@@ -45,6 +45,12 @@ export interface SoulOptions {
45
45
  * duplicate rather than a second row. Set 0 to disable. Defaults to 30s.
46
46
  */
47
47
  dedupeWindowMs?: number;
48
+ /**
49
+ * Receives the model's raw reply, complete. Diagnostics belong to the adapter,
50
+ * not to core, which stays free of I/O; the DSH adapter appends it to a file
51
+ * so a parse failure is explainable instead of silent.
52
+ */
53
+ onLlmRaw?: (raw: string) => void;
48
54
  }
49
55
  export declare class Soul {
50
56
  readonly store: SoulStore;
@@ -56,6 +62,7 @@ export declare class Soul {
56
62
  readonly llm?: LlmFn;
57
63
  readonly llmMaxChars: number;
58
64
  readonly dedupeWindowMs: number;
65
+ readonly onLlmRaw?: (raw: string) => void;
59
66
  constructor(options: SoulOptions);
60
67
  /** Verb 1: session start. */
61
68
  context(request?: Partial<ContextRequest>): ContextResult;
@@ -31,6 +31,7 @@ export class Soul {
31
31
  llm;
32
32
  llmMaxChars;
33
33
  dedupeWindowMs;
34
+ onLlmRaw;
34
35
  constructor(options) {
35
36
  this.store = options.store;
36
37
  this.soulId = options.soulId;
@@ -41,6 +42,7 @@ export class Soul {
41
42
  this.llm = options.llm;
42
43
  this.llmMaxChars = options.llmMaxChars ?? 2000;
43
44
  this.dedupeWindowMs = options.dedupeWindowMs ?? 30_000;
45
+ this.onLlmRaw = options.onLlmRaw;
44
46
  if (!this.store.identity(this.soulId)) {
45
47
  this.store.ensureSoul({
46
48
  soulId: this.soulId,
@@ -132,11 +134,13 @@ export class Soul {
132
134
  let extractedBy = drafts.length > 0 ? 'rules' : 'none';
133
135
  // A model that says "nothing to keep" and a model that answers garbage used
134
136
  // to be indistinguishable; keep them apart for measurement.
135
- let llmOutcome = 'off';
137
+ let llmOutcome = this.llm
138
+ ? 'skipped-rules'
139
+ : 'off';
136
140
  if (drafts.length === 0 && turn.role === 'user' && this.llm) {
137
141
  if (turn.content.length <= this.llmMaxChars) {
138
142
  try {
139
- const draft = await llmExtractTurn(turn.content, this.llm);
143
+ const draft = await llmExtractTurn(turn.content, this.llm, this.onLlmRaw);
140
144
  if (draft) {
141
145
  drafts = [draft];
142
146
  extractedBy = 'llm';
@@ -153,7 +157,7 @@ export class Soul {
153
157
  }
154
158
  }
155
159
  else {
156
- llmOutcome = 'skipped';
160
+ llmOutcome = 'skipped-long';
157
161
  }
158
162
  }
159
163
  // Deterministic write gates: shape -> semantics -> per-turn budget. Whatever
@@ -75,5 +75,5 @@ export interface LlmDraft {
75
75
  * "nothing worth storing"; throws when the transport or the output is unusable,
76
76
  * so the caller can fall back to rules.
77
77
  */
78
- export declare function llmExtractTurn(text: string, llm: LlmFn): Promise<LlmDraft | null>;
78
+ export declare function llmExtractTurn(text: string, llm: LlmFn, onRaw?: (raw: string) => void): Promise<LlmDraft | null>;
79
79
  export {};
@@ -252,8 +252,9 @@ function parseLlmJson(raw) {
252
252
  * "nothing worth storing"; throws when the transport or the output is unusable,
253
253
  * so the caller can fall back to rules.
254
254
  */
255
- export async function llmExtractTurn(text, llm) {
255
+ export async function llmExtractTurn(text, llm, onRaw) {
256
256
  const raw = await llm(`${LLM_PROMPT}\n\n用户:${text}`);
257
+ onRaw?.(String(raw));
257
258
  const parsed = parseLlmJson(String(raw));
258
259
  if (!parsed)
259
260
  throw new Error('llm returned no JSON');
@@ -133,7 +133,7 @@ export interface ObserveResult {
133
133
  * What the optional model did, kept apart from `extractedBy` because a null
134
134
  * answer and a broken answer used to look identical in the logs.
135
135
  */
136
- llmOutcome: 'off' | 'skipped' | 'stored' | 'null' | 'failed';
136
+ llmOutcome: 'off' | 'skipped-rules' | 'skipped-long' | 'stored' | 'null' | 'failed';
137
137
  emotion: Emotion;
138
138
  /** Analysis of the user turn. Empty signal for assistant turns or on failure. */
139
139
  affect: AffectSignal;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "soulbridge",
3
- "version": "0.0.18",
3
+ "version": "0.0.20",
4
4
  "description": "A personality layer for any agent: continuity, forgetting, and growth across tools.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",