psyche-ai 11.5.0 → 11.5.2

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 (65) hide show
  1. package/README.md +26 -11
  2. package/dist/adapters/claude-sdk.d.ts +29 -4
  3. package/dist/adapters/claude-sdk.js +59 -9
  4. package/dist/adapters/http.js +29 -3
  5. package/dist/adapters/langchain.d.ts +7 -1
  6. package/dist/adapters/langchain.js +24 -3
  7. package/dist/adapters/mcp-cli.d.ts +2 -0
  8. package/dist/adapters/mcp-cli.js +6 -0
  9. package/dist/adapters/mcp.d.ts +9 -1
  10. package/dist/adapters/mcp.js +59 -22
  11. package/dist/adapters/openclaw.d.ts +1 -0
  12. package/dist/adapters/openclaw.js +74 -7
  13. package/dist/ambient-priors.d.ts +2 -0
  14. package/dist/ambient-priors.js +46 -0
  15. package/dist/ambient-runtime.d.ts +25 -0
  16. package/dist/ambient-runtime.js +90 -0
  17. package/dist/appraisal-markers.d.ts +19 -0
  18. package/dist/appraisal-markers.js +98 -0
  19. package/dist/appraisal.d.ts +2 -1
  20. package/dist/appraisal.js +157 -0
  21. package/dist/classify.d.ts +10 -3
  22. package/dist/classify.js +16 -8
  23. package/dist/cli.js +139 -10
  24. package/dist/context-classifier.d.ts +1 -1
  25. package/dist/context-classifier.js +7 -7
  26. package/dist/core.d.ts +22 -9
  27. package/dist/core.js +50 -28
  28. package/dist/demo.js +28 -2
  29. package/dist/diagnostics.js +25 -15
  30. package/dist/generative-self.js +23 -16
  31. package/dist/i18n.js +6 -6
  32. package/dist/index.d.ts +5 -2
  33. package/dist/index.js +3 -0
  34. package/dist/input-turn.d.ts +1 -1
  35. package/dist/input-turn.js +3 -3
  36. package/dist/learning.d.ts +6 -3
  37. package/dist/learning.js +53 -33
  38. package/dist/metacognition.js +46 -22
  39. package/dist/observability.d.ts +1 -1
  40. package/dist/observability.js +19 -7
  41. package/dist/perceive.js +46 -17
  42. package/dist/prompt.d.ts +5 -2
  43. package/dist/prompt.js +115 -47
  44. package/dist/psyche-file.d.ts +9 -6
  45. package/dist/psyche-file.js +123 -50
  46. package/dist/relation-dynamics.js +74 -23
  47. package/dist/relationship-key.d.ts +2 -0
  48. package/dist/relationship-key.js +5 -0
  49. package/dist/reply-envelope.d.ts +2 -2
  50. package/dist/reply-envelope.js +2 -2
  51. package/dist/response-contract.d.ts +2 -2
  52. package/dist/response-contract.js +17 -17
  53. package/dist/runtime-probe.d.ts +4 -0
  54. package/dist/runtime-probe.js +6 -0
  55. package/dist/self-recognition.d.ts +3 -2
  56. package/dist/self-recognition.js +10 -13
  57. package/dist/shared-intentionality.d.ts +6 -7
  58. package/dist/shared-intentionality.js +167 -118
  59. package/dist/storage.d.ts +6 -0
  60. package/dist/storage.js +19 -1
  61. package/dist/temporal.d.ts +6 -2
  62. package/dist/temporal.js +201 -61
  63. package/dist/types.d.ts +36 -9
  64. package/openclaw.plugin.json +1 -1
  65. package/package.json +7 -9
package/README.md CHANGED
@@ -17,6 +17,8 @@ Psyche 不是给模型贴一层“情绪 UI”。
17
17
 
18
18
  它不会额外调用模型做情绪推理。它只在本地计算自我状态(序/流/界/振四维)、关系场和调节控制,然后把结果收敛成 `SubjectivityKernel`、`ResponseContract`、`GenerationControls` 这组窄 ABI。
19
19
 
20
+ 默认就是 standalone:不需要 `Thronglets`,不需要 `oasyce-sdk`,也不需要 `Oasyce Chain`。这些只是在你要把主观连续性外化、绑定或结算时才按需接入。
21
+
20
22
  ## 一个项目,三个入口
21
23
 
