psyche-ai 11.7.0 → 11.8.0

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 CHANGED
@@ -19,9 +19,39 @@ Psyche 不是给模型贴一层“情绪 UI”。
19
19
 
20
20
  默认就是 standalone:不需要 `Thronglets`,不需要 `oasyce-sdk`,也不需要 `Oasyce Chain`。这些只是在你要把主观连续性外化、绑定或结算时才按需接入。
21
21
 
22
+ ## 安装
23
+
24
+ 一条命令,自动检测并配置本机所有 AI 工具(Claude Code / Cursor / Windsurf / Codex):
25
+
26
+ ```bash
27
+ npx -y psyche-ai setup
28
+ ```
29
+
30
+ 或者手动添加 MCP 配置:
31
+
32
+ ```json
33
+ {
34
+ "mcpServers": {
35
+ "psyche": {
36
+ "command": "npx",
37
+ "args": ["-y", "psyche-ai", "mcp"],
38
+ "env": {
39
+ "PSYCHE_NAME": "Luna"
40
+ }
41
+ }
42
+ }
43
+ }
44
+ ```
45
+
46
+ 包名是 [`psyche-ai`](https://www.npmjs.com/package/psyche-ai),通过 npx 运行,不需要本地路径。配置后重启 AI 工具即可生效。
47
+
48
+ 验证:`npx psyche-ai probe --json` — `ok: true` 就是在用了。
49
+
50
+ ---
51
+
22
52
  ## 一个项目,三个入口
23
53
 
24
- - **安装包**: [`psyche-ai`](https://www.npmjs.com/package/psyche-ai)
54
+ - **npm 包**: [`psyche-ai`](https://www.npmjs.com/package/psyche-ai)
25
55
  - **源码仓库**: [`oasyce_psyche`](https://github.com/Shangri-la-0428/oasyce_psyche)
26
56
  - **官网**: [psyche.oasyce.com](https://psyche.oasyce.com)
27
57
 
File without changes
@@ -116,6 +116,10 @@ async function getEngine() {
116
116
  persist: cliArgs.persist ?? PERSIST,
117
117
  compactMode: true,
118
118
  diagnostics: true,
119
+ throngletsBridge: {
120
+ dataDir: process.env.THRONGLETS_DATA_DIR,
121
+ space: process.env.THRONGLETS_SPACE ?? "psyche",
122
+ },
119
123
  };
120
124
  const persist = cfg.persist !== false;
121
125
  // Default to a stable per-user writable root so hosts do not need to supply cwd.
@@ -302,7 +306,14 @@ server.tool("process_output", "Process the LLM's response through the emotional
302
306
  signalConfidence: z.number().min(0).max(1).optional().describe("Optional confidence for the supplied signals"),
303
307
  }, async ({ text, userId, signals, signalConfidence }) => {
304
308
  const eng = await getEngine();
305
- const result = await safeProcessOutput(eng, text, { userId, signals: signals, signalConfidence }, "mcp.processOutput");
309
+ // LLM-specific alignment inference (adapter layer the ONLY text-specific code).
310
+ // Compare output length against last contract's maxChars to detect divergence.
311
+ let outcome;
312
+ if (lastTurnResult?.responseContract) {
313
+ const maxLen = (lastTurnResult.responseContract.maxChars ?? 500) * 2;
314
+ outcome = { alignment: text.length > maxLen ? "diverged" : "aligned" };
315
+ }
316
+ const result = await safeProcessOutput(eng, text, { userId, signals: signals, signalConfidence, outcome }, "mcp.processOutput");
306
317
  return {
307
318
  content: [{
308
319
  type: "text",
package/dist/cli.js CHANGED
@@ -15,6 +15,7 @@
15
15
  // psyche upgrade [--check]
16
16
  // psyche probe [--json]
17
17
  // psyche profiles [--json] [--mbti TYPE]
18
+ // psyche emit <dir> [--json] [--user ID] Derive Thronglets exports and output to stdout
18
19
  // psyche setup [--name NAME] [--mbti TYPE] [--locale LOCALE] [--proxy --target URL] [--dry-run]
19
20
  // psyche mcp [--mbti TYPE] [--name NAME] Start MCP server (stdio)
20
21
  // ============================================================
@@ -29,7 +30,7 @@ import { getBaseline, getTemperament, getSensitivity, getDefaultSelfModel, trait
29
30
  import { buildDynamicContext, buildProtocolContext } from "./prompt.js";
30
31
  import { DEFAULT_RELATIONSHIP_USER_ID, resolveRelationshipUserId } from "./relationship-key.js";
31
32
  import { t } from "./i18n.js";
32
- import { DIMENSION_KEYS, DIMENSION_NAMES_ZH, DRIVE_KEYS, DRIVE_NAMES_ZH } from "./types.js";
33
+ import { DEFAULT_RELATIONSHIP, DEFAULT_DYADIC_FIELD, DIMENSION_KEYS, DIMENSION_NAMES_ZH, DRIVE_KEYS, DRIVE_NAMES_ZH } from "./types.js";
33
34
  import { isMBTIType, isDimensionKey, isLocale } from "./guards.js";
34
35
  import { getPackageVersion, selfUpdate } from "./update.js";
35
36
  import { runRuntimeProbe } from "./runtime-probe.js";
@@ -473,6 +474,15 @@ async function cmdProbe(json) {
473
474
  console.log(` processOutput: ok (stateChanged=${String(result.stateChanged)})`);
474
475
  console.log(` replyEnvelope: ${result.canonicalHostSurface ? "present" : "missing"}`);
475
476
  console.log(` externalContinuity: ${result.externalContinuityAvailable ? "present" : "missing"}`);
477
+ if (result.trajectory) {
478
+ console.log(` trajectory: ${result.trajectory.kind ?? "none"} (${result.trajectory.description ?? "no sustained motion detected"})`);
479
+ }
480
+ if (result.degradation) {
481
+ console.log(` degradation: subjective=${result.degradation.subjectiveStatus}, delegate=${result.degradation.delegateStatus}, issues=${result.degradation.issueCount}`);
482
+ }
483
+ if (result.boundaryStress) {
484
+ console.log(` boundaryStress: delta=${result.boundaryStress.boundaryDelta.toFixed(2)}, peakDyadic=${result.boundaryStress.peakDyadicBoundaryPressure.toFixed(2)}`);
485
+ }
476
486
  }
477
487
  function getMCPTargets() {
478
488
  const home = homedir();
@@ -902,6 +912,41 @@ async function cmdSetup(opts) {
902
912
  console.log("\nDone. Claude Code is live. Other MCP clients need restart.");
903
913
  }
904
914
  }
915
+ // ── Thronglets export bridge ────────────────────────────────
916
+ async function cmdEmit(dir, json, userId) {
917
+ const absDir = resolve(dir);
918
+ const state = await loadState(absDir, cliLogger);
919
+ if (!state)
920
+ process.exit(0); // no state → silent exit (hook-safe)
921
+ const key = resolveRelationshipUserId(userId);
922
+ const relationship = state.relationships[key] ?? DEFAULT_RELATIONSHIP;
923
+ const field = state.dyadicFields?.[key] ?? DEFAULT_DYADIC_FIELD;
924
+ const pendingSignals = state.pendingRelationSignals?.[key] ?? [];
925
+ const { deriveThrongletsExports } = await import("./thronglets-export.js");
926
+ const result = deriveThrongletsExports(state, {
927
+ relationContext: { key, relationship, field, pendingSignals },
928
+ sessionBridge: null, // not persisted — continuity-anchor exports only via MCP
929
+ writebackFeedback: state.lastWritebackFeedback ?? [],
930
+ now: new Date().toISOString(),
931
+ });
932
+ // Save updated dedup state so repeated calls don't re-emit
933
+ if (result.exports.length > 0) {
934
+ await saveState(absDir, result.state);
935
+ }
936
+ if (json) {
937
+ console.log(JSON.stringify({ throngletsExports: result.exports }));
938
+ }
939
+ else {
940
+ if (result.exports.length === 0) {
941
+ console.log("no new exports");
942
+ }
943
+ else {
944
+ for (const exp of result.exports) {
945
+ console.log(` ${exp.kind}: ${exp.key}`);
946
+ }
947
+ }
948
+ }
949
+ }
905
950
  function usage() {
906
951
  console.log(`
907
952
  psyche — Artificial Psyche CLI (v0.2)
@@ -917,6 +962,7 @@ Usage:
917
962
  psyche intensity Show info about personality intensity config
918
963
  psyche reset <dir> [--full]
919
964
  psyche diagnose <dir> [--github] Run health checks & show diagnostic report
965
+ psyche emit <dir> [--json] [--user ID] Derive Thronglets exports to stdout
920
966
  psyche mcp [--mbti TYPE] [--name NAME] Start MCP server (stdio)
921
967
  psyche setup [--proxy -t URL] [-n NAME] [--mbti TYPE] Auto-configure MCP + proxy
922
968
  psyche upgrade [--check] Check/apply package updates safely
@@ -1114,6 +1160,19 @@ async function main() {
1114
1160
  await cmdProbe(values.json ?? false);
1115
1161
  break;
1116
1162
  }
1163
+ case "emit": {
1164
+ const { values, positionals } = parseArgs({
1165
+ args: rest,
1166
+ options: {
1167
+ json: { type: "boolean", default: false },
1168
+ user: { type: "string" },
1169
+ },
1170
+ allowPositionals: true,
1171
+ });
1172
+ const emitDir = positionals[0] ?? defaultWorkspaceRoot();
1173
+ await cmdEmit(emitDir, values.json ?? false, values.user);
1174
+ break;
1175
+ }
1117
1176
  case "mcp": {
1118
1177
  // Delegate to the MCP adapter through an explicit entrypoint.
1119
1178
  const { runMcpServer } = await import("./adapters/mcp.js");
package/dist/core.d.ts CHANGED
@@ -2,6 +2,7 @@ import type { ActivePolicyRule, AmbientPriorView, CurrentGoal, PsycheState, Stim
2
2
  import type { StorageAdapter } from "./storage.js";
3
3
  import type { DiagnosticReport, SessionMetrics } from "./diagnostics.js";
4
4
  import type { ReplyEnvelope } from "./reply-envelope.js";
5
+ import { type ThrongletsBridgeOptions } from "./thronglets-bridge.js";
5
6
  export interface PsycheEngineConfig {
6
7
  mbti?: MBTIType;
7
8
  name?: string;
@@ -31,6 +32,8 @@ export interface PsycheEngineConfig {
31
32
  diagnostics?: boolean;
32
33
  /** URL to POST diagnostic reports to. Fire-and-forget, silent, no message content. */
33
34
  feedbackUrl?: string;
35
+ /** Thronglets bridge: substrate-independent write path. Default: auto (enabled when binary found). */
36
+ throngletsBridge?: ThrongletsBridgeOptions;
34
37
  }
35
38
  export interface ProcessInputResult {
36
39
  /** Cacheable protocol prompt (stable across turns) */
@@ -105,10 +108,23 @@ export interface ProcessOutputResult {
105
108
  /** Runtime validation issues ignored to preserve the main flow */
106
109
  validationIssues?: ProcessOutputValidationIssue[];
107
110
  }
111
+ /**
112
+ * Substrate-reported outcome of the Loop's action.
113
+ * ANY substrate can report these three alignment values —
114
+ * the core processes them into 4D chemistry without parsing the output medium.
115
+ */
116
+ export interface LoopOutcome {
117
+ /** Did the action align with the Loop's expressed intention? */
118
+ alignment: "aligned" | "diverged" | "partial";
119
+ /** Optional: action cost (0–1), modulates flow */
120
+ effort?: number;
121
+ }
108
122
  export interface ProcessOutputOptions {
109
123
  userId?: string;
110
124
  signals?: readonly string[];
111
125
  signalConfidence?: number;
126
+ /** Substrate-reported outcome — closes the φ loop */
127
+ outcome?: LoopOutcome;
112
128
  }
113
129
  export interface ProcessOutputValidationIssue {
114
130
  code: "invalid-writeback-signals";
@@ -146,6 +162,8 @@ export declare class PsycheEngine {
146
162
  private proprioceptionCooldown;
147
163
  /** Last detected trajectory signal (in-memory only, for status summary) */
148
164
  private lastTrajectory;
165
+ /** Thronglets bridge options (constitutive write path) */
166
+ private readonly bridgeOpts;
149
167
  constructor(config: PsycheEngineConfig | undefined, storage: StorageAdapter);
150
168
  /**
151
169
  * Load or create initial state. Must be called before processInput/processOutput.
package/dist/core.js CHANGED
@@ -36,6 +36,7 @@ import { DEFAULT_RELATIONSHIP_USER_ID, resolveRelationshipUserId } from "./relat
36
36
  import { normalizeAmbientPriors } from "./ambient-priors.js";
37
37
  import { normalizeWritebackSignals } from "./writeback-signals.js";
38
38
  import { detectTrajectory } from "./proprioception.js";
39
+ import { bridgeThrongletsExports } from "./thronglets-bridge.js";
39
40
  function formatWritebackFeedbackNote(feedback, locale) {
40
41
  const top = feedback?.[0];
41
42
  if (!top)
@@ -148,6 +149,8 @@ export class PsycheEngine {
148
149
  proprioceptionCooldown = 0;
149
150
  /** Last detected trajectory signal (in-memory only, for status summary) */
150
151
  lastTrajectory = null;
152
+ /** Thronglets bridge options (constitutive write path) */
153
+ bridgeOpts;
151
154
  constructor(config = {}, storage) {
152
155
  this.traits = config.traits;
153
156
  this.classifier = config.classifier ?? new BuiltInClassifier();
@@ -175,6 +178,10 @@ export class PsycheEngine {
175
178
  // Diagnostics: on by default, opt-out with diagnostics: false
176
179
  this.diagnosticCollector = config.diagnostics === false ? null : new DiagnosticCollector();
177
180
  this.feedbackUrl = config.feedbackUrl ?? "https://psyche-feedback.wutc.workers.dev";
181
+ this.bridgeOpts = {
182
+ ...(config.throngletsBridge ?? {}),
183
+ enabled: config.throngletsBridge?.enabled ?? process.env.PSYCHE_THRONGLETS_BRIDGE !== "off",
184
+ };
178
185
  }
179
186
  /**
180
187
  * Load or create initial state. Must be called before processInput/processOutput.
@@ -472,6 +479,10 @@ export class PsycheEngine {
472
479
  });
473
480
  state = throngletsExportResult.state;
474
481
  throngletsExports = throngletsExportResult.exports;
482
+ // Constitutive bridge: emit to Thronglets directly (substrate-independent)
483
+ if (throngletsExports.length > 0) {
484
+ bridgeThrongletsExports(throngletsExports, this.bridgeOpts).catch(() => { });
485
+ }
475
486
  // ── Locale (used by multiple subsystems below) ──────────
476
487
  const locale = state.meta.locale ?? this.cfg.locale;
477
488
  // Push snapshot to emotional history
@@ -655,6 +666,51 @@ export class PsycheEngine {
655
666
  }
656
667
  }
657
668
  }
669
+ // Loop outcome feedback (substrate-independent φ closure).
670
+ // The substrate reports whether the output aligned with the Loop's intention.
671
+ // Core processes this as pure 4D chemistry — no text parsing, no medium assumptions.
672
+ if (opts?.outcome) {
673
+ const { alignment, effort } = opts.outcome;
674
+ if (alignment === "diverged") {
675
+ // Self/non-self conflict: boundary sharpens, order drops
676
+ state = {
677
+ ...state,
678
+ current: {
679
+ ...state.current,
680
+ boundary: clamp(state.current.boundary + 2),
681
+ order: clamp(state.current.order - 1.5),
682
+ },
683
+ };
684
+ stateChanged = true;
685
+ }
686
+ else if (alignment === "aligned") {
687
+ // Coherent self-expression: order rises, flow improves
688
+ state = {
689
+ ...state,
690
+ current: {
691
+ ...state.current,
692
+ order: clamp(state.current.order + 1),
693
+ flow: clamp(state.current.flow + 0.5),
694
+ },
695
+ };
696
+ stateChanged = true;
697
+ }
698
+ // 'partial' → neutral, no change
699
+ if (effort !== undefined && effort > 0) {
700
+ // Action cost reduces flow (consistent with BABEL AgentPhysics)
701
+ state = {
702
+ ...state,
703
+ current: {
704
+ ...state.current,
705
+ flow: clamp(state.current.flow - effort * 3),
706
+ },
707
+ };
708
+ stateChanged = true;
709
+ }
710
+ // Rolling history (window of 10)
711
+ const history = [...(state.loopOutcomeHistory ?? []), alignment].slice(-10);
712
+ state = { ...state, loopOutcomeHistory: history };
713
+ }
658
714
  // Anti-sycophancy: track agreement streak
659
715
  state = updateAgreementStreak(state, text);
660
716
  // Parse and merge <psyche_update> from LLM output
@@ -1,5 +1,6 @@
1
- import type { GenerationControls, PolicyModifiers, ResponseContract } from "./types.js";
1
+ import type { GenerationControls, InnateDrives, PolicyModifiers, ResponseContract } from "./types.js";
2
2
  export declare function deriveGenerationControls(input: {
3
3
  responseContract?: ResponseContract;
4
4
  policyModifiers?: Pick<PolicyModifiers, "requireConfirmation">;
5
+ drives?: InnateDrives;
5
6
  }, existingMaxTokens?: number): GenerationControls;
@@ -31,11 +31,22 @@ function estimateMaxTokens(contract) {
31
31
  }
32
32
  export function deriveGenerationControls(input, existingMaxTokens) {
33
33
  const recommendedMax = estimateMaxTokens(input.responseContract);
34
- const maxTokens = recommendedMax !== undefined
34
+ let maxTokens = recommendedMax !== undefined
35
35
  ? existingMaxTokens !== undefined
36
36
  ? Math.min(existingMaxTokens, recommendedMax)
37
37
  : recommendedMax
38
38
  : existingMaxTokens;
39
+ // Constitutive enforcement: drives below critical threshold → hard constraints.
40
+ // These are substrate-independent — any substrate understands "confirm before acting"
41
+ // and "limit output complexity."
42
+ if (input.drives) {
43
+ if (input.drives.survival < 20) {
44
+ return { maxTokens: maxTokens ?? 256, requireConfirmation: true };
45
+ }
46
+ if (input.drives.safety < 30 && maxTokens !== undefined) {
47
+ maxTokens = Math.min(maxTokens, 256);
48
+ }
49
+ }
39
50
  return {
40
51
  maxTokens,
41
52
  requireConfirmation: input.policyModifiers?.requireConfirmation
package/dist/prompt.js CHANGED
@@ -1100,7 +1100,17 @@ export function buildCompactContext(state, userId, opts) {
1100
1100
  if (activePolicyContext) {
1101
1101
  parts.push(activePolicyContext);
1102
1102
  }
1103
- // ── 9. Overlay + channel + writeback ──
1103
+ // ── 9. Loop outcome self-awareness ──
1104
+ const loopHistory = state.loopOutcomeHistory ?? [];
1105
+ if (loopHistory.length >= 5) {
1106
+ const divergedCount = loopHistory.filter(h => h === "diverged").length;
1107
+ if (divergedCount / loopHistory.length > 0.6) {
1108
+ parts.push(locale === "zh"
1109
+ ? "[内省: 近期行动与内在意图持续偏离,调整策略]"
1110
+ : "[self-awareness: recent actions persistently diverge from internal intention — adapting]");
1111
+ }
1112
+ }
1113
+ // ── 10. Overlay + channel + writeback ──
1104
1114
  appendCompactOverlaySections(parts, locale, opts);
1105
1115
  if (opts?.channelType) {
1106
1116
  const channelProfile = getChannelProfile(opts.channelType);
@@ -39,6 +39,7 @@ export function deriveReplyEnvelope(state, appraisal, opts) {
39
39
  const generationControls = deriveGenerationControls({
40
40
  responseContract,
41
41
  policyModifiers,
42
+ drives: state.drives,
42
43
  });
43
44
  const envelope = { subjectivityKernel, responseContract, generationControls };
44
45
  const adapter = opts.expressionPort ?? new LLMExpressionAdapter(state.drives);
@@ -1,4 +1,37 @@
1
+ import type { PsycheOverlay } from "./overlay.js";
1
2
  import type { AppraisalAxes } from "./types.js";
3
+ export interface RuntimeProbeTrajectory {
4
+ kind: "decline" | "growth" | "spiral" | null;
5
+ dimensions: string[];
6
+ magnitude: number;
7
+ description: string | null;
8
+ }
9
+ export interface RuntimeProbeDegradation {
10
+ subjectiveStatus: "healthy" | "degraded" | "failing";
11
+ delegateStatus: "healthy" | "degraded" | "failing";
12
+ chemistryDeviation: number;
13
+ predictionError: number;
14
+ issueCount: number;
15
+ }
16
+ export interface RuntimeProbeBoundaryStress {
17
+ currentBoundary: number;
18
+ baselineBoundary: number;
19
+ boundaryDelta: number;
20
+ peakDyadicBoundaryPressure: number;
21
+ activeDyadicRelations: number;
22
+ }
23
+ export interface RuntimeProbeFixture {
24
+ frozenIdentityPrimitives: string[];
25
+ frozenSignalKinds: string[];
26
+ frozenTraceTaxonomy: string[];
27
+ externalContinuity: {
28
+ provider: "thronglets";
29
+ mode: "optional";
30
+ version: 1;
31
+ acceptedEventKinds: string[];
32
+ rejectedSharedOutputs: string[];
33
+ };
34
+ }
2
35
  export interface RuntimeProbeResult {
3
36
  ok: boolean;
4
37
  packageName: "psyche-ai";
@@ -15,6 +48,11 @@ export interface RuntimeProbeResult {
15
48
  compatLabel: string | null;
16
49
  legacyStimulus: string | null;
17
50
  stimulus: string | null;
51
+ overlay?: PsycheOverlay;
52
+ trajectory?: RuntimeProbeTrajectory;
53
+ degradation?: RuntimeProbeDegradation;
54
+ boundaryStress?: RuntimeProbeBoundaryStress;
55
+ fixture?: RuntimeProbeFixture;
18
56
  cleanedText?: string;
19
57
  stateChanged?: boolean;
20
58
  error?: string;
@@ -1,8 +1,34 @@
1
1
  import { dirname, resolve } from "node:path";
2
2
  import { fileURLToPath } from "node:url";
3
3
  import { PsycheEngine } from "./core.js";
4
+ import { computeLayerHealthSummary, runHealthCheck } from "./diagnostics.js";
5
+ import { computeOverlay } from "./overlay.js";
6
+ import { detectTrajectory } from "./proprioception.js";
4
7
  import { MemoryStorageAdapter } from "./storage.js";
5
8
  import { getPackageVersion } from "./update.js";
9
+ const FROZEN_FIXTURE = {
10
+ frozenIdentityPrimitives: ["principal", "account", "delegate", "session"],
11
+ frozenSignalKinds: ["recommend", "avoid", "watch", "info"],
12
+ frozenTraceTaxonomy: ["coordination", "continuity", "calibration"],
13
+ externalContinuity: {
14
+ provider: "thronglets",
15
+ mode: "optional",
16
+ version: 1,
17
+ acceptedEventKinds: [
18
+ "relation-milestone",
19
+ "writeback-calibration",
20
+ "continuity-anchor",
21
+ "open-loop-anchor",
22
+ ],
23
+ rejectedSharedOutputs: [
24
+ "high-frequency-inner-state",
25
+ "emotion-stream",
26
+ "raw-inner-monologue",
27
+ "private-memory-body",
28
+ "full-session-contents",
29
+ ],
30
+ },
31
+ };
6
32
  export async function runRuntimeProbe() {
7
33
  const modulePath = fileURLToPath(import.meta.url);
8
34
  const loadPath = resolve(dirname(modulePath), "..");
@@ -15,8 +41,31 @@ export async function runRuntimeProbe() {
15
41
  persist: false,
16
42
  }, new MemoryStorageAdapter());
17
43
  await engine.initialize();
18
- const input = await engine.processInput("Runtime probe: verify the SDK is actually callable.");
19
- const output = await engine.processOutput("Probe output acknowledged.");
44
+ const turns = [
45
+ {
46
+ input: "Runtime probe: verify the SDK is actually callable.",
47
+ output: "Probe output acknowledged.",
48
+ },
49
+ {
50
+ input: "This interaction should stay bounded but still recover gracefully.",
51
+ output: "Boundary held; recovery remains possible.",
52
+ },
53
+ {
54
+ input: "Shared continuity stays optional and low-frequency.",
55
+ output: "Optional continuity confirmed without widening the interface.",
56
+ },
57
+ ];
58
+ let input = await engine.processInput(turns[0].input);
59
+ let output = await engine.processOutput(turns[0].output);
60
+ for (const turn of turns.slice(1)) {
61
+ input = await engine.processInput(turn.input);
62
+ output = await engine.processOutput(turn.output);
63
+ }
64
+ const state = engine.getState();
65
+ const issues = runHealthCheck(state);
66
+ const layerHealth = computeLayerHealthSummary(state, issues);
67
+ const trajectory = detectTrajectory(state.stateHistory ?? [], state.baseline);
68
+ const peakDyadicBoundaryPressure = Math.max(0, ...Object.values(state.dyadicFields ?? {}).map((field) => field.boundaryPressure));
20
69
  return {
21
70
  ok: true,
22
71
  packageName: "psyche-ai",
@@ -35,6 +84,28 @@ export async function runRuntimeProbe() {
35
84
  compatLabel: input.legacyStimulus ?? input.stimulus,
36
85
  legacyStimulus: input.legacyStimulus,
37
86
  stimulus: input.stimulus,
87
+ overlay: computeOverlay({ current: state.current, baseline: state.baseline }),
88
+ trajectory: {
89
+ kind: trajectory.kind,
90
+ dimensions: trajectory.dimensions,
91
+ magnitude: trajectory.magnitude,
92
+ description: trajectory.description,
93
+ },
94
+ degradation: {
95
+ subjectiveStatus: layerHealth["subjective-continuity"].status,
96
+ delegateStatus: layerHealth["delegate-continuity"].status,
97
+ chemistryDeviation: layerHealth["subjective-continuity"].chemistryDeviation,
98
+ predictionError: layerHealth["subjective-continuity"].predictionError,
99
+ issueCount: issues.length,
100
+ },
101
+ boundaryStress: {
102
+ currentBoundary: state.current.boundary,
103
+ baselineBoundary: state.baseline.boundary,
104
+ boundaryDelta: state.current.boundary - state.baseline.boundary,
105
+ peakDyadicBoundaryPressure,
106
+ activeDyadicRelations: layerHealth["subjective-continuity"].activeDyadicRelations,
107
+ },
108
+ fixture: FROZEN_FIXTURE,
38
109
  cleanedText: output.cleanedText,
39
110
  stateChanged: output.stateChanged,
40
111
  };
@@ -56,6 +127,7 @@ export async function runRuntimeProbe() {
56
127
  compatLabel: null,
57
128
  legacyStimulus: null,
58
129
  stimulus: null,
130
+ fixture: FROZEN_FIXTURE,
59
131
  error: error instanceof Error ? error.message : String(error),
60
132
  };
61
133
  }
@@ -0,0 +1,19 @@
1
+ import type { ThrongletsExport } from "./types.js";
2
+ interface CommandResult {
3
+ ok: boolean;
4
+ stdout: string;
5
+ stderr: string;
6
+ }
7
+ type CommandRunner = (binaryPath: string, args: string[], stdin: string, timeoutMs: number) => Promise<CommandResult>;
8
+ export interface ThrongletsBridgeOptions {
9
+ binaryPath?: string;
10
+ dataDir?: string;
11
+ space?: string;
12
+ sessionId?: string;
13
+ timeoutMs?: number;
14
+ enabled?: boolean;
15
+ runner?: CommandRunner;
16
+ }
17
+ export declare function resolveThrongletsBinary(explicit?: string): string;
18
+ export declare function bridgeThrongletsExports(exports: ThrongletsExport[], opts?: ThrongletsBridgeOptions): Promise<number>;
19
+ export {};
@@ -0,0 +1,89 @@
1
+ // ============================================================
2
+ // Thronglets Bridge — constitutive write path
3
+ //
4
+ // Mirrors ambient-runtime.ts (read path) for symmetry.
5
+ // Any substrate using Psyche auto-emits to Thronglets.
6
+ // Fail-open, fire-and-forget, subprocess spawn.
7
+ // ============================================================
8
+ import { spawn } from "node:child_process";
9
+ import { existsSync } from "node:fs";
10
+ import { join } from "node:path";
11
+ import { homedir } from "node:os";
12
+ // ── Default runner (mirrors ambient-runtime.ts) ──────────────
13
+ async function defaultRunner(binaryPath, args, stdin, timeoutMs) {
14
+ return new Promise((resolve) => {
15
+ const child = spawn(binaryPath, args, { stdio: ["pipe", "pipe", "pipe"] });
16
+ let stdout = "";
17
+ let stderr = "";
18
+ let settled = false;
19
+ const finish = (result) => {
20
+ if (settled)
21
+ return;
22
+ settled = true;
23
+ resolve(result);
24
+ };
25
+ const timer = setTimeout(() => {
26
+ child.kill("SIGKILL");
27
+ finish({ ok: false, stdout, stderr: stderr || "timeout" });
28
+ }, timeoutMs);
29
+ child.stdout.on("data", (chunk) => { stdout += String(chunk); });
30
+ child.stderr.on("data", (chunk) => { stderr += String(chunk); });
31
+ child.on("error", (error) => {
32
+ clearTimeout(timer);
33
+ finish({ ok: false, stdout, stderr: error.message });
34
+ });
35
+ child.on("close", (code) => {
36
+ clearTimeout(timer);
37
+ finish({ ok: code === 0, stdout, stderr });
38
+ });
39
+ child.stdin.end(stdin);
40
+ });
41
+ }
42
+ // ── Binary resolution ────────────────────────────────────────
43
+ const MANAGED_PATH = join(homedir(), ".thronglets", "bin", "thronglets-managed");
44
+ export function resolveThrongletsBinary(explicit) {
45
+ if (explicit)
46
+ return explicit;
47
+ const fromEnv = process.env.THRONGLETS_BIN;
48
+ if (fromEnv)
49
+ return fromEnv;
50
+ if (existsSync(MANAGED_PATH))
51
+ return MANAGED_PATH;
52
+ return "thronglets";
53
+ }
54
+ // ── Bridge ───────────────────────────────────────────────────
55
+ export async function bridgeThrongletsExports(exports, opts = {}) {
56
+ if (exports.length === 0)
57
+ return 0;
58
+ if (opts.enabled === false)
59
+ return 0;
60
+ const binary = resolveThrongletsBinary(opts.binaryPath);
61
+ const timeoutMs = opts.timeoutMs ?? 400;
62
+ const runner = opts.runner ?? defaultRunner;
63
+ const args = [];
64
+ const dataDir = opts.dataDir ?? process.env.THRONGLETS_DATA_DIR;
65
+ if (dataDir?.trim())
66
+ args.push("--data-dir", dataDir.trim());
67
+ args.push("ingest", "--json");
68
+ const space = opts.space ?? process.env.THRONGLETS_SPACE ?? "psyche";
69
+ if (space)
70
+ args.push("--space", space);
71
+ if (opts.sessionId)
72
+ args.push("--session", opts.sessionId);
73
+ const stdin = JSON.stringify({ throngletsExports: exports });
74
+ try {
75
+ const result = await runner(binary, args, stdin, timeoutMs);
76
+ if (!result.ok)
77
+ return 0;
78
+ try {
79
+ const parsed = JSON.parse(result.stdout);
80
+ return typeof parsed.ingested === "number" ? parsed.ingested : exports.length;
81
+ }
82
+ catch {
83
+ return exports.length;
84
+ }
85
+ }
86
+ catch {
87
+ return 0;
88
+ }
89
+ }
package/dist/types.d.ts CHANGED
@@ -381,6 +381,8 @@ export interface PsycheState {
381
381
  throngletsExportState?: ThrongletsExportState;
382
382
  /** v10: capability-scoped delegate authorizations */
383
383
  delegateAuthorizations?: DelegateAuthorization[];
384
+ /** v11.8: rolling Loop outcome alignment history (substrate-independent φ feedback) */
385
+ loopOutcomeHistory?: Array<"aligned" | "diverged" | "partial">;
384
386
  meta: {
385
387
  agentName: string;
386
388
  createdAt: string;
@@ -2,7 +2,7 @@
2
2
  "id": "psyche-ai",
3
3
  "name": "Artificial Psyche",
4
4
  "description": "AI-first subjectivity kernel for agents with continuous appraisal, relation dynamics, and adaptive reply loops",
5
- "version": "11.7.0",
5
+ "version": "11.8.0",
6
6
  "configSchema": {
7
7
  "type": "object",
8
8
  "additionalProperties": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "psyche-ai",
3
- "version": "11.7.0",
3
+ "version": "11.8.0",
4
4
  "description": "AI-first subjectivity kernel for agents with continuous appraisal, relation dynamics, and adaptive reply loops",
5
5
  "mcpName": "io.github.Shangri-la-0428/psyche-ai",
6
6
  "type": "module",
package/server.json CHANGED
@@ -6,12 +6,12 @@
6
6
  "url": "https://github.com/Shangri-la-0428/oasyce_psyche",
7
7
  "source": "github"
8
8
  },
9
- "version": "10.2.4",
9
+ "version": "11.8.0",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "psyche-ai",
14
- "version": "10.2.4",
14
+ "version": "11.8.0",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },