@totalreclaw/totalreclaw 3.3.12-rc.2 → 3.3.12-rc.21

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 (87) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/SKILL.md +34 -249
  3. package/api-client.ts +17 -9
  4. package/batch-gate.ts +42 -0
  5. package/billing-cache.ts +25 -20
  6. package/claims-helper.ts +7 -1
  7. package/config.ts +54 -12
  8. package/consolidation.ts +6 -13
  9. package/contradiction-sync.ts +19 -14
  10. package/credential-provider.ts +184 -0
  11. package/crypto.ts +27 -160
  12. package/dist/api-client.js +17 -9
  13. package/dist/batch-gate.js +40 -0
  14. package/dist/billing-cache.js +19 -21
  15. package/dist/claims-helper.js +7 -1
  16. package/dist/config.js +54 -12
  17. package/dist/consolidation.js +6 -15
  18. package/dist/contradiction-sync.js +15 -15
  19. package/dist/credential-provider.js +145 -0
  20. package/dist/crypto.js +17 -137
  21. package/dist/download-ux.js +11 -7
  22. package/dist/embedder-loader.js +266 -0
  23. package/dist/embedding.js +36 -3
  24. package/dist/entry.js +123 -0
  25. package/dist/extractor.js +134 -0
  26. package/dist/fs-helpers.js +116 -241
  27. package/dist/import-adapters/chatgpt-adapter.js +14 -0
  28. package/dist/import-adapters/claude-adapter.js +14 -0
  29. package/dist/import-adapters/gemini-adapter.js +43 -159
  30. package/dist/import-adapters/mcp-memory-adapter.js +14 -0
  31. package/dist/import-state-manager.js +100 -0
  32. package/dist/index.js +1130 -2520
  33. package/dist/llm-client.js +69 -1
  34. package/dist/memory-runtime.js +459 -0
  35. package/dist/native-memory.js +123 -0
  36. package/dist/onboarding-cli.js +3 -2
  37. package/dist/pair-cli.js +1 -1
  38. package/dist/pair-crypto.js +16 -358
  39. package/dist/pair-http.js +147 -4
  40. package/dist/relay.js +140 -0
  41. package/dist/reranker.js +13 -8
  42. package/dist/semantic-dedup.js +5 -7
  43. package/dist/skill-register.js +97 -0
  44. package/dist/subgraph-search.js +3 -1
  45. package/dist/subgraph-store.js +315 -282
  46. package/dist/tool-gating.js +39 -26
  47. package/dist/tools.js +367 -0
  48. package/dist/tr-cli-export-helper.js +103 -0
  49. package/dist/tr-cli.js +220 -127
  50. package/dist/trajectory-poller.js +155 -9
  51. package/dist/vault-crypto.js +551 -0
  52. package/download-ux.ts +12 -6
  53. package/embedder-loader.ts +293 -1
  54. package/embedding.ts +43 -3
  55. package/entry.ts +132 -0
  56. package/extractor.ts +167 -0
  57. package/fs-helpers.ts +166 -292
  58. package/import-adapters/chatgpt-adapter.ts +18 -0
  59. package/import-adapters/claude-adapter.ts +18 -0
  60. package/import-adapters/gemini-adapter.ts +56 -183
  61. package/import-adapters/mcp-memory-adapter.ts +18 -0
  62. package/import-adapters/types.ts +1 -1
  63. package/import-state-manager.ts +139 -0
  64. package/index.ts +1432 -3002
  65. package/llm-client.ts +74 -1
  66. package/memory-runtime.ts +723 -0
  67. package/native-memory.ts +196 -0
  68. package/onboarding-cli.ts +3 -2
  69. package/openclaw.plugin.json +5 -17
  70. package/package.json +7 -4
  71. package/pair-cli.ts +1 -1
  72. package/pair-crypto.ts +41 -483
  73. package/pair-http.ts +194 -5
  74. package/postinstall.mjs +138 -0
  75. package/relay.ts +172 -0
  76. package/reranker.ts +13 -8
  77. package/semantic-dedup.ts +5 -6
  78. package/skill-register.ts +146 -0
  79. package/skill.json +1 -1
  80. package/subgraph-search.ts +3 -1
  81. package/subgraph-store.ts +334 -299
  82. package/tool-gating.ts +39 -26
  83. package/tools.ts +499 -0
  84. package/tr-cli-export-helper.ts +138 -0
  85. package/tr-cli.ts +263 -133
  86. package/trajectory-poller.ts +162 -10
  87. package/vault-crypto.ts +705 -0
