neotoma 0.9.1 → 0.10.1

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 (203) hide show
  1. package/README.md +9 -7
  2. package/dist/actions.d.ts +9 -4
  3. package/dist/actions.d.ts.map +1 -1
  4. package/dist/actions.js +222 -29
  5. package/dist/actions.js.map +1 -1
  6. package/dist/cli/aauth_signer.d.ts.map +1 -1
  7. package/dist/cli/aauth_signer.js +17 -3
  8. package/dist/cli/aauth_signer.js.map +1 -1
  9. package/dist/cli/auth_keygen.d.ts +11 -0
  10. package/dist/cli/auth_keygen.d.ts.map +1 -0
  11. package/dist/cli/auth_keygen.js +110 -0
  12. package/dist/cli/auth_keygen.js.map +1 -0
  13. package/dist/cli/commands/processes.d.ts +33 -0
  14. package/dist/cli/commands/processes.d.ts.map +1 -0
  15. package/dist/cli/commands/processes.js +385 -0
  16. package/dist/cli/commands/processes.js.map +1 -0
  17. package/dist/cli/doctor.d.ts +2 -0
  18. package/dist/cli/doctor.d.ts.map +1 -1
  19. package/dist/cli/doctor.js +14 -7
  20. package/dist/cli/doctor.js.map +1 -1
  21. package/dist/cli/hooks.d.ts.map +1 -1
  22. package/dist/cli/hooks.js +85 -7
  23. package/dist/cli/hooks.js.map +1 -1
  24. package/dist/cli/hooks_detect.d.ts.map +1 -1
  25. package/dist/cli/hooks_detect.js +6 -2
  26. package/dist/cli/hooks_detect.js.map +1 -1
  27. package/dist/cli/index.d.ts.map +1 -1
  28. package/dist/cli/index.js +199 -0
  29. package/dist/cli/index.js.map +1 -1
  30. package/dist/cli/mcp_config_scan.d.ts +30 -0
  31. package/dist/cli/mcp_config_scan.d.ts.map +1 -1
  32. package/dist/cli/mcp_config_scan.js +250 -27
  33. package/dist/cli/mcp_config_scan.js.map +1 -1
  34. package/dist/cli/mcp_proxy.d.ts +9 -0
  35. package/dist/cli/mcp_proxy.d.ts.map +1 -0
  36. package/dist/cli/mcp_proxy.js +114 -0
  37. package/dist/cli/mcp_proxy.js.map +1 -0
  38. package/dist/cli/setup.d.ts.map +1 -1
  39. package/dist/cli/setup.js +6 -1
  40. package/dist/cli/setup.js.map +1 -1
  41. package/dist/inspector/assets/{Combination-BP0-kPZX.js → Combination-BM2JQOww.js} +1 -1
  42. package/dist/inspector/assets/{agent_badge-BZT-JO2h.js → agent_badge-CpN0F7Uw.js} +1 -1
  43. package/dist/inspector/assets/{agent_detail-BGMLF8-n.js → agent_detail-BiJ3HPkP.js} +1 -1
  44. package/dist/inspector/assets/{agent_filter-DMC4CzhM.js → agent_filter-BazKcQ9w.js} +1 -1
  45. package/dist/inspector/assets/{agent_grant_detail-Brqy5K7M.js → agent_grant_detail-DXgCn9N4.js} +1 -1
  46. package/dist/inspector/assets/{agent_grant_form-BLDkUh1Y.js → agent_grant_form-DZQjBAcw.js} +1 -1
  47. package/dist/inspector/assets/{agent_grants-B2StunRb.js → agent_grants-DCKfkpwq.js} +1 -1
  48. package/dist/inspector/assets/{agents-lYmKs-fG.js → agents-ThrWnZTX.js} +1 -1
  49. package/dist/inspector/assets/{arrow-left-D1s7DOns.js → arrow-left-DaYPjbWH.js} +1 -1
  50. package/dist/inspector/assets/{attribution_card-D-bp008l.js → attribution_card-D8wccbDX.js} +1 -1
  51. package/dist/inspector/assets/{attribution_summary-Cs9Ccvt3.js → attribution_summary-Ds3UqXE_.js} +1 -1
  52. package/dist/inspector/assets/{card-Btqgzh6p.js → card-gNKW1s0y.js} +1 -1
  53. package/dist/inspector/assets/{check-DAyRtq63.js → check-CsKn_Wh2.js} +1 -1
  54. package/dist/inspector/assets/{checkbox-BrpwHaRo.js → checkbox-Ckyj4vrh.js} +1 -1
  55. package/dist/inspector/assets/{chevron-down-CCk_jMPN.js → chevron-down-7OlcRYdt.js} +1 -1
  56. package/dist/inspector/assets/{chevron-right-C9NbdZtC.js → chevron-right-BLI_oHPA.js} +1 -1
  57. package/dist/inspector/assets/{compliance-BHiHtgfg.js → compliance-CBrpHE20.js} +1 -1
  58. package/dist/inspector/assets/{confirm-dialog-BcxtVONz.js → confirm-dialog--bNglOrP.js} +1 -1
  59. package/dist/inspector/assets/{conversation_common-Dw5j3QuN.js → conversation_common-BtftGfBd.js} +1 -1
  60. package/dist/inspector/assets/{conversation_detail-BR_rBMFV.js → conversation_detail-DcL600xx.js} +1 -1
  61. package/dist/inspector/assets/{copy_id_button-CyjfY7dx.js → copy_id_button-V_cdlRRU.js} +1 -1
  62. package/dist/inspector/assets/{corrections-DFExbcsm.js → corrections-CSny2Vo_.js} +1 -1
  63. package/dist/inspector/assets/{dashboard-CxnNRphy.js → dashboard-QDIRlmWG.js} +1 -1
  64. package/dist/inspector/assets/{data-table-CazqdSem.js → data-table-qmF3Z4gV.js} +1 -1
  65. package/dist/inspector/assets/{dialog-X5X7rLah.js → dialog-DYUGw6eQ.js} +1 -1
  66. package/dist/inspector/assets/{dropdown-menu-CmZjxUWM.js → dropdown-menu-F92-sh_x.js} +1 -1
  67. package/dist/inspector/assets/{entities-l6icu6fc.js → entities-Dsipnex_.js} +1 -1
  68. package/dist/inspector/assets/{entity_detail-daoxB-h1.js → entity_detail-BfgIPU8x.js} +1 -1
  69. package/dist/inspector/assets/{entity_link-DtMv__WC.js → entity_link-Ux3LzoaM.js} +1 -1
  70. package/dist/inspector/assets/{external-link-BBoTnT-P.js → external-link-CWkNK5Tk.js} +1 -1
  71. package/dist/inspector/assets/feedback-CJK1vjsI.js +35 -0
  72. package/dist/inspector/assets/{graph_explorer-BvicLJEW.js → graph_explorer-BmseNUoQ.js} +1 -1
  73. package/dist/inspector/assets/index-BAvZ5vLC.css +1 -0
  74. package/dist/inspector/assets/{index-D5i6AEXI.js → index-BykFtejE.js} +1 -1
  75. package/dist/inspector/assets/index-CYCtoVGs.js +224 -0
  76. package/dist/inspector/assets/{index-Czej0Y93.js → index-DV_dUptd.js} +1 -1
  77. package/dist/inspector/assets/{index-DJuPlRtP.js → index-DwNCMlAU.js} +1 -1
  78. package/dist/inspector/assets/{interpretations-E0sIBf-l.js → interpretations-CMfzQ1Ln.js} +1 -1
  79. package/dist/inspector/assets/interpretations-T0E9nJx-.js +1 -0
  80. package/dist/inspector/assets/{json_viewer-ojLDPDtf.js → json_viewer-Bq_YbebZ.js} +1 -1
  81. package/dist/inspector/assets/{label-DWyQNl4E.js → label-Bl-6LEeg.js} +1 -1
  82. package/dist/inspector/assets/{live_relative_time-DzLnsA9y.js → live_relative_time-B1_XT7na.js} +1 -1
  83. package/dist/inspector/assets/{observations-Ds0kFmaM.js → observations-DBIz6sjx.js} +1 -1
  84. package/dist/inspector/assets/{page_shell-C-4AKr0Y.js → page_shell-BEmO9wa5.js} +1 -1
  85. package/dist/inspector/assets/{pagination-lSg-a95h.js → pagination-BUY-_ZUw.js} +1 -1
  86. package/dist/inspector/assets/{plus-CQMoR71F.js → plus-CN3iw613.js} +1 -1
  87. package/dist/inspector/assets/{query_refresh_indicator-Bewf0Dj1.js → query_refresh_indicator-BvQCvZ7y.js} +1 -1
  88. package/dist/inspector/assets/{recent_activity-Csh_YraY.js → recent_activity-8sqCYQa2.js} +1 -1
  89. package/dist/inspector/assets/{recent_conversations-CBengQjb.js → recent_conversations-Dm7bsH19.js} +1 -1
  90. package/dist/inspector/assets/{recent_conversations-MKmxYevd.js → recent_conversations-ZmrvYZt6.js} +1 -1
  91. package/dist/inspector/assets/{recent_records_feed-CCrBRfkG.js → recent_records_feed-BCwEB-gO.js} +1 -1
  92. package/dist/inspector/assets/{relationship_detail-CUz_GhPI.js → relationship_detail-AY29Q_zg.js} +1 -1
  93. package/dist/inspector/assets/{relationships-Z9ALu9Oa.js → relationships-BfjaoQF3.js} +1 -1
  94. package/dist/inspector/assets/{relationships-Ulajo16_.js → relationships-C9JOmG6k.js} +1 -1
  95. package/dist/inspector/assets/{sandbox-CfD5QvC5.js → sandbox-2sw7hAPG.js} +1 -1
  96. package/dist/inspector/assets/{schema_detail-B1W8rbzV.js → schema_detail-BwWRhFNS.js} +1 -1
  97. package/dist/inspector/assets/{schemas-DVlEFPuf.js → schemas-UX8RWzu0.js} +1 -1
  98. package/dist/inspector/assets/{search-BccQXyTN.js → search-sJaK1X54.js} +1 -1
  99. package/dist/inspector/assets/{select-Dv1QM6oO.js → select-Sk4XRvus.js} +1 -1
  100. package/dist/inspector/assets/{settings-B4U8tFYI.js → settings-D9mhRMBN.js} +1 -1
  101. package/dist/inspector/assets/{source_detail-B1JlZBBx.js → source_detail-BAF1G5q0.js} +1 -1
  102. package/dist/inspector/assets/{source_link-p8KzI1os.js → source_link-DTok2HA9.js} +1 -1
  103. package/dist/inspector/assets/{sources-B5ssCN-s.js → sources-DUcCw0Vc.js} +1 -1
  104. package/dist/inspector/assets/{switch-BPI_y_Z3.js → switch-DepRZ0--.js} +1 -1
  105. package/dist/inspector/assets/{tabs-HUG-sxc2.js → tabs-B0Dy5is1.js} +1 -1
  106. package/dist/inspector/assets/{textarea-DNz92WE1.js → textarea-pfrE_cJC.js} +1 -1
  107. package/dist/inspector/assets/{timeline-HN2EoMGt.js → timeline-Bh9ngotn.js} +1 -1
  108. package/dist/inspector/assets/{timeline-DRqxyQUF.js → timeline-COztEDwn.js} +1 -1
  109. package/dist/inspector/assets/{timeline_event_detail-DbA5EpiN.js → timeline_event_detail-CN1g0r15.js} +1 -1
  110. package/dist/inspector/assets/{trash-2-BAfHKatZ.js → trash-2-CGNk7jcZ.js} +1 -1
  111. package/dist/inspector/assets/{turn_detail-BUHzIhWX.js → turn_detail-DYjGa2Qt.js} +1 -1
  112. package/dist/inspector/assets/{turns-ADRkuqKL.js → turns-qZVfg6Dm.js} +1 -1
  113. package/dist/inspector/assets/{use_agents-CTZrpsvS.js → use_agents-B8yN5BKA.js} +1 -1
  114. package/dist/inspector/assets/{use_entities-BEhy6HWn.js → use_entities-l4hLQEUh.js} +1 -1
  115. package/dist/inspector/assets/use_interpretations-B_ssuETV.js +1 -0
  116. package/dist/inspector/assets/{use_mutations-B4y1qmV5.js → use_mutations-BjaD74zY.js} +1 -1
  117. package/dist/inspector/assets/use_recent_conversations-BgZIIrU6.js +1 -0
  118. package/dist/inspector/assets/{use_relationships-Cl-o_7u6.js → use_relationships-6dqoUmCP.js} +1 -1
  119. package/dist/inspector/assets/{use_schemas-Bl11WNgP.js → use_schemas-BkMuCTZx.js} +1 -1
  120. package/dist/inspector/assets/{use_sources-DkJZZBDp.js → use_sources-Bu_vb2VA.js} +1 -1
  121. package/dist/inspector/assets/{use_stats-Cn1a3yt-.js → use_stats-DvcjJbZf.js} +1 -1
  122. package/dist/inspector/assets/{use_timeline-DZkbwA-7.js → use_timeline-BXlKLsFO.js} +1 -1
  123. package/dist/inspector/assets/{use_turns-BHfaal9v.js → use_turns-BYjwOq0j.js} +1 -1
  124. package/dist/inspector/index.html +2 -2
  125. package/dist/proxy/aauth_client_signer.d.ts +37 -0
  126. package/dist/proxy/aauth_client_signer.d.ts.map +1 -0
  127. package/dist/proxy/aauth_client_signer.js +172 -0
  128. package/dist/proxy/aauth_client_signer.js.map +1 -0
  129. package/dist/proxy/index.d.ts +7 -0
  130. package/dist/proxy/index.d.ts.map +1 -0
  131. package/dist/proxy/index.js +4 -0
  132. package/dist/proxy/index.js.map +1 -0
  133. package/dist/proxy/mcp_stdio_proxy.d.ts +32 -0
  134. package/dist/proxy/mcp_stdio_proxy.d.ts.map +1 -0
  135. package/dist/proxy/mcp_stdio_proxy.js +234 -0
  136. package/dist/proxy/mcp_stdio_proxy.js.map +1 -0
  137. package/dist/proxy/preflight.d.ts +15 -0
  138. package/dist/proxy/preflight.d.ts.map +1 -0
  139. package/dist/proxy/preflight.js +71 -0
  140. package/dist/proxy/preflight.js.map +1 -0
  141. package/dist/repositories/sqlite/sqlite_client.d.ts.map +1 -1
  142. package/dist/repositories/sqlite/sqlite_client.js +2 -0
  143. package/dist/repositories/sqlite/sqlite_client.js.map +1 -1
  144. package/dist/server.d.ts +2 -0
  145. package/dist/server.d.ts.map +1 -1
  146. package/dist/server.js +269 -115
  147. package/dist/server.js.map +1 -1
  148. package/dist/services/entity_queries.d.ts.map +1 -1
  149. package/dist/services/entity_queries.js +68 -20
  150. package/dist/services/entity_queries.js.map +1 -1
  151. package/dist/services/feedback/admin_proxy.d.ts +28 -9
  152. package/dist/services/feedback/admin_proxy.d.ts.map +1 -1
  153. package/dist/services/feedback/admin_proxy.js +145 -24
  154. package/dist/services/feedback/admin_proxy.js.map +1 -1
  155. package/dist/services/feedback/admin_session.d.ts +36 -0
  156. package/dist/services/feedback/admin_session.d.ts.map +1 -0
  157. package/dist/services/feedback/admin_session.js +147 -0
  158. package/dist/services/feedback/admin_session.js.map +1 -0
  159. package/dist/services/inspector_mount.d.ts.map +1 -1
  160. package/dist/services/inspector_mount.js +9 -0
  161. package/dist/services/inspector_mount.js.map +1 -1
  162. package/dist/services/interpretation.d.ts.map +1 -1
  163. package/dist/services/interpretation.js +8 -4
  164. package/dist/services/interpretation.js.map +1 -1
  165. package/dist/services/mcp_oauth.d.ts +9 -7
  166. package/dist/services/mcp_oauth.d.ts.map +1 -1
  167. package/dist/services/mcp_oauth.js +170 -13
  168. package/dist/services/mcp_oauth.js.map +1 -1
  169. package/dist/services/raw_fragments.d.ts +3 -0
  170. package/dist/services/raw_fragments.d.ts.map +1 -1
  171. package/dist/services/raw_fragments.js +18 -8
  172. package/dist/services/raw_fragments.js.map +1 -1
  173. package/dist/services/root_landing/harness_snippets.d.ts +12 -0
  174. package/dist/services/root_landing/harness_snippets.d.ts.map +1 -1
  175. package/dist/services/root_landing/harness_snippets.js +49 -0
  176. package/dist/services/root_landing/harness_snippets.js.map +1 -1
  177. package/dist/services/root_landing/site_nav.d.ts.map +1 -1
  178. package/dist/services/root_landing/site_nav.js +8 -3
  179. package/dist/services/root_landing/site_nav.js.map +1 -1
  180. package/dist/services/schema_registry.d.ts +2 -1
  181. package/dist/services/schema_registry.d.ts.map +1 -1
  182. package/dist/services/schema_registry.js +31 -9
  183. package/dist/services/schema_registry.js.map +1 -1
  184. package/dist/shared/action_schemas.d.ts +166 -0
  185. package/dist/shared/action_schemas.d.ts.map +1 -1
  186. package/dist/shared/action_schemas.js +17 -0
  187. package/dist/shared/action_schemas.js.map +1 -1
  188. package/dist/shared/contract_mappings.d.ts.map +1 -1
  189. package/dist/shared/contract_mappings.js +15 -2
  190. package/dist/shared/contract_mappings.js.map +1 -1
  191. package/dist/shared/openapi_types.d.ts +102 -0
  192. package/dist/shared/openapi_types.d.ts.map +1 -1
  193. package/dist/tool_definitions.d.ts.map +1 -1
  194. package/dist/tool_definitions.js +12 -2
  195. package/dist/tool_definitions.js.map +1 -1
  196. package/openapi.yaml +111 -0
  197. package/package.json +1 -1
  198. package/dist/inspector/assets/feedback-DeFhdWId.js +0 -35
  199. package/dist/inspector/assets/index-B2zHigxN.js +0 -199
  200. package/dist/inspector/assets/index-Df569_c9.css +0 -1
  201. package/dist/inspector/assets/interpretations-D9gWqVhy.js +0 -1
  202. package/dist/inspector/assets/use_interpretations-CKN63UxX.js +0 -1
  203. package/dist/inspector/assets/use_recent_conversations-CIBgmz9B.js +0 -1
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Your agents forget. Neotoma makes them remember.
4
4
 
