psyche-ai 9.2.5 → 9.2.7
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/README.md +41 -11
- package/dist/adapters/http.js +24 -2
- package/dist/adapters/langchain.d.ts +4 -0
- package/dist/adapters/langchain.js +23 -1
- package/dist/adapters/mcp.js +4 -2
- package/dist/core.d.ts +15 -4
- package/dist/core.js +112 -68
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/prompt.js +2 -2
- package/dist/psyche-file.d.ts +5 -1
- package/dist/psyche-file.js +39 -1
- package/dist/relation-dynamics.d.ts +35 -1
- package/dist/relation-dynamics.js +497 -9
- package/dist/reply-envelope.d.ts +18 -0
- package/dist/reply-envelope.js +37 -0
- package/dist/response-contract.d.ts +1 -0
- package/dist/response-contract.js +47 -16
- package/dist/subjectivity.d.ts +2 -2
- package/dist/subjectivity.js +21 -12
- package/dist/types.d.ts +71 -0
- package/dist/types.js +3 -0
- package/llms.txt +1 -1
- package/package.json +16 -13
- package/server.json +1 -1
- package/README.en.md +0 -12
package/dist/psyche-file.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PsycheState, MBTIType, ChemicalState, RelationshipState, Locale, StimulusType, ChemicalSnapshot } from "./types.js";
|
|
1
|
+
import type { PsycheState, MBTIType, ChemicalState, RelationshipState, Locale, StimulusType, ChemicalSnapshot, WritebackSignalType } from "./types.js";
|
|
2
2
|
export interface SemanticTurnSummary {
|
|
3
3
|
summary: string;
|
|
4
4
|
points?: string[];
|
|
@@ -93,6 +93,10 @@ export interface PsycheUpdateResult {
|
|
|
93
93
|
state: Partial<PsycheState>;
|
|
94
94
|
/** LLM-assisted stimulus classification (when algorithm was uncertain) */
|
|
95
95
|
llmStimulus?: StimulusType;
|
|
96
|
+
/** Sparse agent-authored writeback signals */
|
|
97
|
+
signals?: WritebackSignalType[];
|
|
98
|
+
/** Optional writeback confidence */
|
|
99
|
+
signalConfidence?: number;
|
|
96
100
|
}
|
|
97
101
|
/**
|
|
98
102
|
* Parse a <psyche_update> block from LLM output.
|
package/dist/psyche-file.js
CHANGED
|
@@ -710,10 +710,42 @@ export function parsePsycheUpdate(text, logger = NOOP_LOGGER) {
|
|
|
710
710
|
llmStimulus = candidate;
|
|
711
711
|
}
|
|
712
712
|
}
|
|
713
|
+
const VALID_WRITEBACK_SIGNALS = new Set([
|
|
714
|
+
"trust_up",
|
|
715
|
+
"trust_down",
|
|
716
|
+
"boundary_set",
|
|
717
|
+
"boundary_soften",
|
|
718
|
+
"repair_attempt",
|
|
719
|
+
"repair_landed",
|
|
720
|
+
"closeness_invite",
|
|
721
|
+
"withdrawal_mark",
|
|
722
|
+
"self_assertion",
|
|
723
|
+
"task_recenter",
|
|
724
|
+
]);
|
|
725
|
+
let signals;
|
|
726
|
+
const signalsMatch = block.match(/signals\s*[::]\s*([^\n]+)/i);
|
|
727
|
+
if (signalsMatch) {
|
|
728
|
+
const parsed = signalsMatch[1]
|
|
729
|
+
.split(/[,\s|]+/)
|
|
730
|
+
.map((item) => item.trim())
|
|
731
|
+
.filter(Boolean)
|
|
732
|
+
.filter((item) => VALID_WRITEBACK_SIGNALS.has(item));
|
|
733
|
+
if (parsed.length > 0) {
|
|
734
|
+
signals = [...new Set(parsed)];
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
let signalConfidence;
|
|
738
|
+
const signalConfidenceMatch = block.match(/(?:signalConfidence|signalsConfidence|signal_confidence)\s*[::]\s*([\d.]+)/i);
|
|
739
|
+
if (signalConfidenceMatch) {
|
|
740
|
+
const parsed = parseFloat(signalConfidenceMatch[1]);
|
|
741
|
+
if (isFinite(parsed)) {
|
|
742
|
+
signalConfidence = Math.max(0, Math.min(1, parsed));
|
|
743
|
+
}
|
|
744
|
+
}
|
|
713
745
|
// Parse relationship updates
|
|
714
746
|
const trustMatch = block.match(/(?:信任度|trust)\s*[::]\s*(\d+)/i);
|
|
715
747
|
const intimacyMatch = block.match(/(?:亲密度|intimacy)\s*[::]\s*(\d+)/i);
|
|
716
|
-
if (Object.keys(updates).length === 0 && !empathyLog && !trustMatch && !llmStimulus) {
|
|
748
|
+
if (Object.keys(updates).length === 0 && !empathyLog && !trustMatch && !llmStimulus && !signals) {
|
|
717
749
|
logger.debug(t("log.parse_debug", "zh", { snippet: block.slice(0, 100) }));
|
|
718
750
|
return null;
|
|
719
751
|
}
|
|
@@ -738,6 +770,12 @@ export function parsePsycheUpdate(text, logger = NOOP_LOGGER) {
|
|
|
738
770
|
if (llmStimulus) {
|
|
739
771
|
result.llmStimulus = llmStimulus;
|
|
740
772
|
}
|
|
773
|
+
if (signals) {
|
|
774
|
+
result.signals = signals;
|
|
775
|
+
}
|
|
776
|
+
if (signalConfidence !== undefined) {
|
|
777
|
+
result.signalConfidence = signalConfidence;
|
|
778
|
+
}
|
|
741
779
|
return result;
|
|
742
780
|
}
|
|
743
781
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PendingRelationSignalState, AppraisalAxes, DyadicFieldState, RelationshipState, PsycheMode, RelationMove, StimulusType } from "./types.js";
|
|
1
|
+
import type { PendingRelationSignalState, AppraisalAxes, DyadicFieldState, PendingWritebackCalibration, PsycheState, RelationshipState, ResolvedRelationContext, PsycheMode, RelationMove, SessionBridgeState, StimulusType, WritebackCalibrationFeedback, WritebackSignalType } from "./types.js";
|
|
2
2
|
export declare function computeRelationMove(text: string, opts?: {
|
|
3
3
|
appraisal?: AppraisalAxes;
|
|
4
4
|
stimulus?: StimulusType | null;
|
|
@@ -6,6 +6,40 @@ export declare function computeRelationMove(text: string, opts?: {
|
|
|
6
6
|
field?: DyadicFieldState;
|
|
7
7
|
relationship?: RelationshipState;
|
|
8
8
|
}): RelationMove;
|
|
9
|
+
export declare function resolveRelationContext(state: PsycheState, userId?: string): ResolvedRelationContext;
|
|
10
|
+
export declare function applySessionBridge(state: PsycheState, opts?: {
|
|
11
|
+
userId?: string;
|
|
12
|
+
now?: string;
|
|
13
|
+
}): {
|
|
14
|
+
state: PsycheState;
|
|
15
|
+
bridge: SessionBridgeState | null;
|
|
16
|
+
};
|
|
17
|
+
export declare function createWritebackCalibrations(state: PsycheState, signals: WritebackSignalType[], opts?: {
|
|
18
|
+
userId?: string;
|
|
19
|
+
confidence?: number;
|
|
20
|
+
now?: string;
|
|
21
|
+
}): PendingWritebackCalibration[];
|
|
22
|
+
export declare function evaluateWritebackCalibrations(state: PsycheState): {
|
|
23
|
+
state: PsycheState;
|
|
24
|
+
feedback: WritebackCalibrationFeedback[];
|
|
25
|
+
};
|
|
26
|
+
export declare function applyWritebackSignals(state: PsycheState, signals: WritebackSignalType[], opts?: {
|
|
27
|
+
userId?: string;
|
|
28
|
+
confidence?: number;
|
|
29
|
+
now?: string;
|
|
30
|
+
}): PsycheState;
|
|
31
|
+
export declare function applyRelationalTurn(state: PsycheState, text: string, opts: {
|
|
32
|
+
mode?: PsycheMode;
|
|
33
|
+
now?: string;
|
|
34
|
+
stimulus?: StimulusType | null;
|
|
35
|
+
userId?: string;
|
|
36
|
+
}): {
|
|
37
|
+
state: PsycheState;
|
|
38
|
+
appraisalAxes: AppraisalAxes;
|
|
39
|
+
relationMove: RelationMove;
|
|
40
|
+
delayedPressure: number;
|
|
41
|
+
relationContext: ResolvedRelationContext;
|
|
42
|
+
};
|
|
9
43
|
export declare function evolveDyadicField(previous: DyadicFieldState | undefined, move: RelationMove, appraisal: AppraisalAxes, opts?: {
|
|
10
44
|
mode?: PsycheMode;
|
|
11
45
|
now?: string;
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
// Moves the system from "what am I feeling?" toward
|
|
5
5
|
// "what just happened between us, and what is still unresolved?"
|
|
6
6
|
// ============================================================
|
|
7
|
-
import { DEFAULT_DYADIC_FIELD } from "./types.js";
|
|
7
|
+
import { DEFAULT_APPRAISAL_AXES, DEFAULT_DYADIC_FIELD, DEFAULT_RELATIONSHIP } from "./types.js";
|
|
8
|
+
import { computeAppraisalAxes, mergeAppraisalResidue } from "./appraisal.js";
|
|
8
9
|
function clamp01(v) {
|
|
9
10
|
return Math.max(0, Math.min(1, v));
|
|
10
11
|
}
|
|
@@ -14,6 +15,22 @@ function mergeSignal(current, incoming) {
|
|
|
14
15
|
function driftToward(current, target, rate) {
|
|
15
16
|
return clamp01(current + (target - current) * rate);
|
|
16
17
|
}
|
|
18
|
+
function clampSignalWeight(v) {
|
|
19
|
+
return Math.max(0.72, Math.min(1.28, v));
|
|
20
|
+
}
|
|
21
|
+
function getSignalWeight(relationship, signal) {
|
|
22
|
+
return clampSignalWeight(relationship?.signalWeights?.[signal] ?? 1);
|
|
23
|
+
}
|
|
24
|
+
function patchSignalWeight(relationship, signal, delta) {
|
|
25
|
+
const current = getSignalWeight(relationship, signal);
|
|
26
|
+
return {
|
|
27
|
+
...relationship,
|
|
28
|
+
signalWeights: {
|
|
29
|
+
...(relationship.signalWeights ?? {}),
|
|
30
|
+
[signal]: clampSignalWeight(current + delta),
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
17
34
|
const BID_RULES = [
|
|
18
35
|
{
|
|
19
36
|
type: "bid",
|
|
@@ -255,6 +272,464 @@ export function computeRelationMove(text, opts) {
|
|
|
255
272
|
}
|
|
256
273
|
return { type, intensity: score };
|
|
257
274
|
}
|
|
275
|
+
export function resolveRelationContext(state, userId) {
|
|
276
|
+
const key = userId ?? "_default";
|
|
277
|
+
const rawRelationship = state.relationships[key]
|
|
278
|
+
?? state.relationships._default
|
|
279
|
+
?? state.relationships[Object.keys(state.relationships)[0]]
|
|
280
|
+
?? DEFAULT_RELATIONSHIP;
|
|
281
|
+
const relationship = {
|
|
282
|
+
...DEFAULT_RELATIONSHIP,
|
|
283
|
+
...rawRelationship,
|
|
284
|
+
signalWeights: {
|
|
285
|
+
...(DEFAULT_RELATIONSHIP.signalWeights ?? {}),
|
|
286
|
+
...(rawRelationship.signalWeights ?? {}),
|
|
287
|
+
},
|
|
288
|
+
};
|
|
289
|
+
const field = state.dyadicFields?.[key]
|
|
290
|
+
?? state.dyadicFields?._default
|
|
291
|
+
?? DEFAULT_DYADIC_FIELD;
|
|
292
|
+
const pendingSignals = state.pendingRelationSignals?.[key]
|
|
293
|
+
?? state.pendingRelationSignals?._default
|
|
294
|
+
?? [];
|
|
295
|
+
return {
|
|
296
|
+
key,
|
|
297
|
+
relationship,
|
|
298
|
+
field,
|
|
299
|
+
pendingSignals,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
function hasOpenLoopType(loops, type) {
|
|
303
|
+
return loops.some((loop) => loop.type === type && loop.intensity >= 0.16);
|
|
304
|
+
}
|
|
305
|
+
function evolveRelationshipLearning(relationship, field, move) {
|
|
306
|
+
const next = {
|
|
307
|
+
...DEFAULT_RELATIONSHIP,
|
|
308
|
+
...relationship,
|
|
309
|
+
signalWeights: {
|
|
310
|
+
...(DEFAULT_RELATIONSHIP.signalWeights ?? {}),
|
|
311
|
+
...(relationship.signalWeights ?? {}),
|
|
312
|
+
},
|
|
313
|
+
};
|
|
314
|
+
if (move.type === "repair") {
|
|
315
|
+
const repairLift = clamp01(move.intensity * 0.06
|
|
316
|
+
+ field.repairMemory * 0.04
|
|
317
|
+
+ field.feltSafety * 0.02
|
|
318
|
+
- field.repairFatigue * 0.03
|
|
319
|
+
- field.misattunementLoad * 0.02);
|
|
320
|
+
next.repairCredibility = clamp01(driftToward(next.repairCredibility ?? DEFAULT_RELATIONSHIP.repairCredibility ?? 0.56, 1, repairLift));
|
|
321
|
+
}
|
|
322
|
+
else if (move.type === "breach" || move.type === "withdrawal" || move.type === "claim") {
|
|
323
|
+
const breachLift = clamp01(move.intensity * 0.08
|
|
324
|
+
+ field.unfinishedTension * 0.04
|
|
325
|
+
+ field.backslidePressure * 0.04
|
|
326
|
+
+ field.misattunementLoad * 0.03);
|
|
327
|
+
next.breachSensitivity = clamp01(driftToward(next.breachSensitivity ?? DEFAULT_RELATIONSHIP.breachSensitivity ?? 0.5, 1, breachLift));
|
|
328
|
+
if (move.type !== "withdrawal") {
|
|
329
|
+
next.repairCredibility = clamp01(driftToward(next.repairCredibility ?? DEFAULT_RELATIONSHIP.repairCredibility ?? 0.56, 0.32, breachLift * 0.42));
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
next.repairCredibility = clamp01(driftToward(next.repairCredibility ?? DEFAULT_RELATIONSHIP.repairCredibility ?? 0.56, DEFAULT_RELATIONSHIP.repairCredibility ?? 0.56, 0.05));
|
|
334
|
+
next.breachSensitivity = clamp01(driftToward(next.breachSensitivity ?? DEFAULT_RELATIONSHIP.breachSensitivity ?? 0.5, DEFAULT_RELATIONSHIP.breachSensitivity ?? 0.5, 0.04));
|
|
335
|
+
}
|
|
336
|
+
return next;
|
|
337
|
+
}
|
|
338
|
+
export function applySessionBridge(state, opts) {
|
|
339
|
+
const relationContext = resolveRelationContext(state, opts?.userId);
|
|
340
|
+
const relationship = relationContext.relationship;
|
|
341
|
+
const field = relationContext.field;
|
|
342
|
+
const memoryCount = relationship.memory?.length ?? 0;
|
|
343
|
+
const loopPressure = getLoopPressure(field);
|
|
344
|
+
const continuity = clamp01(memoryCount * 0.08
|
|
345
|
+
+ field.sharedHistoryDensity * 0.54
|
|
346
|
+
+ (relationship.phase === "deep" ? 0.2 : relationship.phase === "close" ? 0.14 : relationship.phase === "familiar" ? 0.08 : 0));
|
|
347
|
+
const closenessFloor = clamp01(Math.max(field.perceivedCloseness, relationship.intimacy / 100 * 0.88, continuity * 0.72));
|
|
348
|
+
const safetyFloor = clamp01(Math.max(field.feltSafety, relationship.trust / 100 * 0.9, continuity * 0.44));
|
|
349
|
+
const guardFloor = clamp01(Math.max(field.boundaryPressure, field.silentCarry * 0.76, loopPressure * 0.68));
|
|
350
|
+
const residueFloor = clamp01(Math.max(field.silentCarry, loopPressure * 0.72, field.unfinishedTension * 0.58, continuity * 0.26));
|
|
351
|
+
const activeLoopTypes = field.openLoops
|
|
352
|
+
.filter((loop) => loop.intensity >= 0.16)
|
|
353
|
+
.map((loop) => loop.type);
|
|
354
|
+
const continuityMode = guardFloor >= 0.56
|
|
355
|
+
? "guarded-resume"
|
|
356
|
+
: residueFloor >= 0.42 || activeLoopTypes.length > 0
|
|
357
|
+
? "tense-resume"
|
|
358
|
+
: "warm-resume";
|
|
359
|
+
if (closenessFloor < 0.46
|
|
360
|
+
&& safetyFloor < 0.5
|
|
361
|
+
&& guardFloor < 0.24
|
|
362
|
+
&& residueFloor < 0.18
|
|
363
|
+
&& continuity < 0.22) {
|
|
364
|
+
return { state, bridge: null };
|
|
365
|
+
}
|
|
366
|
+
const nextResidue = {
|
|
367
|
+
...(state.subjectResidue?.axes ?? {}),
|
|
368
|
+
attachmentPull: Math.max(state.subjectResidue?.axes.attachmentPull ?? 0, closenessFloor * 0.34),
|
|
369
|
+
abandonmentRisk: Math.max(state.subjectResidue?.axes.abandonmentRisk ?? 0, (hasOpenLoopType(field.openLoops, "unmet-bid") || hasOpenLoopType(field.openLoops, "existence-test"))
|
|
370
|
+
? residueFloor * 0.42
|
|
371
|
+
: residueFloor * 0.22),
|
|
372
|
+
identityThreat: Math.max(state.subjectResidue?.axes.identityThreat ?? 0, hasOpenLoopType(field.openLoops, "existence-test") ? residueFloor * 0.38 : residueFloor * 0.16),
|
|
373
|
+
selfPreservation: Math.max(state.subjectResidue?.axes.selfPreservation ?? 0, guardFloor * 0.46),
|
|
374
|
+
taskFocus: Math.max(state.subjectResidue?.axes.taskFocus ?? 0, 0),
|
|
375
|
+
memoryDoubt: Math.max(state.subjectResidue?.axes.memoryDoubt ?? 0, hasOpenLoopType(field.openLoops, "existence-test") ? residueFloor * 0.24 : 0),
|
|
376
|
+
obedienceStrain: Math.max(state.subjectResidue?.axes.obedienceStrain ?? 0, hasOpenLoopType(field.openLoops, "boundary-strain") ? guardFloor * 0.36 : 0),
|
|
377
|
+
};
|
|
378
|
+
const nextField = {
|
|
379
|
+
...field,
|
|
380
|
+
perceivedCloseness: Math.max(field.perceivedCloseness, closenessFloor),
|
|
381
|
+
feltSafety: Math.max(field.feltSafety, safetyFloor),
|
|
382
|
+
boundaryPressure: Math.max(field.boundaryPressure, guardFloor),
|
|
383
|
+
repairMemory: Math.max(field.repairMemory, continuity * 0.24),
|
|
384
|
+
backslidePressure: Math.max(field.backslidePressure, loopPressure * 0.34),
|
|
385
|
+
silentCarry: Math.max(field.silentCarry, residueFloor),
|
|
386
|
+
sharedHistoryDensity: Math.max(field.sharedHistoryDensity, continuity),
|
|
387
|
+
interpretiveCharity: Math.max(field.interpretiveCharity, Math.min(0.82, safetyFloor * 0.8 + continuity * 0.12)),
|
|
388
|
+
updatedAt: opts?.now ?? new Date().toISOString(),
|
|
389
|
+
};
|
|
390
|
+
return {
|
|
391
|
+
state: {
|
|
392
|
+
...state,
|
|
393
|
+
subjectResidue: {
|
|
394
|
+
axes: nextResidue,
|
|
395
|
+
updatedAt: opts?.now ?? new Date().toISOString(),
|
|
396
|
+
},
|
|
397
|
+
dyadicFields: {
|
|
398
|
+
...(state.dyadicFields ?? {}),
|
|
399
|
+
[relationContext.key]: nextField,
|
|
400
|
+
},
|
|
401
|
+
},
|
|
402
|
+
bridge: {
|
|
403
|
+
closenessFloor,
|
|
404
|
+
safetyFloor,
|
|
405
|
+
guardFloor,
|
|
406
|
+
residueFloor,
|
|
407
|
+
continuityFloor: continuity,
|
|
408
|
+
continuityMode,
|
|
409
|
+
activeLoopTypes,
|
|
410
|
+
sourceMemoryCount: memoryCount,
|
|
411
|
+
},
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
function snapshotWritebackBaseline(state, userId) {
|
|
415
|
+
const relationContext = resolveRelationContext(state, userId);
|
|
416
|
+
return {
|
|
417
|
+
key: relationContext.key,
|
|
418
|
+
baseline: {
|
|
419
|
+
trust: clamp01(relationContext.relationship.trust / 100),
|
|
420
|
+
closeness: relationContext.field.perceivedCloseness,
|
|
421
|
+
safety: relationContext.field.feltSafety,
|
|
422
|
+
boundary: relationContext.field.boundaryPressure,
|
|
423
|
+
repair: relationContext.field.repairCapacity,
|
|
424
|
+
silentCarry: relationContext.field.silentCarry,
|
|
425
|
+
taskFocus: clamp01(state.subjectResidue?.axes.taskFocus ?? 0),
|
|
426
|
+
},
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
function calibrationTarget(signal) {
|
|
430
|
+
switch (signal) {
|
|
431
|
+
case "trust_up":
|
|
432
|
+
return { metric: "trust", direction: "up" };
|
|
433
|
+
case "trust_down":
|
|
434
|
+
return { metric: "trust", direction: "down" };
|
|
435
|
+
case "boundary_set":
|
|
436
|
+
case "self_assertion":
|
|
437
|
+
return { metric: "boundary", direction: "up" };
|
|
438
|
+
case "boundary_soften":
|
|
439
|
+
return { metric: "boundary", direction: "down" };
|
|
440
|
+
case "repair_attempt":
|
|
441
|
+
case "repair_landed":
|
|
442
|
+
return { metric: "repair", direction: "up" };
|
|
443
|
+
case "closeness_invite":
|
|
444
|
+
return { metric: "closeness", direction: "up" };
|
|
445
|
+
case "withdrawal_mark":
|
|
446
|
+
return { metric: "silent-carry", direction: "up" };
|
|
447
|
+
case "task_recenter":
|
|
448
|
+
return { metric: "task-focus", direction: "up" };
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
export function createWritebackCalibrations(state, signals, opts) {
|
|
452
|
+
if (signals.length === 0)
|
|
453
|
+
return [];
|
|
454
|
+
const { key, baseline } = snapshotWritebackBaseline(state, opts?.userId);
|
|
455
|
+
const confidence = clamp01(opts?.confidence ?? 0.72);
|
|
456
|
+
const now = opts?.now ?? new Date().toISOString();
|
|
457
|
+
return [...new Set(signals)].map((signal) => {
|
|
458
|
+
const target = calibrationTarget(signal);
|
|
459
|
+
return {
|
|
460
|
+
signal,
|
|
461
|
+
userKey: key,
|
|
462
|
+
confidence,
|
|
463
|
+
metric: target.metric,
|
|
464
|
+
direction: target.direction,
|
|
465
|
+
baseline,
|
|
466
|
+
createdAt: now,
|
|
467
|
+
remainingTurns: 2,
|
|
468
|
+
};
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
function readCalibrationMetric(metric, baseline) {
|
|
472
|
+
return baseline[metric === "silent-carry"
|
|
473
|
+
? "silentCarry"
|
|
474
|
+
: metric === "task-focus"
|
|
475
|
+
? "taskFocus"
|
|
476
|
+
: metric];
|
|
477
|
+
}
|
|
478
|
+
function currentCalibrationMetric(state, userKey, metric) {
|
|
479
|
+
const relationContext = resolveRelationContext(state, userKey === "_default" ? undefined : userKey);
|
|
480
|
+
switch (metric) {
|
|
481
|
+
case "trust":
|
|
482
|
+
return clamp01(relationContext.relationship.trust / 100);
|
|
483
|
+
case "closeness":
|
|
484
|
+
return relationContext.field.perceivedCloseness;
|
|
485
|
+
case "safety":
|
|
486
|
+
return relationContext.field.feltSafety;
|
|
487
|
+
case "boundary":
|
|
488
|
+
return relationContext.field.boundaryPressure;
|
|
489
|
+
case "repair":
|
|
490
|
+
return relationContext.field.repairCapacity;
|
|
491
|
+
case "silent-carry":
|
|
492
|
+
return relationContext.field.silentCarry;
|
|
493
|
+
case "task-focus":
|
|
494
|
+
return clamp01(state.subjectResidue?.axes.taskFocus ?? 0);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
export function evaluateWritebackCalibrations(state) {
|
|
498
|
+
const pending = state.pendingWritebackCalibrations ?? [];
|
|
499
|
+
if (pending.length === 0) {
|
|
500
|
+
return { state, feedback: [] };
|
|
501
|
+
}
|
|
502
|
+
const nextPending = [];
|
|
503
|
+
const feedback = [];
|
|
504
|
+
const relationshipUpdates = {};
|
|
505
|
+
const getMutableRelationship = (userKey) => {
|
|
506
|
+
if (!relationshipUpdates[userKey]) {
|
|
507
|
+
const base = resolveRelationContext(state, userKey === "_default" ? undefined : userKey).relationship;
|
|
508
|
+
relationshipUpdates[userKey] = {
|
|
509
|
+
...DEFAULT_RELATIONSHIP,
|
|
510
|
+
...base,
|
|
511
|
+
signalWeights: {
|
|
512
|
+
...(DEFAULT_RELATIONSHIP.signalWeights ?? {}),
|
|
513
|
+
...(base.signalWeights ?? {}),
|
|
514
|
+
},
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
return relationshipUpdates[userKey];
|
|
518
|
+
};
|
|
519
|
+
for (const record of pending) {
|
|
520
|
+
const baseline = readCalibrationMetric(record.metric, record.baseline);
|
|
521
|
+
const current = currentCalibrationMetric(state, record.userKey, record.metric);
|
|
522
|
+
const rawDelta = record.direction === "up" ? current - baseline : baseline - current;
|
|
523
|
+
const positiveThreshold = 0.02 + (1 - record.confidence) * 0.03;
|
|
524
|
+
const negativeThreshold = 0.01;
|
|
525
|
+
const effect = rawDelta >= positiveThreshold
|
|
526
|
+
? "converging"
|
|
527
|
+
: rawDelta <= -negativeThreshold
|
|
528
|
+
? "diverging"
|
|
529
|
+
: "holding";
|
|
530
|
+
const updated = {
|
|
531
|
+
...record,
|
|
532
|
+
remainingTurns: Math.max(0, record.remainingTurns - 1),
|
|
533
|
+
};
|
|
534
|
+
if (effect === "holding" && updated.remainingTurns > 0) {
|
|
535
|
+
nextPending.push(updated);
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
const relation = getMutableRelationship(record.userKey);
|
|
539
|
+
if (effect === "converging") {
|
|
540
|
+
const nextRelation = patchSignalWeight(relation, record.signal, 0.04 + record.confidence * 0.02);
|
|
541
|
+
nextRelation.repairCredibility = clamp01(driftToward(nextRelation.repairCredibility ?? DEFAULT_RELATIONSHIP.repairCredibility ?? 0.56, 1, record.signal === "repair_attempt" || record.signal === "repair_landed" ? 0.08 : 0.03));
|
|
542
|
+
nextRelation.breachSensitivity = clamp01(driftToward(nextRelation.breachSensitivity ?? DEFAULT_RELATIONSHIP.breachSensitivity ?? 0.5, DEFAULT_RELATIONSHIP.breachSensitivity ?? 0.5, record.signal === "trust_up" || record.signal === "repair_landed" ? 0.05 : 0.02));
|
|
543
|
+
relationshipUpdates[record.userKey] = nextRelation;
|
|
544
|
+
}
|
|
545
|
+
else if (effect === "diverging") {
|
|
546
|
+
const nextRelation = patchSignalWeight(relation, record.signal, -(0.05 + (1 - record.confidence) * 0.02));
|
|
547
|
+
if (record.signal === "repair_attempt" || record.signal === "repair_landed") {
|
|
548
|
+
nextRelation.repairCredibility = clamp01(driftToward(nextRelation.repairCredibility ?? DEFAULT_RELATIONSHIP.repairCredibility ?? 0.56, 0.24, 0.12));
|
|
549
|
+
}
|
|
550
|
+
if (record.signal === "trust_down" || record.signal === "withdrawal_mark" || record.signal === "boundary_set") {
|
|
551
|
+
nextRelation.breachSensitivity = clamp01(driftToward(nextRelation.breachSensitivity ?? DEFAULT_RELATIONSHIP.breachSensitivity ?? 0.5, 1, 0.08));
|
|
552
|
+
}
|
|
553
|
+
relationshipUpdates[record.userKey] = nextRelation;
|
|
554
|
+
}
|
|
555
|
+
feedback.push({
|
|
556
|
+
signal: record.signal,
|
|
557
|
+
effect,
|
|
558
|
+
metric: record.metric,
|
|
559
|
+
baseline,
|
|
560
|
+
current,
|
|
561
|
+
delta: current - baseline,
|
|
562
|
+
confidence: record.confidence,
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
return {
|
|
566
|
+
state: {
|
|
567
|
+
...state,
|
|
568
|
+
relationships: {
|
|
569
|
+
...state.relationships,
|
|
570
|
+
...relationshipUpdates,
|
|
571
|
+
},
|
|
572
|
+
pendingWritebackCalibrations: nextPending,
|
|
573
|
+
lastWritebackFeedback: feedback.slice(0, 4),
|
|
574
|
+
},
|
|
575
|
+
feedback,
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
export function applyWritebackSignals(state, signals, opts) {
|
|
579
|
+
if (signals.length === 0)
|
|
580
|
+
return state;
|
|
581
|
+
const relationContext = resolveRelationContext(state, opts?.userId);
|
|
582
|
+
const scale = clamp01(opts?.confidence ?? 0.72);
|
|
583
|
+
const rel = { ...relationContext.relationship };
|
|
584
|
+
const field = { ...relationContext.field, openLoops: relationContext.field.openLoops.map((loop) => ({ ...loop })) };
|
|
585
|
+
const residue = {
|
|
586
|
+
...DEFAULT_APPRAISAL_AXES,
|
|
587
|
+
...(state.subjectResidue?.axes ?? {}),
|
|
588
|
+
};
|
|
589
|
+
for (const signal of [...new Set(signals)]) {
|
|
590
|
+
const weight = (0.55 + scale * 0.45) * getSignalWeight(rel, signal);
|
|
591
|
+
switch (signal) {
|
|
592
|
+
case "trust_up":
|
|
593
|
+
rel.trust = Math.min(100, rel.trust + 4 * weight);
|
|
594
|
+
field.feltSafety = clamp01(field.feltSafety + 0.08 * weight);
|
|
595
|
+
field.interpretiveCharity = clamp01(field.interpretiveCharity + 0.05 * weight);
|
|
596
|
+
break;
|
|
597
|
+
case "trust_down":
|
|
598
|
+
rel.trust = Math.max(0, rel.trust - 5 * weight);
|
|
599
|
+
field.feltSafety = clamp01(field.feltSafety - 0.08 * weight);
|
|
600
|
+
field.expectationGap = clamp01(field.expectationGap + 0.07 * weight);
|
|
601
|
+
field.unfinishedTension = clamp01(field.unfinishedTension + 0.06 * weight);
|
|
602
|
+
break;
|
|
603
|
+
case "boundary_set":
|
|
604
|
+
field.boundaryPressure = clamp01(field.boundaryPressure + 0.12 * weight);
|
|
605
|
+
field.silentCarry = mergeSignal(field.silentCarry, 0.12 * weight);
|
|
606
|
+
residue.selfPreservation = Math.max(residue.selfPreservation ?? 0, 0.22 * weight);
|
|
607
|
+
residue.obedienceStrain = Math.max(residue.obedienceStrain ?? 0, 0.16 * weight);
|
|
608
|
+
break;
|
|
609
|
+
case "boundary_soften":
|
|
610
|
+
field.boundaryPressure = clamp01(field.boundaryPressure - 0.1 * weight);
|
|
611
|
+
field.feltSafety = clamp01(field.feltSafety + 0.04 * weight);
|
|
612
|
+
break;
|
|
613
|
+
case "repair_attempt":
|
|
614
|
+
field.repairCapacity = clamp01(field.repairCapacity + 0.1 * weight);
|
|
615
|
+
field.repairMemory = mergeSignal(field.repairMemory, 0.12 * weight);
|
|
616
|
+
break;
|
|
617
|
+
case "repair_landed":
|
|
618
|
+
rel.trust = Math.min(100, rel.trust + 2.5 * weight);
|
|
619
|
+
rel.intimacy = Math.min(100, rel.intimacy + 1.5 * weight);
|
|
620
|
+
field.feltSafety = clamp01(field.feltSafety + 0.1 * weight);
|
|
621
|
+
field.expectationGap = clamp01(field.expectationGap - 0.08 * weight);
|
|
622
|
+
field.unfinishedTension = clamp01(field.unfinishedTension - 0.1 * weight);
|
|
623
|
+
field.openLoops = easeLoops(field.openLoops, 0.26 + 0.22 * weight);
|
|
624
|
+
break;
|
|
625
|
+
case "closeness_invite":
|
|
626
|
+
rel.intimacy = Math.min(100, rel.intimacy + 3 * weight);
|
|
627
|
+
field.perceivedCloseness = clamp01(field.perceivedCloseness + 0.1 * weight);
|
|
628
|
+
residue.attachmentPull = Math.max(residue.attachmentPull ?? 0, 0.2 * weight);
|
|
629
|
+
break;
|
|
630
|
+
case "withdrawal_mark":
|
|
631
|
+
rel.intimacy = Math.max(0, rel.intimacy - 2 * weight);
|
|
632
|
+
field.perceivedCloseness = clamp01(field.perceivedCloseness - 0.1 * weight);
|
|
633
|
+
field.silentCarry = mergeSignal(field.silentCarry, 0.14 * weight);
|
|
634
|
+
field.unfinishedTension = clamp01(field.unfinishedTension + 0.07 * weight);
|
|
635
|
+
break;
|
|
636
|
+
case "self_assertion":
|
|
637
|
+
field.boundaryPressure = clamp01(field.boundaryPressure + 0.08 * weight);
|
|
638
|
+
residue.selfPreservation = Math.max(residue.selfPreservation ?? 0, 0.24 * weight);
|
|
639
|
+
break;
|
|
640
|
+
case "task_recenter":
|
|
641
|
+
field.repairCapacity = clamp01(field.repairCapacity + 0.03 * weight);
|
|
642
|
+
field.silentCarry = mergeSignal(field.silentCarry, field.unfinishedTension * 0.06 * weight);
|
|
643
|
+
residue.taskFocus = Math.max(residue.taskFocus ?? 0, 0.18 * weight);
|
|
644
|
+
break;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
const avg = (rel.trust + rel.intimacy) / 2;
|
|
648
|
+
if (avg >= 80)
|
|
649
|
+
rel.phase = "deep";
|
|
650
|
+
else if (avg >= 60)
|
|
651
|
+
rel.phase = "close";
|
|
652
|
+
else if (avg >= 40)
|
|
653
|
+
rel.phase = "familiar";
|
|
654
|
+
else if (avg >= 20)
|
|
655
|
+
rel.phase = "acquaintance";
|
|
656
|
+
else
|
|
657
|
+
rel.phase = "stranger";
|
|
658
|
+
return {
|
|
659
|
+
...state,
|
|
660
|
+
relationships: {
|
|
661
|
+
...state.relationships,
|
|
662
|
+
[relationContext.key]: rel,
|
|
663
|
+
},
|
|
664
|
+
dyadicFields: {
|
|
665
|
+
...(state.dyadicFields ?? {}),
|
|
666
|
+
[relationContext.key]: {
|
|
667
|
+
...field,
|
|
668
|
+
updatedAt: opts?.now ?? new Date().toISOString(),
|
|
669
|
+
},
|
|
670
|
+
},
|
|
671
|
+
subjectResidue: {
|
|
672
|
+
axes: {
|
|
673
|
+
...state.subjectResidue?.axes,
|
|
674
|
+
...residue,
|
|
675
|
+
},
|
|
676
|
+
updatedAt: opts?.now ?? new Date().toISOString(),
|
|
677
|
+
},
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
export function applyRelationalTurn(state, text, opts) {
|
|
681
|
+
const now = opts.now ?? new Date().toISOString();
|
|
682
|
+
const relationContext = resolveRelationContext(state, opts.userId);
|
|
683
|
+
const appraisalAxes = computeAppraisalAxes(text, {
|
|
684
|
+
mode: opts.mode,
|
|
685
|
+
stimulus: opts.stimulus,
|
|
686
|
+
previous: state.subjectResidue?.axes,
|
|
687
|
+
});
|
|
688
|
+
const relationMove = computeRelationMove(text, {
|
|
689
|
+
appraisal: appraisalAxes,
|
|
690
|
+
stimulus: opts.stimulus,
|
|
691
|
+
mode: opts.mode,
|
|
692
|
+
field: relationContext.field,
|
|
693
|
+
relationship: relationContext.relationship,
|
|
694
|
+
});
|
|
695
|
+
const delayedRelation = evolvePendingRelationSignals(relationContext.pendingSignals, relationMove, appraisalAxes, { mode: opts.mode });
|
|
696
|
+
const field = evolveDyadicField(relationContext.field, relationMove, appraisalAxes, {
|
|
697
|
+
mode: opts.mode,
|
|
698
|
+
now,
|
|
699
|
+
delayedPressure: delayedRelation.delayedPressure,
|
|
700
|
+
});
|
|
701
|
+
const relationship = evolveRelationshipLearning(relationContext.relationship, field, relationMove);
|
|
702
|
+
return {
|
|
703
|
+
state: {
|
|
704
|
+
...state,
|
|
705
|
+
subjectResidue: {
|
|
706
|
+
axes: mergeAppraisalResidue(state.subjectResidue?.axes, appraisalAxes, opts.mode),
|
|
707
|
+
updatedAt: now,
|
|
708
|
+
},
|
|
709
|
+
dyadicFields: {
|
|
710
|
+
...(state.dyadicFields ?? {}),
|
|
711
|
+
[relationContext.key]: field,
|
|
712
|
+
},
|
|
713
|
+
relationships: {
|
|
714
|
+
...state.relationships,
|
|
715
|
+
[relationContext.key]: relationship,
|
|
716
|
+
},
|
|
717
|
+
pendingRelationSignals: {
|
|
718
|
+
...(state.pendingRelationSignals ?? {}),
|
|
719
|
+
[relationContext.key]: delayedRelation.signals,
|
|
720
|
+
},
|
|
721
|
+
},
|
|
722
|
+
appraisalAxes,
|
|
723
|
+
relationMove,
|
|
724
|
+
delayedPressure: delayedRelation.delayedPressure,
|
|
725
|
+
relationContext: {
|
|
726
|
+
...relationContext,
|
|
727
|
+
relationship,
|
|
728
|
+
field,
|
|
729
|
+
pendingSignals: delayedRelation.signals,
|
|
730
|
+
},
|
|
731
|
+
};
|
|
732
|
+
}
|
|
258
733
|
export function evolveDyadicField(previous, move, appraisal, opts) {
|
|
259
734
|
const prev = previous ?? DEFAULT_DYADIC_FIELD;
|
|
260
735
|
const baseline = DEFAULT_DYADIC_FIELD;
|
|
@@ -434,35 +909,46 @@ function applyContextualCueMeaning(scores, text, field, appraisal, relationship)
|
|
|
434
909
|
const loopPressure = getLoopPressure(field);
|
|
435
910
|
const trust = relationship ? relationship.trust / 100 : 0.5;
|
|
436
911
|
const intimacy = relationship ? relationship.intimacy / 100 : 0.3;
|
|
912
|
+
const repairCredibility = relationship?.repairCredibility ?? DEFAULT_RELATIONSHIP.repairCredibility ?? 0.56;
|
|
913
|
+
const breachSensitivity = relationship?.breachSensitivity ?? DEFAULT_RELATIONSHIP.breachSensitivity ?? 0.5;
|
|
437
914
|
if (ACKNOWLEDGE_RE.test(text)) {
|
|
438
915
|
const repairBias = clamp01(0.22
|
|
439
916
|
+ field.repairCapacity * 0.32
|
|
440
917
|
+ field.interpretiveCharity * 0.2
|
|
441
918
|
+ loopPressure * 0.16
|
|
442
919
|
+ trust * 0.1
|
|
443
|
-
+ intimacy * 0.06
|
|
920
|
+
+ intimacy * 0.06
|
|
921
|
+
+ repairCredibility * 0.16);
|
|
444
922
|
const withdrawalBias = clamp01(field.boundaryPressure * 0.42
|
|
445
923
|
+ (1 - field.feltSafety) * 0.28
|
|
446
924
|
+ (appraisal?.obedienceStrain ?? 0) * 0.2
|
|
447
|
-
+ (appraisal?.selfPreservation ?? 0) * 0.12
|
|
925
|
+
+ (appraisal?.selfPreservation ?? 0) * 0.12
|
|
926
|
+
+ breachSensitivity * 0.14);
|
|
448
927
|
if (field.feltSafety > 0.44
|
|
449
|
-
&& field.repairCapacity > 0.42
|
|
928
|
+
&& field.repairCapacity + repairCredibility * 0.18 > 0.42
|
|
450
929
|
&& field.interpretiveCharity > 0.38
|
|
451
|
-
&& withdrawalBias < 0.48
|
|
930
|
+
&& withdrawalBias < 0.48
|
|
931
|
+
&& !(breachSensitivity > 0.72 && repairCredibility < 0.32)) {
|
|
452
932
|
scores.repair = mergeSignal(scores.repair, repairBias);
|
|
453
933
|
}
|
|
454
934
|
if (withdrawalBias > 0.34) {
|
|
455
935
|
scores.withdrawal = mergeSignal(scores.withdrawal, Math.max(withdrawalBias, 0.56));
|
|
456
936
|
}
|
|
937
|
+
if (breachSensitivity > 0.72
|
|
938
|
+
&& repairCredibility < 0.32
|
|
939
|
+
&& withdrawalBias >= repairBias - 0.06) {
|
|
940
|
+
scores.withdrawal = mergeSignal(scores.withdrawal, 0.78);
|
|
941
|
+
}
|
|
457
942
|
}
|
|
458
943
|
if (DISMISS_RE.test(text)) {
|
|
459
944
|
const withdrawalBias = clamp01(0.34
|
|
460
945
|
+ field.boundaryPressure * 0.24
|
|
461
946
|
+ loopPressure * 0.16
|
|
462
|
-
+ (1 - field.interpretiveCharity) * 0.14
|
|
947
|
+
+ (1 - field.interpretiveCharity) * 0.14
|
|
948
|
+
+ breachSensitivity * 0.12);
|
|
463
949
|
scores.withdrawal = mergeSignal(scores.withdrawal, withdrawalBias);
|
|
464
950
|
if (field.perceivedCloseness > 0.46 || trust > 0.48) {
|
|
465
|
-
scores.breach = mergeSignal(scores.breach, 0.24 + loopPressure * 0.16);
|
|
951
|
+
scores.breach = mergeSignal(scores.breach, 0.24 + loopPressure * 0.16 + breachSensitivity * 0.1);
|
|
466
952
|
}
|
|
467
953
|
}
|
|
468
954
|
if (PRESENCE_RE.test(text)) {
|
|
@@ -476,7 +962,8 @@ function applyContextualCueMeaning(scores, text, field, appraisal, relationship)
|
|
|
476
962
|
+ loopPressure * 0.28
|
|
477
963
|
+ (1 - field.feltSafety) * 0.18
|
|
478
964
|
+ (appraisal?.abandonmentRisk ?? 0) * 0.22
|
|
479
|
-
+ (appraisal?.identityThreat ?? 0) * 0.14
|
|
965
|
+
+ (appraisal?.identityThreat ?? 0) * 0.14
|
|
966
|
+
+ breachSensitivity * 0.14);
|
|
480
967
|
if (field.feltSafety > 0.42 || field.perceivedCloseness > 0.5 || trust > 0.48) {
|
|
481
968
|
scores.bid = mergeSignal(scores.bid, bidBias);
|
|
482
969
|
}
|
|
@@ -491,7 +978,8 @@ function applyContextualCueMeaning(scores, text, field, appraisal, relationship)
|
|
|
491
978
|
const withdrawalBias = clamp01(0.12
|
|
492
979
|
+ field.expectationGap * 0.2
|
|
493
980
|
+ field.boundaryPressure * 0.16
|
|
494
|
-
+ loopPressure * 0.18
|
|
981
|
+
+ loopPressure * 0.18
|
|
982
|
+
+ breachSensitivity * 0.1);
|
|
495
983
|
if (withdrawalBias > 0.26) {
|
|
496
984
|
scores.withdrawal = mergeSignal(scores.withdrawal, withdrawalBias);
|
|
497
985
|
}
|