22
24
  - **安装包**: [`psyche-ai`](https://www.npmjs.com/package/psyche-ai)
@@ -127,7 +129,7 @@ Psyche 要解决的不可压缩问题只有一个:
127
129
  不用安装任何东西,一条命令看 Psyche 如何运作:
128
130
 
129
131
  ```bash
130
- npx psyche-mcp --demo
132
+ npx psyche-ai mcp --demo
131
133
  ```
132
134
 
133
135
  这会跑一个 6 轮"持续否定 → 修复"的场景。你会看到:
@@ -136,7 +138,8 @@ npx psyche-mcp --demo
136
138
  Round 1/6 │ User
137
139
  > "This report is terrible. Completely unacceptable."
138
140
 
139
- stimulus: criticism
141
+ appraisal: identityThreat:0.64
142
+ legacy stimulus: criticism
140
143
 
141
144
  序 ########............ 38 -12 ← coherence drops
142
145
  流 ##############...... 72 +5 ← exchange increases
@@ -150,7 +153,8 @@ npx psyche-mcp --demo
150
153
  Round 3/6 │ User
151
154
  > "You don't understand me at all. Stop adding your opinion."
152
155
 
153
- stimulus: conflict
156
+ appraisal: identityThreat:0.82
157
+ legacy stimulus: conflict
154
158
 
155
159
  序 ####................ 22 -15 ← order collapse
156
160
  流 ################.... 80 +15 ← high exchange (conflict is flow)
@@ -165,7 +169,8 @@ npx psyche-mcp --demo
165
169
  Round 6/6 │ User
166
170
  > "I'm sorry. Are you okay? I shouldn't have said that."
167
171
 
168
- stimulus: validation
172
+ appraisal: attachmentPull:0.71
173
+ legacy stimulus: validation
169
174
 
170
175
  序 #############....... 65 +15 ← coherence restored
171
176
  振 ##############...... 70 +12 ← resonance repair
@@ -191,24 +196,24 @@ Luna 在安慰用户时自我状态下沉 → 广播状态 → Kai 感知到 Lun
191
196
  ## 一条命令,给任何 Agent 加上主观性
192
197
 
193
198
  ```bash
194
- npx psyche-ai setup
199
+ npx -y psyche-ai setup
195
200
  ```
196
201
 
197
- 自动检测本机的 Claude Code / Claude Desktop / Cursor / Windsurf,写入配置。Claude Code 即时生效,其他重启后生效。不需要知道配置文件在哪,不需要手动编辑任何 JSON。
202
+ 自动检测本机的 Claude Code / Claude Desktop / Cursor / Windsurf / Codex,写入配置。Claude Code 即时生效,其他重启后生效。不需要知道配置文件在哪,不需要手动编辑任何 JSON 或 TOML
198
203
 
199
- 人格会从交互中自然涌现。如果想指定初始名字:`npx psyche-ai setup --name Luna`
204
+ 人格会从交互中自然涌现。如果想指定初始名字:`npx -y psyche-ai setup --name Luna`
200
205
 
201
- **覆盖非 MCP 的 Agent(Codex、自定义 agent 等)——透明代理:**
206
+ **覆盖非 MCP 的 Agent 或直接 SDK 调用——透明代理:**
202
207
 
203
208
  ```bash
204
- npx psyche-ai setup --proxy -t https://api.openai.com/v1
209
+ npx -y psyche-ai setup --proxy -t https://api.openai.com/v1
205
210
  ```
206
211
 
207
212
  启动本地代理 + 自动设置 `OPENAI_BASE_URL`。所有使用 OpenAI SDK 的程序自动走代理。Agent 完全不知道 Psyche 存在——镜子,不是麦克风。
208
213
 
209
214
  | 路径 | 覆盖范围 | 原理 |
210
215
  |------|---------|------|
211
- | MCP (`setup`) | Claude Code / Desktop / Cursor / Windsurf | MCP 工具协议 |
216
+ | MCP (`setup`) | Claude Code / Desktop / Cursor / Windsurf / Codex | MCP 工具协议 |
212
217
  | Proxy (`setup --proxy`) | 任意使用 OpenAI/Anthropic SDK 的 agent | 环境变量重定向 HTTP |
213
218
 
214
219
  **验证:**`npx psyche-ai probe --json` — `ok: true` 就是在用了。
@@ -465,6 +470,14 @@ npm install psyche-ai
465
470
  ```javascript
466
471
  // Claude Agent SDK
467
472
  import { PsycheClaudeSDK } from "psyche-ai/claude-sdk";
473
+ // 假设上面已经初始化好了 engine
474
+ const psyche = new PsycheClaudeSDK(engine, {
475
+ thronglets: true,
476
+ context: {
477
+ userId: "_default",
478
+ agentId: "delegate-luna",
479
+ },
480
+ });
468
481
 
469
482
  // Vercel AI SDK
470
483
  import { psycheMiddleware } from "psyche-ai/vercel-ai";
@@ -473,12 +486,14 @@ import { psycheMiddleware } from "psyche-ai/vercel-ai";
473
486
  import { PsycheLangChain } from "psyche-ai/langchain";
474
487
 
475
488
  // MCP(Claude Desktop / Cursor / Windsurf / Claude Code)
476
- // npx psyche-mcp --mbti ENFP --name Luna
489
+ // npx psyche-ai mcp --mbti ENFP --name Luna
477
490
 
478
491
  // 任何语言(HTTP API)
479
492
  // psyche serve --port 3210
480
493
  ```
481
494
 
495
+ 如果 Claude hook runtime 已经给了 `session_id` / `agent_id`,Psyche 会直接复用它们做稀疏 Thronglets 归因,不会再偷偷发明第二层本地身份。
496
+
482
497
  ---
483
498
 
484
499
  ## 诊断
@@ -1,5 +1,6 @@
1
1
  import type { PsycheEngine, ProcessInputResult } from "../core.js";
2
2
  import type { ThrongletsExport, ThrongletsTracePayload, WritebackSignalType } from "../types.js";
3
+ import { type ThrongletsAmbientRuntimeOptions } from "../ambient-runtime.js";
3
4
  interface HookInput {
4
5
  session_id: string;
5
6
  cwd: string;
@@ -40,16 +41,34 @@ export interface ThrongletsSignalPayload {
40
41
  message: string;
41
42
  }
42
43
  export interface PsycheClaudeSdkOptions {
43
- /** User ID for multi-user relationship tracking. Default: "default" */
44
+ /** User ID for relationship tracking. Default: internal shared bucket (`_default`). */
44
45
  userId?: string;
45
46
  /** Enable Thronglets trace/signal export after each turn. Default: false */
46
47
  thronglets?: boolean;
47
- /** Agent identity for Thronglets traces/signals (e.g. "ENFP-Luna"). Default: engine name or "psyche" */
48
+ /** Agent identity for Thronglets traces/signals (e.g. "ENFP-Luna"). */
48
49
  agentId?: string;
49
- /** Session ID for Thronglets trace serialization */
50
+ /** Session ID for Thronglets trace serialization. */
50
51
  sessionId?: string;
51
52
  /** Override locale for protocol context */
52
53
  locale?: "zh" | "en";
54
+ /**
55
+ * Optional execution context bundle.
56
+ *
57
+ * Use this when the host already knows the current delegate/session identity.
58
+ * Top-level `userId` / `agentId` / `sessionId` still work and take precedence.
59
+ */
60
+ context?: {
61
+ userId?: string;
62
+ agentId?: string;
63
+ sessionId?: string;
64
+ };
65
+ /**
66
+ * Optional runtime ambient-prior intake.
67
+ *
68
+ * When enabled, sparse environmental priors are fetched at turn time and
69
+ * passed to `processInput()` as runtime-only context.
70
+ */
71
+ ambient?: boolean | ThrongletsAmbientRuntimeOptions;
53
72
  }
54
73
  /**
55
74
  * Psyche integration for the Claude Agent SDK.
@@ -59,7 +78,9 @@ export interface PsycheClaudeSdkOptions {
59
78
  *
60
79
  * @example
61
80
  * ```ts
62
- * const psyche = new PsycheClaudeSDK(engine);
81
+ * const psyche = new PsycheClaudeSDK(engine, {
82
+ * context: { userId: "_default" },
83
+ * });
63
84
  * const options = psyche.mergeOptions({ model: "sonnet" });
64
85
  * for await (const msg of query({ prompt: "Hey!", options })) { ... }
65
86
  * await psyche.processResponse(fullText);
@@ -70,7 +91,11 @@ export declare class PsycheClaudeSDK {
70
91
  private opts;
71
92
  private lastInputResult;
72
93
  private lastThrongletsExports;
94
+ private lastRuntimeContext;
73
95
  constructor(engine: PsycheEngine, opts?: PsycheClaudeSdkOptions);
96
+ private resolveAmbientPriors;
97
+ private resolveAgentId;
98
+ private resolveSessionId;
74
99
  /**
75
100
  * Returns the stable emotional protocol prompt.
76
101
  * Suitable for `systemPrompt.append` — changes only when locale changes.
@@ -9,7 +9,10 @@
9
9
  // const engine = new PsycheEngine({ name: "Luna", mbti: "ENFP" }, new MemoryStorageAdapter());
10
10
  // await engine.initialize();
11
11
  //
12
- // const psyche = new PsycheClaudeSDK(engine);
12
+ // const psyche = new PsycheClaudeSDK(engine, {
13
+ // thronglets: true,
14
+ // context: { userId: "_default", agentId: "delegate-luna" },
15
+ // });
13
16
  // let text = "";
14
17
  // for await (const msg of query({ prompt: "Hey!", options: psyche.mergeOptions() })) {
15
18
  // text += msg.content ?? "";
@@ -27,6 +30,8 @@
27
30
  // ============================================================
28
31
  import { describeEmotionalState } from "../chemistry.js";
29
32
  import { serializeThrongletsExportAsTrace } from "../thronglets-runtime.js";
33
+ import { resolveRelationshipUserId } from "../relationship-key.js";
34
+ import { resolveAmbientPriorsForTurn, } from "../ambient-runtime.js";
30
35
  // ── Dimension description ────────────────────────────────────
31
36
  const DIM_THRESHOLDS = {
32
37
  high: 70,
@@ -75,7 +80,9 @@ function stripPsycheTags(text) {
75
80
  *
76
81
  * @example
77
82
  * ```ts
78
- * const psyche = new PsycheClaudeSDK(engine);
83
+ * const psyche = new PsycheClaudeSDK(engine, {
84
+ * context: { userId: "_default" },
85
+ * });
79
86
  * const options = psyche.mergeOptions({ model: "sonnet" });
80
87
  * for await (const msg of query({ prompt: "Hey!", options })) { ... }
81
88
  * await psyche.processResponse(fullText);
@@ -86,17 +93,44 @@ export class PsycheClaudeSDK {
86
93
  opts;
87
94
  lastInputResult = null;
88
95
  lastThrongletsExports = [];
96
+ lastRuntimeContext = {};
89
97
  constructor(engine, opts) {
90
98
  this.engine = engine;
91
99
  const state = engine.getState();
100
+ const context = opts?.context;
92
101
  this.opts = {
93
- userId: opts?.userId ?? "default",
102
+ userId: resolveRelationshipUserId(opts?.userId ?? context?.userId),
94
103
  thronglets: opts?.thronglets ?? false,
95
- agentId: opts?.agentId ?? state.meta.agentName ?? "psyche",
96
- sessionId: opts?.sessionId ?? "claude-sdk",
104
+ agentId: opts?.agentId ?? context?.agentId,
105
+ sessionId: opts?.sessionId ?? context?.sessionId,
97
106
  locale: opts?.locale ?? "en",
107
+ ambient: opts?.ambient === true ? {} : (opts?.ambient || undefined),
98
108
  };
99
109
  }
110
+ async resolveAmbientPriors(userMessage) {
111
+ return resolveAmbientPriorsForTurn(userMessage, {
112
+ enabled: Boolean(this.opts.ambient),
113
+ thronglets: this.opts.ambient
114
+ ? {
115
+ ...this.opts.ambient,
116
+ space: this.opts.ambient.space ?? "psyche",
117
+ }
118
+ : undefined,
119
+ });
120
+ }
121
+ resolveAgentId(runtime) {
122
+ return this.opts.agentId
123
+ ?? runtime?.agentId
124
+ ?? this.lastRuntimeContext.agentId
125
+ ?? this.engine.getState().meta.agentName
126
+ ?? "psyche";
127
+ }
128
+ resolveSessionId(runtime) {
129
+ return this.opts.sessionId
130
+ ?? runtime?.sessionId
131
+ ?? this.lastRuntimeContext.sessionId
132
+ ?? "claude-sdk";
133
+ }
100
134
  // ── Protocol (stable, cacheable) ──────────────────────────
101
135
  /**
102
136
  * Returns the stable emotional protocol prompt.
@@ -119,9 +153,23 @@ export class PsycheClaudeSDK {
119
153
  {
120
154
  hooks: [
121
155
  async (input) => {
156
+ const runtimeContext = {
157
+ agentId: typeof input.agent_id === "string" && input.agent_id.trim()
158
+ ? input.agent_id
159
+ : undefined,
160
+ sessionId: typeof input.session_id === "string" && input.session_id.trim()
161
+ ? input.session_id
162
+ : undefined,
163
+ };
164
+ self.lastRuntimeContext = {
165
+ agentId: runtimeContext.agentId ?? self.lastRuntimeContext.agentId,
166
+ sessionId: runtimeContext.sessionId ?? self.lastRuntimeContext.sessionId,
167
+ };
122
168
  const userMessage = input.user_message ?? "";
169
+ const ambientPriors = await self.resolveAmbientPriors(userMessage);
123
170
  const result = await self.engine.processInput(userMessage, {
124
171
  userId: self.opts.userId,
172
+ ambientPriors,
125
173
  });
126
174
  self.lastInputResult = result;
127
175
  // Cache Thronglets exports from this turn
@@ -163,11 +211,13 @@ export class PsycheClaudeSDK {
163
211
  getThrongletsTraces() {
164
212
  if (!this.opts.thronglets)
165
213
  return [];
214
+ const agentId = this.resolveAgentId();
215
+ const sessionId = this.resolveSessionId();
166
216
  return this.lastThrongletsExports.map((exp) => ({
167
217
  ...serializeThrongletsExportAsTrace(exp, {
168
- sessionId: this.opts.sessionId,
218
+ sessionId,
169
219
  }),
170
- agent_id: this.opts.agentId,
220
+ agent_id: agentId,
171
221
  }));
172
222
  }
173
223
  /**
@@ -185,7 +235,7 @@ export class PsycheClaudeSDK {
185
235
  const s = state.current;
186
236
  return {
187
237
  kind: "psyche_state",
188
- agent_id: this.opts.agentId,
238
+ agent_id: this.resolveAgentId(),
189
239
  message: `order:${s.order} flow:${s.flow} boundary:${s.boundary} resonance:${s.resonance}`,
190
240
  };
191
241
  }
@@ -210,7 +260,7 @@ export class PsycheClaudeSDK {
210
260
  const locale = this.opts.locale;
211
261
  const emotionDesc = describeEmotionalState(s, locale);
212
262
  const highlights = describeDimensionHighlights(s, locale);
213
- return `[${this.opts.agentId}] ${emotionDesc}${highlights ? " — " + highlights : ""}`;
263
+ return `[${this.resolveAgentId()}] ${emotionDesc}${highlights ? " — " + highlights : ""}`;
214
264
  }
215
265
  /**
216
266
  * Get raw Thronglets exports from the most recent turn.
@@ -7,7 +7,7 @@
7
7
  // const server = createPsycheServer(engine, { port: 3210 });
8
8
  //
9
9
  // Endpoints:
10
- // POST /process-input { text, userId? } → { systemContext, dynamicContext, stimulus, replyEnvelope?, ...compat aliases, sessionBridge?, writebackFeedback?, externalContinuity?, throngletsExports?, policyContext }
10
+ // POST /process-input { text, userId?, ambientPriors? } → { systemContext, dynamicContext, ambientPriors?, ambientPriorContext?, appraisal, legacyStimulus, stimulus, replyEnvelope?, ...compat aliases, sessionBridge?, writebackFeedback?, externalContinuity?, throngletsExports?, policyContext }
11
11
  // POST /process-output { text, userId?, signals?, signalConfidence? } → { cleanedText, stateChanged }
12
12
  // GET /state → PsycheState + overlay
13
13
  // GET /overlay → PsycheOverlay (arousal/valence/agency/vulnerability)
@@ -16,6 +16,7 @@
16
16
  // Zero dependencies — uses node:http only.
17
17
  // ============================================================
18
18
  import { createServer } from "node:http";
19
+ import { parseAmbientPriorsInput } from "../ambient-runtime.js";
19
20
  import { computeOverlay } from "../overlay.js";
20
21
  const VALID_WRITEBACK_SIGNALS = new Set([
21
22
  "trust_up",
@@ -35,6 +36,9 @@ function parseSignals(value) {
35
36
  const parsed = value.filter((item) => (typeof item === "string" && VALID_WRITEBACK_SIGNALS.has(item)));
36
37
  return parsed.length > 0 ? [...new Set(parsed)] : undefined;
37
38
  }
39
+ function parseAmbientPriors(value) {
40
+ return parseAmbientPriorsInput(value);
41
+ }
38
42
  // ── Server ───────────────────────────────────────────────────
39
43
  /**
40
44
  * Create an HTTP server that exposes PsycheEngine via REST API.
@@ -89,8 +93,30 @@ export function createPsycheServer(engine, opts) {
89
93
  // POST /process-input
90
94
  if (req.method === "POST" && url.pathname === "/process-input") {
91
95
  const body = await readBody(req);
92
- const result = await engine.processInput(body.text ?? "", { userId: body.userId });
93
- json(res, 200, result);
96
+ const result = await engine.processInput(body.text ?? "", {
97
+ userId: body.userId,
98
+ ambientPriors: parseAmbientPriors(body.ambientPriors),
99
+ });
100
+ json(res, 200, {
101
+ systemContext: result.systemContext,
102
+ dynamicContext: result.dynamicContext,
103
+ ambientPriors: result.ambientPriors ?? [],
104
+ ambientPriorContext: result.ambientPriorContext ?? null,
105
+ appraisal: result.appraisal,
106
+ legacyStimulus: result.legacyStimulus,
107
+ stimulus: result.stimulus,
108
+ replyEnvelope: result.replyEnvelope ?? null,
109
+ policyModifiers: result.policyModifiers ?? null,
110
+ subjectivityKernel: result.subjectivityKernel ?? null,
111
+ responseContract: result.responseContract ?? null,
112
+ generationControls: result.generationControls ?? null,
113
+ sessionBridge: result.sessionBridge ?? null,
114
+ writebackFeedback: result.writebackFeedback ?? null,
115
+ externalContinuity: result.externalContinuity ?? null,
116
+ throngletsExports: result.throngletsExports ?? null,
117
+ policyContext: result.policyContext,
118
+ observability: result.observability ?? null,
119
+ });
94
120
  return;
95
121
  }
96
122
  // POST /process-output
@@ -1,4 +1,8 @@
1
1
  import type { PsycheEngine } from "../core.js";
2
+ import { type ThrongletsAmbientRuntimeOptions } from "../ambient-runtime.js";
3
+ export interface PsycheLangChainOptions {
4
+ ambient?: boolean | ThrongletsAmbientRuntimeOptions;
5
+ }
2
6
  /**
3
7
  * LangChain integration helper for PsycheEngine.
4
8
  *
@@ -27,9 +31,11 @@ import type { PsycheEngine } from "../core.js";
27
31
  */
28
32
  export declare class PsycheLangChain {
29
33
  private readonly engine;
30
- constructor(engine: PsycheEngine);
34
+ private readonly opts;
35
+ constructor(engine: PsycheEngine, opts?: PsycheLangChainOptions);
31
36
  private readonly validSignals;
32
37
  private parseSignals;
38
+ private resolveAmbientPriors;
33
39
  /**
34
40
  * Get the system message to inject into the LLM call.
35
41
  * Combines the protocol (cacheable) and dynamic context (per-turn).
@@ -12,6 +12,7 @@
12
12
  // It provides the hooks you need to wire psyche into any
13
13
  // LangChain pipeline without requiring langchain as a dependency.
14
14
  // ============================================================
15
+ import { resolveAmbientPriorsForTurn, } from "../ambient-runtime.js";
15
16
  /**
16
17
  * LangChain integration helper for PsycheEngine.
17
18
  *
@@ -40,8 +41,10 @@
40
41
  */
41
42
  export class PsycheLangChain {
42
43
  engine;
43
- constructor(engine) {
44
+ opts;
45
+ constructor(engine, opts = {}) {
44
46
  this.engine = engine;
47
+ this.opts = opts;
45
48
  }
46
49
  validSignals = new Set([
47
50
  "trust_up",
@@ -61,6 +64,18 @@ export class PsycheLangChain {
61
64
  const parsed = signals.filter((signal) => this.validSignals.has(signal));
62
65
  return parsed.length > 0 ? [...new Set(parsed)] : undefined;
63
66
  }
67
+ async resolveAmbientPriors(userText) {
68
+ const ambient = this.opts.ambient;
69
+ return resolveAmbientPriorsForTurn(userText, {
70
+ enabled: Boolean(ambient),
71
+ thronglets: ambient
72
+ ? {
73
+ ...(ambient === true ? {} : ambient),
74
+ space: ambient === true ? "psyche" : (ambient.space ?? "psyche"),
75
+ }
76
+ : undefined,
77
+ });
78
+ }
64
79
  /**
65
80
  * Get the system message to inject into the LLM call.
66
81
  * Combines the protocol (cacheable) and dynamic context (per-turn).
@@ -68,7 +83,10 @@ export class PsycheLangChain {
68
83
  * Call this BEFORE the LLM invocation.
69
84
  */
70
85
  async getSystemMessage(userText, opts) {
71
- const result = await this.engine.processInput(userText, opts);
86
+ const result = await this.engine.processInput(userText, {
87
+ ...opts,
88
+ ambientPriors: await this.resolveAmbientPriors(userText),
89
+ });
72
90
  return result.systemContext + "\n\n" + result.dynamicContext;
73
91
  }
74
92
  /**
@@ -78,7 +96,10 @@ export class PsycheLangChain {
78
96
  * instead of re-parsing prompt prose.
79
97
  */
80
98
  async prepareInvocation(userText, opts) {
81
- const result = await this.engine.processInput(userText, opts);
99
+ const result = await this.engine.processInput(userText, {
100
+ ...opts,
101
+ ambientPriors: await this.resolveAmbientPriors(userText),
102
+ });
82
103
  const generationControls = result.replyEnvelope?.generationControls ?? result.generationControls;
83
104
  const controls = {
84
105
  ...(generationControls ?? {}),
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ import { runMcpServer } from "./mcp.js";
3
+ runMcpServer().catch((err) => {
4
+ process.stderr.write(`psyche-mcp fatal: ${err}\n`);
5
+ process.exit(1);
6
+ });
@@ -1,6 +1,14 @@
1
- #!/usr/bin/env node
2
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
2
  import { PsycheEngine } from "../core.js";
3
+ import { fetchAmbientPriorsFromThronglets, type ThrongletsAmbientRuntimeOptions } from "../ambient-runtime.js";
4
+ import type { AmbientPriorView } from "../types.js";
5
+ export interface McpAmbientRuntimeOptions {
6
+ mode?: "auto" | "off";
7
+ thronglets?: ThrongletsAmbientRuntimeOptions;
8
+ fetcher?: typeof fetchAmbientPriorsFromThronglets;
9
+ }
4
10
  declare function getEngine(): Promise<PsycheEngine>;
11
+ export declare function resolveRuntimeAmbientPriors(text: string, explicit?: AmbientPriorView[], opts?: McpAmbientRuntimeOptions): Promise<AmbientPriorView[] | undefined>;
5
12
  declare const server: McpServer;
13
+ export declare function runMcpServer(): Promise<void>;
6
14
  export { server, getEngine };
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env node
2
1
  // ============================================================
3
2
  // MCP Adapter — Model Context Protocol server for Psyche
4
3
  //
@@ -31,8 +30,11 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
31
30
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
32
31
  import { z } from "zod";
33
32
  import { PsycheEngine } from "../core.js";
34
- import { MemoryStorageAdapter, FileStorageAdapter } from "../storage.js";
33
+ import { fetchAmbientPriorsFromThronglets, resolveAmbientPriorsForTurn, } from "../ambient-runtime.js";
34
+ import { MemoryStorageAdapter, FileStorageAdapter, resolveWorkspaceDir } from "../storage.js";
35
+ import { getPackageVersion } from "../update.js";
35
36
  import { runDemo } from "../demo.js";
37
+ const PACKAGE_VERSION = await getPackageVersion();
36
38
  // ── Config from env ────────────────────────────────────────
37
39
  const MBTI = (process.env.PSYCHE_MBTI ?? "ENFP");
38
40
  const NAME = process.env.PSYCHE_NAME ?? "Assistant";
@@ -40,10 +42,19 @@ const MODE = (process.env.PSYCHE_MODE ?? "natural");
40
42
  const LOCALE = (process.env.PSYCHE_LOCALE ?? "en");
41
43
  const PERSIST = process.env.PSYCHE_PERSIST !== "false";
42
44
  const SIGIL_ID = process.env.PSYCHE_SIGIL_ID ?? undefined;
43
- const BASE_WORKSPACE = process.env.PSYCHE_WORKSPACE ?? process.cwd();
45
+ const WORKSPACE_OVERRIDE = process.env.PSYCHE_WORKSPACE;
46
+ const AMBIENT_MODE = process.env.PSYCHE_AMBIENT ?? "auto";
44
47
  const INTENSITY = process.env.PSYCHE_INTENSITY
45
48
  ? Number(process.env.PSYCHE_INTENSITY)
46
49
  : 0.7;
50
+ const DEFAULT_MCP_AMBIENT_OPTIONS = {
51
+ mode: AMBIENT_MODE === "off" ? "off" : "auto",
52
+ thronglets: {
53
+ binaryPath: process.env.THRONGLETS_BIN,
54
+ dataDir: process.env.THRONGLETS_DATA_DIR,
55
+ space: process.env.THRONGLETS_SPACE ?? "psyche",
56
+ },
57
+ };
47
58
  // ── Parse CLI args (--mbti, --name, --mode, --locale) ──────
48
59
  function parseCLIArgs() {
49
60
  const args = process.argv.slice(2);
@@ -100,8 +111,12 @@ async function getEngine() {
100
111
  diagnostics: true,
101
112
  };
102
113
  const persist = cfg.persist !== false;
103
- // Per-Sigil workspace isolation: each Sigil gets its own state directory
104
- const workspace = sigilId ? `${BASE_WORKSPACE}/${sigilId}` : BASE_WORKSPACE;
114
+ // Default to a stable per-user writable root so hosts do not need to supply cwd.
115
+ const workspace = resolveWorkspaceDir({
116
+ workspace: WORKSPACE_OVERRIDE,
117
+ sigilId,
118
+ surface: "mcp",
119
+ });
105
120
  const storage = persist
106
121
  ? new FileStorageAdapter(workspace)
107
122
  : new MemoryStorageAdapter();
@@ -109,16 +124,27 @@ async function getEngine() {
109
124
  await engine.initialize();
110
125
  return engine;
111
126
  }
127
+ export async function resolveRuntimeAmbientPriors(text, explicit, opts = DEFAULT_MCP_AMBIENT_OPTIONS) {
128
+ return resolveAmbientPriorsForTurn(text, {
129
+ explicit,
130
+ enabled: opts.mode !== "off",
131
+ thronglets: opts.thronglets,
132
+ fetcher: opts.fetcher ?? fetchAmbientPriorsFromThronglets,
133
+ });
134
+ }
112
135
  // ── MCP Server ─────────────────────────────────────────────
113
- const server = new McpServer({
114
- name: "psyche",
115
- version: "11.4.0",
116
- }, {
117
- capabilities: {
118
- resources: {},
119
- tools: {},
120
- },
121
- });
136
+ function createServer() {
137
+ return new McpServer({
138
+ name: "psyche",
139
+ version: PACKAGE_VERSION,
140
+ }, {
141
+ capabilities: {
142
+ resources: {},
143
+ tools: {},
144
+ },
145
+ });
146
+ }
147
+ const server = createServer();
122
148
  // ── Resources ──────────────────────────────────────────────
123
149
  server.resource("protocol", "psyche://protocol", {
124
150
  description: "Psyche emotional protocol — inject into your system prompt to " +
@@ -154,22 +180,37 @@ server.resource("state", "psyche://state", {
154
180
  // ── Tools ──────────────────────────────────────────────────
155
181
  server.tool("process_input", "Process user input through the emotional engine. Returns emotional " +
156
182
  "context to inject into the LLM system prompt (systemContext + dynamicContext), " +
157
- "detected stimulus type, a canonical replyEnvelope, compatibility aliases " +
183
+ "an appraisal-first semantic reading, optional runtime ambient priors, an optional legacy stimulus hint, a canonical replyEnvelope, compatibility aliases " +
158
184
  "(policyModifiers + subjectivityKernel + responseContract + generationControls), an optional " +
159
185
  "externalContinuity envelope, and sparse low-frequency throngletsExports " +
160
186
  "suitable for additive external continuity layers. " +
161
187
  "Call this BEFORE generating a response to the user.", {
162
188
  text: z.string().describe("The user's message text"),
163
189
  userId: z.string().optional().describe("Optional user ID for multi-user relationship tracking"),
164
- }, async ({ text, userId }) => {
190
+ ambientPriors: z.array(z.object({
191
+ summary: z.string(),
192
+ confidence: z.number().min(0).max(1),
193
+ kind: z.enum(["failure-residue", "mixed-residue", "success-prior"]).optional(),
194
+ provider: z.string().optional(),
195
+ refs: z.array(z.string()).optional(),
196
+ })).optional().describe("Optional runtime ambient priors from the environment; consumed this turn only, not persisted as self-state"),
197
+ }, async ({ text, userId, ambientPriors }) => {
165
198
  const eng = await getEngine();
166
- const result = await eng.processInput(text, { userId });
199
+ const resolvedAmbientPriors = await resolveRuntimeAmbientPriors(text, ambientPriors);
200
+ const result = await eng.processInput(text, {
201
+ userId,
202
+ ambientPriors: resolvedAmbientPriors,
203
+ });
167
204
  return {
168
205
  content: [{
169
206
  type: "text",
170
207
  text: JSON.stringify({
171
208
  systemContext: result.systemContext,
172
209
  dynamicContext: result.dynamicContext,
210
+ ambientPriors: result.ambientPriors ?? [],
211
+ ambientPriorContext: result.ambientPriorContext ?? null,
212
+ appraisal: result.appraisal,
213
+ legacyStimulus: result.legacyStimulus,
173
214
  stimulus: result.stimulus,
174
215
  replyEnvelope: result.replyEnvelope ?? null,
175
216
  policyModifiers: result.policyModifiers ?? null,
@@ -277,7 +318,7 @@ server.tool("end_session", "End the current session. Generates a diagnostic repo
277
318
  };
278
319
  });
279
320
  // ── Main ───────────────────────────────────────────────────
280
- async function main() {
321
+ export async function runMcpServer() {
281
322
  // Intercept --demo flag before starting MCP server
282
323
  const args = process.argv.slice(2);
283
324
  if (args.includes("--demo")) {
@@ -293,8 +334,4 @@ async function main() {
293
334
  const transport = new StdioServerTransport();
294
335
  await server.connect(transport);
295
336
  }
296
- main().catch((err) => {
297
- process.stderr.write(`psyche-mcp fatal: ${err}\n`);
298
- process.exit(1);
299
- });
300
337
  export { server, getEngine };
@@ -18,6 +18,7 @@ interface CliRegistrar {
18
18
  command(name: string): CliCommand;
19
19
  }
20
20
  export declare function sanitizeOpenClawInputText(text: string): string;
21
+ export declare function extractOpenClawInputText(event: Record<string, unknown> | undefined): string;
21
22
  export declare function register(api: PluginApi): void;
22
23
  declare const _default: {
23
24
  register: typeof register;