package/extractor.ts CHANGED
@@ -199,6 +199,24 @@ export interface ExtractedFact {
199
199
  * is skipped (facts.length < 5), by the `defaultVolatility` heuristic.
200
200
  */
201
201
  volatility?: MemoryVolatility;
202
+ /**
203
+ * am-1: Crystal-shaped debrief structured metadata. When present,
204
+ * re-attached as `metadata` on the v1 blob after core validation.
205
+ * Only set on Crystal debrief facts (type='summary', source='derived').
206
+ */
207
+ crystalMetadata?: CrystalMetadata;
208
+ }
209
+
210
+ /** Structured metadata for a Crystal-shaped session summary (am-1). */
211
+ export interface CrystalMetadata {
212
+ subtype: 'session_crystal';
213
+ key_outcomes: string[];
214
+ open_threads: string[];
215
+ lessons: string[];
216
+ session_id?: string;
217
+ source_message_ids?: string[];
218
+ files_affected?: string[]; // coding hosts (OpenClaw, MCP)
219
+ topics_discussed?: string[]; // chat hosts (Hermes, NanoClaw)
202
220
  }
203
221
 
204
222
  const ALLOWED_ENTITY_TYPES: ReadonlySet<EntityType> = new Set([
@@ -925,6 +943,155 @@ export async function extractDebrief(
925
943
  }
926
944
  }
927
945
 
946
+ // ---------------------------------------------------------------------------
947
+ // Crystal-shaped debrief (am-1) — one structured summary per session
948
+ // ---------------------------------------------------------------------------
949
+
950
+ const _CRYSTAL_COMMON_FIELDS = `Return a JSON object (no markdown, no code fences):
951
+ {
952
+ "narrative": "1-2 sentence summary of what happened this session",
953
+ "key_outcomes": ["outcome or decision 1", "outcome or decision 2"],
954
+ "open_threads": ["unfinished item 1"],
955
+ "lessons": ["lesson or gotcha 1"],
956
+ "importance": 8
957
+ }`;
958
+
959
+ export const CRYSTAL_SYSTEM_PROMPT_CHAT = `You are crystallising a finished conversation into one structured session summary.
960
+
961
+ The following facts were already extracted and stored during this conversation:
962
+ {already_stored_facts}
963
+
964
+ Write ONE Crystal that captures what turn-by-turn extraction missed. Include:
965
+ - narrative: 1-2 sentences describing the conversation overall
966
+ - key_outcomes: decisions made, problems solved, conclusions reached
967
+ - open_threads: things left unfinished or needing follow-up
968
+ - lessons: patterns, gotchas, or insights worth remembering
969
+ - topics_discussed: the main subjects covered
970
+ - importance: 7-10 (8 default)
971
+
972
+ Do NOT repeat facts already stored. Return null for trivial or very short conversations.
973
+
974
+ ${_CRYSTAL_COMMON_FIELDS}
975
+
976
+ Also include "topics_discussed": ["topic 1", "topic 2"] in the object.
977
+ Return null (not an object) for trivial or very short conversations.`;
978
+
979
+ export const CRYSTAL_SYSTEM_PROMPT_CODING = `You are crystallising a finished coding session into one structured session summary.
980
+
981
+ The following facts were already extracted and stored during this conversation:
982
+ {already_stored_facts}
983
+
984
+ Write ONE Crystal that captures what turn-by-turn extraction missed. Include:
985
+ - narrative: 1-2 sentences describing the session overall
986
+ - key_outcomes: decisions made, bugs fixed, features built
987
+ - open_threads: things left unfinished or needing follow-up
988
+ - lessons: patterns, gotchas, or insights worth remembering
989
+ - files_affected: file paths mentioned or worked on (extract from assistant messages)
990
+ - importance: 7-10 (8 default)
991
+
992
+ Do NOT repeat facts already stored. Return null for trivial or very short sessions.
993
+
994
+ ${_CRYSTAL_COMMON_FIELDS}
995
+
996
+ Also include "files_affected": ["/path/to/file.ts", ...] in the object (may be []).
997
+ Return null (not an object) for trivial or very short sessions.`;
998
+
999
+ /** Parse an LLM Crystal response into a CrystalMetadata + narrative pair. */
1000
+ export function parseCrystalResponse(
1001
+ response: string,
1002
+ hostType: 'chat' | 'coding' = 'chat',
1003
+ ): { narrative: string; importance: number; metadata: CrystalMetadata } | null {
1004
+ let cleaned = response.trim();
1005
+ if (cleaned.startsWith('```')) {
1006
+ cleaned = cleaned.replace(/^```(?:json)?\n?/, '').replace(/\n?```$/, '').trim();
1007
+ }
1008
+ if (cleaned.toLowerCase() === 'null' || cleaned === '') return null;
1009
+
1010
+ let parsed: unknown;
1011
+ try {
1012
+ parsed = JSON.parse(cleaned);
1013
+ } catch {
1014
+ return null;
1015
+ }
1016
+
1017
+ if (parsed === null) return null;
1018
+ if (typeof parsed !== 'object' || Array.isArray(parsed)) return null;
1019
+
1020
+ const d = parsed as Record<string, unknown>;
1021
+ const narrative = typeof d.narrative === 'string' ? d.narrative.trim().slice(0, 512) : '';
1022
+ if (narrative.length < 10) return null;
1023
+
1024
+ const strList = (key: string): string[] => {
1025
+ const raw = d[key];
1026
+ if (!Array.isArray(raw)) return [];
1027
+ return raw.map((x) => String(x).trim()).filter((x) => x.length > 0).slice(0, 10);
1028
+ };
1029
+
1030
+ let importance = typeof d.importance === 'number' ? d.importance : 8;
1031
+ importance = Math.max(1, Math.min(10, Math.round(importance)));
1032
+
1033
+ const metadata: CrystalMetadata = {
1034
+ subtype: 'session_crystal',
1035
+ key_outcomes: strList('key_outcomes'),
1036
+ open_threads: strList('open_threads'),
1037
+ lessons: strList('lessons'),
1038
+ };
1039
+ if (hostType === 'coding') {
1040
+ metadata.files_affected = strList('files_affected');
1041
+ } else {
1042
+ metadata.topics_discussed = strList('topics_discussed');
1043
+ }
1044
+
1045
+ return { narrative, importance, metadata };
1046
+ }
1047
+
1048
+ /**
1049
+ * Extract a Crystal session summary using LLM.
1050
+ *
1051
+ * Returns null if LLM unavailable, session too short, or LLM returns null.
1052
+ */
1053
+ export async function extractCrystal(
1054
+ rawMessages: unknown[],
1055
+ storedFactTexts: string[],
1056
+ hostType: 'chat' | 'coding' = 'coding',
1057
+ ): Promise<{ narrative: string; importance: number; metadata: CrystalMetadata } | null> {
1058
+ const config = resolveLLMConfig();
1059
+ if (!config) return null;
1060
+
1061
+ const parsed = rawMessages
1062
+ .map(messageToText)
1063
+ .filter((m): m is { role: string; content: string } => m !== null);
1064
+
1065
+ if (parsed.length < 8) return null;
1066
+
1067
+ const conversationText = truncateMessages(parsed, 12_000);
1068
+ if (conversationText.length < 20) return null;
1069
+
1070
+ const alreadyStored = storedFactTexts.length > 0
1071
+ ? storedFactTexts.map((t) => `- ${t}`).join('\n')
1072
+ : '(none)';
1073
+
1074
+ const promptTemplate = hostType === 'coding'
1075
+ ? CRYSTAL_SYSTEM_PROMPT_CODING
1076
+ : CRYSTAL_SYSTEM_PROMPT_CHAT;
1077
+ const systemPrompt = promptTemplate.replace('{already_stored_facts}', alreadyStored);
1078
+
1079
+ try {
1080
+ const response = await chatCompletion(config, [
1081
+ { role: 'system', content: systemPrompt },
1082
+ { role: 'user', content: `Crystallise this session:\n\n${conversationText}` },
1083
+ ], {
1084
+ retry: { attempts: 3, baseDelayMs: 1000 },
1085
+ timeoutMs: 30_000,
1086
+ });
1087
+
1088
+ if (!response) return null;
1089
+ return parseCrystalResponse(response, hostType);
1090
+ } catch {
1091
+ return null;
1092
+ }
1093
+ }
1094
+
928
1095
  // ---------------------------------------------------------------------------
929
1096
  // v1 Taxonomy Extraction Pipeline (default as of plugin v3.0.0)
930
1097
  //