5
- Versioned records — contacts, tasks, decisions, finances — that persist across Claude, Cursor, ChatGPT, OpenClaw, and every agent you run. Open-source. Local-first. Deterministic. MIT licensed.
5
+ Versioned records — contacts, tasks, decisions, finances — that persist across Claude, Cursor, ChatGPT, OpenClaw, IronClaw, and every agent you run. Open-source. Local-first. Deterministic. MIT licensed.
6
6
 
7
7
  **[neotoma.io](https://neotoma.io)** · **[Evaluate](https://neotoma.io/evaluate)** · **[Install](https://neotoma.io/install)** · **[Documentation](https://neotoma.io/docs)**
8
8
 
@@ -38,6 +38,7 @@ graph LR
38
38
  MCP --> ChatGPT
39
39
  MCP --> Cursor
40
40
  MCP --> OpenClaw
41
+ MCP --> IronClaw
41
42
  ```
42
43
 
43
44
  - **Deterministic.** Same observations always produce the same versioned entity snapshots. No ordering sensitivity.
@@ -51,7 +52,7 @@ graph LR
51
52
  | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
52
53
  | **Privacy-first** | Your data stays local. Never used for training. User-controlled storage, optional encryption at rest. Full export and deletion control. |
53
54
  | **Deterministic** | Same input always produces same output. Schema-first extraction, hash-based entity IDs, full provenance. No silent mutation. |
54
- | **Cross-platform** | One memory graph across Claude, ChatGPT, Cursor, OpenClaw, Codex, and CLI. MCP-based access. No platform lock-in. Works alongside native memory. |
55
+ | **Cross-platform** | One memory graph across Claude, ChatGPT, Cursor, OpenClaw, IronClaw, Codex, and CLI. MCP-based access. No platform lock-in. Works alongside native memory. |
55
56
 
56
57
  ## State guarantees
57
58
 
@@ -100,6 +101,7 @@ The agent handles npm install, initialization, and MCP configuration. **Manual i
100
101
  npm install -g neotoma
101
102
  neotoma init
102
103
  neotoma mcp config
104
+ neotoma mcp check --mcp-transport a
103
105
  ```
104
106
 
105
107
  More options: [Docker](docs/developer/docker.md) | [CLI reference](docs/developer/cli_reference.md) | [Getting started](docs/developer/getting_started.md)
@@ -121,14 +123,14 @@ Three interfaces. One state invariant. Every interface provides the same determi
121
123
  | Interface | Description |
122
124
  | -------------- | ------------------------------------------------------------------------------------------------------------------------------ |
123
125
  | **REST API** | Full HTTP interface for application integration. Entities, relationships, observations, schema, timeline, and version history. |
124
- | **MCP Server** | Model Context Protocol for Claude, ChatGPT, Cursor, OpenClaw, Codex, and more. Agents store and retrieve state through structured tool calls. |
126
+ | **MCP Server** | Model Context Protocol for Claude, ChatGPT, Cursor, OpenClaw, IronClaw, Codex, and more. Agents store and retrieve state through structured tool calls. |
125
127
  | **CLI** | Command-line for scripting and direct access. Inspect entities, replay timelines, and manage state from the terminal. |
126
128
 
127
129
  All three map to the same OpenAPI-backed operations. MCP tool calls log the equivalent CLI invocation.
128
130
 
129
131
  ## Who this is for
130
132
 
131
- People building a personal operating system with AI agents across their life — wiring together tools like Claude, Cursor, ChatGPT, OpenClaw, and custom scripts to manage contacts, tasks, finances, code, content, and other domains. The same person operates their agents, builds new pipelines, and debugs state drift. These are three operational modes, not separate personas:
133
+ People building a personal operating system with AI agents across their life — wiring together tools like Claude, Cursor, ChatGPT, OpenClaw, IronClaw, and custom scripts to manage contacts, tasks, finances, code, content, and other domains. The same person operates their agents, builds new pipelines, and debugs state drift. These are three operational modes, not separate personas:
132
134
 
133
135
  | Mode | What you're doing | The tax you pay without Neotoma | What you get back |
134
136
  | ---- | ----------------- | ------------------------------- | ----------------- |
@@ -155,7 +157,7 @@ Schema is flexible — store any entity type with whatever fields the message im
155
157
 
156
158
  ## Current status
157
159
 
158
- **Version:** v0.4.2 · **Releases:** 13 · **License:** MIT
160
+ **Version:** v0.9.1 · **Releases:** 26 · **License:** MIT
159
161
 
160
162
  ### What is guaranteed (even in preview)
161
163
 
@@ -217,9 +219,9 @@ npm test
217
219
 
218
220
  Neotoma exposes state via MCP. Local storage only in preview. Local built-in auth.
219
221
 
220
- **Setup guides:** [Cursor](https://neotoma.io/neotoma-with-cursor) · [Claude Code](https://neotoma.io/neotoma-with-claude-code) · [Claude](https://neotoma.io/neotoma-with-claude) · [ChatGPT](https://neotoma.io/neotoma-with-chatgpt) · [Codex](https://neotoma.io/neotoma-with-codex) · [OpenClaw](https://neotoma.io/neotoma-with-openclaw)
222
+ **Setup guides:** [Cursor](https://neotoma.io/neotoma-with-cursor) · [Claude Code](https://neotoma.io/neotoma-with-claude-code) · [Claude](https://neotoma.io/neotoma-with-claude) · [ChatGPT](https://neotoma.io/neotoma-with-chatgpt) · [Codex](https://neotoma.io/neotoma-with-codex) · [OpenCode](docs/integrations/hooks/opencode.md) · [OpenClaw](https://neotoma.io/neotoma-with-openclaw) · [IronClaw](https://neotoma.io/neotoma-with-ironclaw)
221
223
 
222
- For local source iteration, use the stable dev shim (`scripts/run_neotoma_mcp_stdio_dev_shim.sh` or `npm run dev:mcp:dev-shim`) instead of pointing installed MCP clients at a `tsx watch` stdio process. The shim keeps the client-facing JSON-RPC stream stable and asks clients to refresh or reconnect when the tool interface changes.
224
+ For local source iteration, use the stable dev shim (`scripts/run_neotoma_mcp_stdio_dev_shim.sh`) or signed shim (`scripts/run_neotoma_mcp_signed_stdio_dev_shim.sh`) instead of pointing installed MCP clients at a `tsx watch` stdio process. `neotoma mcp check` defaults to **`a`**: signed + AAuth with **neotoma-dev dev** and **neotoma prod** HTTP `/mcp` behind stdio; use `--mcp-transport c` for direct stdio, **`d`** if both MCP entries should target prod.
223
225
 
224
226
  **Agent behavior contract:** Store first, retrieve before storing, extract entities from user input, create tasks for commitments, and attach bounded host context such as repository name/root scope when available. Full instructions: [MCP instructions](docs/developer/mcp/instructions.md) and [CLI agent instructions](docs/developer/cli_agent_instructions.md).
225
227
 
package/dist/actions.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import express from "express";
2
+ import { type StoreInterpretationInput } from "./shared/action_schemas.js";
2
3
  export declare const app: import("express-serve-static-core").Express;
3
4
  /**
4
5
  * True when the request arrived over a loopback socket.
@@ -38,6 +39,8 @@ export declare function storeStructuredForApi(params: {
38
39
  idempotencyKey: string;
39
40
  originalFilename?: string;
40
41
  relationships?: StoreRelationshipRef[];
42
+ interpretation?: StoreInterpretationInput;
43
+ interpretationSourceId?: string;
41
44
  commit?: boolean;
42
45
  strict?: boolean;
43
46
  }): Promise<{
@@ -61,10 +64,6 @@ export declare function storeStructuredForApi(params: {
61
64
  identity_basis: string;
62
65
  identity_rule: string;
63
66
  }[] | undefined;
64
- success: boolean;
65
- replayed: boolean;
66
- commit: boolean;
67
- source_id: string | null;
68
67
  entities_created: number;
69
68
  observations_created: number;
70
69
  entities: {
@@ -91,6 +90,12 @@ export declare function storeStructuredForApi(params: {
91
90
  source_entity_id: string;
92
91
  target_entity_id: string;
93
92
  }[];
93
+ interpretation_id?: string | undefined;
94
+ interpretation_source_id?: string | undefined;
95
+ success: boolean;
96
+ replayed: boolean;
97
+ commit: boolean;
98
+ source_id: string | null;
94
99
  }>;
95
100
  export declare function startHTTPServer(): Promise<{
96
101
  server: import("http").Server<typeof import("http").IncomingMessage, typeof import("http").ServerResponse>;
@@ -1 +1 @@
1
- {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAmK9B,eAAO,MAAM,GAAG,6CAAY,CAAC;AAif7B;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,GAAG,OAAO,CAU5D;AA4PD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,CAmBhD;AAyrHD,KAAK,oBAAoB,GAAG;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC,CAAC;AAEF,wBAAsB,qBAAqB,CAAC,MAAM,EAAE;IAClD,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,CAAC,EAAE,OAAO,4BAA4B,EAAE,iBAAiB,CAAC;IAC3E,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,aAAa,CAAC,EAAE,oBAAoB,EAAE,CAAC;IACvC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;;;;;;;;;;;;;cA+fS,MAAM;gBACJ,MAAM;2BACK,MAAM;qBACZ,MAAM;mBACR,MAAM;wBACD,MAAM;uBACP,MAAM;;;;;;;;;mBA3JV,MAAM;qBACJ,MAAM;wBACH,MAAM,GAAG,IAAI;2BACV,MAAM;;wBAET,MAAM;uBACP,MAAM,EAAE;wBACP,MAAM;uBACP,MAAM;;kBA9NX,MAAM;oBACJ,MAAM;yBACD,MAAM;4BACH,MAAM;2BACP,MAAM;;+BA4NF,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;;;2BAkFlC,MAAM;0BACP,MAAM;0BACN,MAAM;;GA2F3B;AAuhED,wBAAsB,eAAe;;;eA2FpC"}
1
+ {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AA2G9B,OAAO,EAyBL,KAAK,wBAAwB,EAG9B,MAAM,4BAA4B,CAAC;AA+BpC,eAAO,MAAM,GAAG,6CAAY,CAAC;AAygB7B;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,GAAG,OAAO,CAU5D;AA4PD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,CAmBhD;AAiyHD,KAAK,oBAAoB,GAAG;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC,CAAC;AAuDF,wBAAsB,qBAAqB,CAAC,MAAM,EAAE;IAClD,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,CAAC,EAAE,OAAO,4BAA4B,EAAE,iBAAiB,CAAC;IAC3E,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,aAAa,CAAC,EAAE,oBAAoB,EAAE,CAAC;IACvC,cAAc,CAAC,EAAE,wBAAwB,CAAC;IAC1C,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;;;;;;;;;;;;;cA+gBS,MAAM;gBACJ,MAAM;2BACK,MAAM;qBACZ,MAAM;mBACR,MAAM;wBACD,MAAM;uBACP,MAAM;;;;;mBA3JV,MAAM;qBACJ,MAAM;wBACH,MAAM,GAAG,IAAI;2BACV,MAAM;;wBAET,MAAM;uBACP,MAAM,EAAE;wBACP,MAAM;uBACP,MAAM;;kBA9NX,MAAM;oBACJ,MAAM;yBACD,MAAM;4BACH,MAAM;2BACP,MAAM;;+BA4NF,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;;;2BAkFlC,MAAM;0BACP,MAAM;0BACN,MAAM;;;;;;;;GAsG3B;AA+iED,wBAAsB,eAAe;;;eA2FpC"}
package/dist/actions.js CHANGED
@@ -19,10 +19,10 @@ import { aauthVerify, getAAuthContextFromRequest, getAttributionDecisionFromRequ
19
19
  import { attributionContext } from "./middleware/attribution_context.js";
20
20
  import { aauthAdmission, getAAuthAdmissionFromRequest, } from "./middleware/aauth_admission.js";
21
21
  import { buildSessionInfo } from "./services/session_info.js";
22
- import { AttributionPolicyError } from "./services/attribution_policy.js";
22
+ import { AttributionPolicyError, enforceAttributionPolicy } from "./services/attribution_policy.js";
23
23
  import { registerFeedbackAdminProxyRoutes } from "./services/feedback/admin_proxy.js";
24
24
  import { AgentCapabilityError, contextFromAgentIdentity, enforceAgentCapability, } from "./services/agent_capabilities.js";
25
- import { getCurrentAAuthAdmission, getCurrentAgentIdentity, runWithRequestContext, } from "./services/request_context.js";
25
+ import { getCurrentAAuthAdmission, getCurrentAgentIdentity, getCurrentAttribution, runWithRequestContext, } from "./services/request_context.js";
26
26
  import { assertCanWriteProtectedBatch } from "./services/protected_entity_types.js";
27
27
  import { createAgentIdentity as buildAgentIdentity, getAgentIdentityFromRequest, normaliseClientName, } from "./crypto/agent_identity.js";
28
28
  import { initServerKeys } from "./services/encryption_service.js";
@@ -43,7 +43,7 @@ import { resolveSandboxReportTransport } from "./services/sandbox/transport.js";
43
43
  import { getSqliteDb } from "./repositories/sqlite/sqlite_client.js";
44
44
  import { getMcpAuthToken } from "./crypto/mcp_auth_token.js";
45
45
  import { isOauthKeyCredentialValid, normalizeOauthNextPath, OAuthKeySessionStore, } from "./services/oauth_key_gate.js";
46
- import { AnalyzeSchemaCandidatesRequestSchema, CorrectEntityRequestSchema, CreateRelationshipsRequestSchema, CreateRelationshipRequestSchema, DeleteEntityRequestSchema, DeleteRelationshipRequestSchema, EntitiesQueryRequestSchema, EntitySnapshotRequestSchema, FieldProvenanceRequestSchema, GetSchemaRecommendationsRequestSchema, ListObservationsRequestSchema, ListRelationshipsRequestSchema, MergeEntitiesRequestSchema, SplitEntityRequestSchema, ObservationsQueryRequestSchema, RegisterSchemaRequestSchema, RelationshipSnapshotRequestSchema, RestoreEntityRequestSchema, RestoreRelationshipRequestSchema, RetrieveEntityByIdentifierSchema, RetrieveGraphNeighborhoodSchema, RetrieveRelatedEntitiesSchema, StoreRequestSchema, StoreUnstructuredRequestSchema, UpdateSchemaIncrementalRequestSchema, } from "./shared/action_schemas.js";
46
+ import { AnalyzeSchemaCandidatesRequestSchema, CorrectEntityRequestSchema, CreateInterpretationRequestSchema, CreateRelationshipsRequestSchema, CreateRelationshipRequestSchema, DeleteEntityRequestSchema, DeleteRelationshipRequestSchema, EntitiesQueryRequestSchema, EntitySnapshotRequestSchema, FieldProvenanceRequestSchema, GetSchemaRecommendationsRequestSchema, ListObservationsRequestSchema, ListRelationshipsRequestSchema, MergeEntitiesRequestSchema, SplitEntityRequestSchema, ObservationsQueryRequestSchema, RegisterSchemaRequestSchema, RelationshipSnapshotRequestSchema, RestoreEntityRequestSchema, RestoreRelationshipRequestSchema, RetrieveEntityByIdentifierSchema, RetrieveGraphNeighborhoodSchema, RetrieveRelatedEntitiesSchema, StoreRequestSchema, StoreUnstructuredRequestSchema, UpdateSchemaIncrementalRequestSchema, } from "./shared/action_schemas.js";
47
47
  import { getMimeTypeFromExtension } from "./services/file_text_extraction.js";
48
48
  import { queryEntitiesWithCount } from "./shared/action_handlers/entity_handlers.js";
49
49
  import { retrieveEntityByIdentifierWithFallback } from "./shared/action_handlers/entity_identifier_handler.js";
@@ -496,6 +496,24 @@ app.get("/server-info", (_req, res) => {
496
496
  neotoma_env: readNeotomaConfigEnvironment(),
497
497
  });
498
498
  });
499
+ app.get("/mcp-interaction-instructions", (_req, res) => {
500
+ const instructionsPath = path.join(config.projectRoot, "docs", "developer", "mcp", "instructions.md");
501
+ try {
502
+ const raw = fs.readFileSync(instructionsPath, "utf-8");
503
+ const match = raw.match(/```\s*\n?([\s\S]*?)```/);
504
+ if (match && match[1]) {
505
+ const text = match[1].trim();
506
+ if (text) {
507
+ res.type("text/plain").send(text);
508
+ return;
509
+ }
510
+ }
511
+ }
512
+ catch {
513
+ // fall through to 404
514
+ }
515
+ res.status(404).json({ error: "instructions_not_found" });
516
+ });
499
517
  // ============================================================================
500
518
  // MCP StreamableHTTP Endpoint (OAuth-enabled MCP transport)
501
519
  // ============================================================================
@@ -1689,20 +1707,37 @@ app.post("/mcp/oauth/token", oauthTokenLimit, express.urlencoded({ extended: tru
1689
1707
  try {
1690
1708
  const grant_type = req.body?.grant_type;
1691
1709
  const code = req.body?.code;
1710
+ const refresh_token = req.body?.refresh_token;
1692
1711
  logger.info("[MCP OAuth] Token request received", {
1693
1712
  grant_type: grant_type ?? null,
1694
1713
  has_code: typeof code === "string" && code.length > 0,
1695
1714
  code_hint: typeof code === "string" ? code.slice(0, 8) : null,
1715
+ has_refresh_token: typeof refresh_token === "string" && refresh_token.length > 0,
1696
1716
  host: req.header("host") ?? null,
1697
1717
  });
1698
- if (grant_type !== "authorization_code") {
1718
+ if (grant_type !== "authorization_code" && grant_type !== "refresh_token") {
1699
1719
  logger.warn("[MCP OAuth] Token rejected: unsupported grant_type", {
1700
1720
  grant_type: grant_type ?? null,
1701
1721
  });
1702
1722
  return res.status(400).json({
1703
1723
  error: "unsupported_grant_type",
1704
- error_description: "Only authorization_code is supported",
1724
+ error_description: "Only authorization_code and refresh_token are supported",
1725
+ });
1726
+ }
1727
+ if (grant_type === "refresh_token") {
1728
+ if (!refresh_token || typeof refresh_token !== "string") {
1729
+ logger.warn("[MCP OAuth] Token refresh rejected: missing refresh_token");
1730
+ return res
1731
+ .status(400)
1732
+ .json({ error: "invalid_request", error_description: "refresh_token is required" });
1733
+ }
1734
+ const { refreshAccessToken } = await import("./services/mcp_oauth.js");
1735
+ const token = await refreshAccessToken(refresh_token);
1736
+ logger.info("[MCP OAuth] Token refreshed", {
1737
+ has_refresh_token: Boolean(token.refresh_token),
1705
1738
  });
1739
+ res.setHeader("Content-Type", "application/json");
1740
+ return res.json(token);
1706
1741
  }
1707
1742
  if (!code || typeof code !== "string") {
1708
1743
  logger.warn("[MCP OAuth] Token rejected: missing code");
@@ -3774,6 +3809,83 @@ app.get("/interpretations", async (req, res) => {
3774
3809
  return sendError(res, 500, "DB_QUERY_FAILED", message);
3775
3810
  }
3776
3811
  });
3812
+ app.post("/interpretations/create", async (req, res) => {
3813
+ const parsed = CreateInterpretationRequestSchema.safeParse(req.body);
3814
+ if (!parsed.success) {
3815
+ logWarn("ValidationError:interpretations_create", req, { issues: parsed.error.issues });
3816
+ return sendValidationError(res, parsed.error.issues);
3817
+ }
3818
+ try {
3819
+ const userId = await getAuthenticatedUserId(req, parsed.data.user_id);
3820
+ const { data: source, error: sourceError } = await db
3821
+ .from("sources")
3822
+ .select("id")
3823
+ .eq("id", parsed.data.source_id)
3824
+ .eq("user_id", userId)
3825
+ .maybeSingle();
3826
+ if (sourceError)
3827
+ throw sourceError;
3828
+ if (!source) {
3829
+ return sendError(res, 404, "SOURCE_NOT_FOUND", "Source not found for authenticated user");
3830
+ }
3831
+ const { runInterpretation } = await import("./services/interpretation.js");
3832
+ const interpretationResult = await runInterpretation({
3833
+ userId,
3834
+ sourceId: parsed.data.source_id,
3835
+ extractedData: parsed.data.entities,
3836
+ config: normalizeInterpretationConfig(parsed.data.interpretation_config),
3837
+ });
3838
+ const relationshipsCreated = [];
3839
+ if (parsed.data.relationships?.length) {
3840
+ const { relationshipsService } = await import("./services/relationships.js");
3841
+ for (const rel of parsed.data.relationships) {
3842
+ const sourceEntityId = typeof rel.source_entity_id === "string"
3843
+ ? rel.source_entity_id
3844
+ : typeof rel.source_index === "number"
3845
+ ? interpretationResult.entities[rel.source_index]?.entityId
3846
+ : undefined;
3847
+ const targetEntityId = typeof rel.target_entity_id === "string"
3848
+ ? rel.target_entity_id
3849
+ : typeof rel.target_index === "number"
3850
+ ? interpretationResult.entities[rel.target_index]?.entityId
3851
+ : undefined;
3852
+ if (!sourceEntityId || !targetEntityId)
3853
+ continue;
3854
+ await relationshipsService.createRelationship({
3855
+ source_entity_id: sourceEntityId,
3856
+ target_entity_id: targetEntityId,
3857
+ relationship_type: rel.relationship_type,
3858
+ source_id: parsed.data.source_id,
3859
+ metadata: rel.metadata ?? {},
3860
+ user_id: userId,
3861
+ });
3862
+ relationshipsCreated.push({
3863
+ relationship_type: rel.relationship_type,
3864
+ source_entity_id: sourceEntityId,
3865
+ target_entity_id: targetEntityId,
3866
+ });
3867
+ }
3868
+ }
3869
+ return res.json({
3870
+ success: true,
3871
+ interpretation_id: interpretationResult.interpretationId,
3872
+ source_id: parsed.data.source_id,
3873
+ entities: interpretationResult.entities.map((entity) => ({
3874
+ entity_id: entity.entityId,
3875
+ entity_type: entity.entityType,
3876
+ observation_id: entity.observationId,
3877
+ })),
3878
+ observations_created: interpretationResult.observationsCreated,
3879
+ unknown_fields_count: interpretationResult.unknownFieldsCount,
3880
+ relationships_created: relationshipsCreated,
3881
+ });
3882
+ }
3883
+ catch (error) {
3884
+ logError("APIError:interpretations_create", req, error);
3885
+ const message = error instanceof Error ? error.message : "Failed to create interpretation";
3886
+ return sendError(res, 500, "INTERPRETATION_CREATE_FAILED", message);
3887
+ }
3888
+ });
3777
3889
  // GET /api/observations - Get observations with filters (FU-302, FU-601)
3778
3890
  // REQUIRES AUTHENTICATION - all queries filtered by authenticated user_id
3779
3891
  app.get("/observations", async (req, res) => {
@@ -3889,8 +4001,47 @@ app.post("/observations/create", async (req, res) => {
3889
4001
  return res.status(500).json(buildErrorEnvelope("DB_QUERY_FAILED", message));
3890
4002
  }
3891
4003
  });
4004
+ function normalizeInterpretationConfig(configInput) {
4005
+ return {
4006
+ extractor_type: "agent",
4007
+ extractor_version: "unknown",
4008
+ schema_version: "1.0",
4009
+ ...configInput,
4010
+ };
4011
+ }
4012
+ async function createInterpretationRunForSource(params) {
4013
+ enforceAttributionPolicy("interpretations", getCurrentAgentIdentity());
4014
+ const attribution = getCurrentAttribution();
4015
+ const { data, error } = await db
4016
+ .from("interpretations")
4017
+ .insert({
4018
+ user_id: params.userId,
4019
+ source_id: params.sourceId,
4020
+ interpretation_config: normalizeInterpretationConfig(params.interpretationConfig),
4021
+ status: "running",
4022
+ started_at: new Date().toISOString(),
4023
+ ...(Object.keys(attribution).length > 0 ? { provenance: attribution } : {}),
4024
+ })
4025
+ .select("id")
4026
+ .single();
4027
+ if (error) {
4028
+ throw new Error(`Failed to create interpretation run: ${error.message}`);
4029
+ }
4030
+ return data.id;
4031
+ }
4032
+ async function completeInterpretationRun(params) {
4033
+ await db
4034
+ .from("interpretations")
4035
+ .update({
4036
+ status: "completed",
4037
+ completed_at: new Date().toISOString(),
4038
+ observations_created: params.observationsCreated,
4039
+ unknown_fields_count: params.unknownFieldsCount ?? 0,
4040
+ })
4041
+ .eq("id", params.interpretationId);
4042
+ }
3892
4043
  export async function storeStructuredForApi(params) {
3893
- const { userId, entities, sourcePriority, observationSource, idempotencyKey, originalFilename, relationships, commit: commitInput, strict: strictInput, } = params;
4044
+ const { userId, entities, sourcePriority, observationSource, idempotencyKey, originalFilename, relationships, interpretation, interpretationSourceId, commit: commitInput, strict: strictInput, } = params;
3894
4045
  const commit = commitInput !== false;
3895
4046
  const strict = strictInput === true;
3896
4047
  // Capability scoping: when the caller is an AAuth-verified agent covered
@@ -3989,6 +4140,17 @@ export async function storeStructuredForApi(params) {
3989
4140
  source_priority: sourcePriority,
3990
4141
  },
3991
4142
  });
4143
+ const resolvedInterpretationSourceId = interpretation?.source_id ??
4144
+ interpretationSourceId ??
4145
+ (interpretation?.source_ref === "structured" ? storageResult.sourceId : undefined);
4146
+ const interpretationId = commit && interpretation && resolvedInterpretationSourceId
4147
+ ? await createInterpretationRunForSource({
4148
+ userId,
4149
+ sourceId: resolvedInterpretationSourceId,
4150
+ interpretationConfig: interpretation.interpretation_config,
4151
+ })
4152
+ : null;
4153
+ const observationSourceId = resolvedInterpretationSourceId ?? storageResult.sourceId;
3992
4154
  // v0.5.1: structured guidance for the v0.5.0 breaking change where callers
3993
4155
  // nested fields under `attributes`. If resolution failed and the only
3994
4156
  // observed top-level keys are `attributes` (plus optionally `entity_type`),
@@ -4163,8 +4325,8 @@ export async function storeStructuredForApi(params) {
4163
4325
  entity_id: r.entity_id,
4164
4326
  entity_type: r.entity_type,
4165
4327
  schema_version: "1.0",
4166
- source_id: storageResult.sourceId,
4167
- interpretation_id: null,
4328
+ source_id: observationSourceId,
4329
+ interpretation_id: interpretationId,
4168
4330
  observed_at: new Date().toISOString(),
4169
4331
  specificity_score: 1.0,
4170
4332
  source_priority: sourcePriority,
@@ -4199,7 +4361,7 @@ export async function storeStructuredForApi(params) {
4199
4361
  fields: r.fields,
4200
4362
  schema: schemaEntry.schema_definition,
4201
4363
  userId,
4202
- sourceId: storageResult.sourceId,
4364
+ sourceId: observationSourceId,
4203
4365
  });
4204
4366
  }
4205
4367
  }
@@ -4251,7 +4413,7 @@ export async function storeStructuredForApi(params) {
4251
4413
  source_entity_id: sourceEntityId,
4252
4414
  target_entity_id: targetEntityId,
4253
4415
  relationship_type: rel.relationship_type,
4254
- source_id: storageResult.sourceId,
4416
+ source_id: observationSourceId,
4255
4417
  metadata: rel.metadata ?? {},
4256
4418
  user_id: userId,
4257
4419
  });
@@ -4286,11 +4448,21 @@ export async function storeStructuredForApi(params) {
4286
4448
  });
4287
4449
  }
4288
4450
  }
4451
+ if (commit && interpretationId) {
4452
+ await completeInterpretationRun({
4453
+ interpretationId,
4454
+ observationsCreated: createdEntities.filter((e) => e.observation_id).length,
4455
+ unknownFieldsCount: 0,
4456
+ });
4457
+ }
4289
4458
  return {
4290
4459
  success: true,
4291
4460
  replayed: false,
4292
4461
  commit,
4293
4462
  source_id: commit ? storageResult.sourceId : null,
4463
+ ...(interpretationId
4464
+ ? { interpretation_id: interpretationId, interpretation_source_id: observationSourceId }
4465
+ : {}),
4294
4466
  entities_created: commit
4295
4467
  ? createdEntities.filter((e) => e.action === "created" || e.action === "extended").length
4296
4468
  : 0,
@@ -4347,23 +4519,7 @@ async function handleStorePost(req, res) {
4347
4519
  const hasUnstructured = hasFileContent || hasFilePath;
4348
4520
  let structuredResult;
4349
4521
  let unstructuredResult;
4350
- if (hasEntities) {
4351
- if (!parsed.data.idempotency_key) {
4352
- return sendError(res, 400, "VALIDATION_ERROR", "idempotency_key is required when entities are provided");
4353
- }
4354
- structuredResult = await storeStructuredForApi({
4355
- userId,
4356
- entities: parsed.data.entities,
4357
- sourcePriority: parsed.data.source_priority ?? 100,
4358
- observationSource: parsed.data.observation_source,
4359
- idempotencyKey: parsed.data.idempotency_key,
4360
- originalFilename: parsed.data.original_filename,
4361
- relationships: parsed.data.relationships,
4362
- commit: parsed.data.commit,
4363
- strict: parsed.data.strict,
4364
- });
4365
- }
4366
- if (hasUnstructured) {
4522
+ const storeUnstructuredFromRequest = async () => {
4367
4523
  const fileContent = parsed.data.file_content;
4368
4524
  let mimeType = parsed.data.mime_type;
4369
4525
  let originalFilename = parsed.data.original_filename;
@@ -4380,9 +4536,10 @@ async function handleStorePost(req, res) {
4380
4536
  originalFilename = originalFilename || path.basename(resolvedPath);
4381
4537
  }
4382
4538
  if ((!fileContent && !resolvedFileBuffer) || !mimeType) {
4383
- return sendError(res, 400, "VALIDATION_ERROR", "Unstructured store requires file_content+mime_type or file_path");
4539
+ sendError(res, 400, "VALIDATION_ERROR", "Unstructured store requires file_content+mime_type or file_path");
4540
+ return null;
4384
4541
  }
4385
- unstructuredResult = await storeUnstructuredForApi({
4542
+ return await storeUnstructuredForApi({
4386
4543
  userId,
4387
4544
  fileContent,
4388
4545
  fileBuffer: resolvedFileBuffer,
@@ -4391,6 +4548,42 @@ async function handleStorePost(req, res) {
4391
4548
  (!hasEntities ? parsed.data.idempotency_key : undefined),
4392
4549
  originalFilename,
4393
4550
  });
4551
+ };
4552
+ if (hasUnstructured && parsed.data.interpretation?.source_ref === "unstructured") {
4553
+ const result = await storeUnstructuredFromRequest();
4554
+ if (!result) {
4555
+ return;
4556
+ }
4557
+ unstructuredResult = result;
4558
+ }
4559
+ if (hasEntities) {
4560
+ if (!parsed.data.idempotency_key) {
4561
+ return sendError(res, 400, "VALIDATION_ERROR", "idempotency_key is required when entities are provided");
4562
+ }
4563
+ structuredResult = await storeStructuredForApi({
4564
+ userId,
4565
+ entities: parsed.data.entities,
4566
+ sourcePriority: parsed.data.source_priority ?? 100,
4567
+ observationSource: parsed.data.observation_source,
4568
+ idempotencyKey: parsed.data.idempotency_key,
4569
+ originalFilename: parsed.data.original_filename,
4570
+ relationships: parsed.data.relationships,
4571
+ interpretation: parsed.data.interpretation,
4572
+ interpretationSourceId: parsed.data.interpretation?.source_ref === "unstructured" &&
4573
+ unstructuredResult &&
4574
+ typeof unstructuredResult.source_id === "string"
4575
+ ? unstructuredResult.source_id
4576
+ : undefined,
4577
+ commit: parsed.data.commit,
4578
+ strict: parsed.data.strict,
4579
+ });
4580
+ }
4581
+ if (hasUnstructured && !unstructuredResult) {
4582
+ const result = await storeUnstructuredFromRequest();
4583
+ if (!result) {
4584
+ return;
4585
+ }
4586
+ unstructuredResult = result;
4394
4587
  }
4395
4588
  if (structuredResult && unstructuredResult) {
4396
4589
  return res.json({