iranti 0.1.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.
Files changed (255) hide show
  1. package/.env.example +63 -0
  2. package/LICENSE +12 -0
  3. package/README.md +520 -0
  4. package/bin/iranti.js +24 -0
  5. package/dist/scripts/api-key-create.js +57 -0
  6. package/dist/scripts/api-key-list.js +42 -0
  7. package/dist/scripts/api-key-revoke.js +42 -0
  8. package/dist/scripts/iranti-cli.js +387 -0
  9. package/dist/scripts/seed-codebase.js +138 -0
  10. package/dist/scripts/seed.js +121 -0
  11. package/dist/scripts/setup.js +86 -0
  12. package/dist/src/api/archivistScheduler.d.ts +8 -0
  13. package/dist/src/api/archivistScheduler.d.ts.map +1 -0
  14. package/dist/src/api/archivistScheduler.js +100 -0
  15. package/dist/src/api/archivistScheduler.js.map +1 -0
  16. package/dist/src/api/diag.d.ts +2 -0
  17. package/dist/src/api/diag.d.ts.map +1 -0
  18. package/dist/src/api/diag.js +54 -0
  19. package/dist/src/api/diag.js.map +1 -0
  20. package/dist/src/api/middleware/auth.d.ts +3 -0
  21. package/dist/src/api/middleware/auth.d.ts.map +1 -0
  22. package/dist/src/api/middleware/auth.js +36 -0
  23. package/dist/src/api/middleware/auth.js.map +1 -0
  24. package/dist/src/api/middleware/authorization.d.ts +4 -0
  25. package/dist/src/api/middleware/authorization.d.ts.map +1 -0
  26. package/dist/src/api/middleware/authorization.js +54 -0
  27. package/dist/src/api/middleware/authorization.js.map +1 -0
  28. package/dist/src/api/middleware/rateLimit.d.ts +23 -0
  29. package/dist/src/api/middleware/rateLimit.d.ts.map +1 -0
  30. package/dist/src/api/middleware/rateLimit.js +70 -0
  31. package/dist/src/api/middleware/rateLimit.js.map +1 -0
  32. package/dist/src/api/middleware/validation.d.ts +128 -0
  33. package/dist/src/api/middleware/validation.d.ts.map +1 -0
  34. package/dist/src/api/middleware/validation.js +137 -0
  35. package/dist/src/api/middleware/validation.js.map +1 -0
  36. package/dist/src/api/repro.d.ts +2 -0
  37. package/dist/src/api/repro.d.ts.map +1 -0
  38. package/dist/src/api/repro.js +25 -0
  39. package/dist/src/api/repro.js.map +1 -0
  40. package/dist/src/api/routes/agents.d.ts +4 -0
  41. package/dist/src/api/routes/agents.d.ts.map +1 -0
  42. package/dist/src/api/routes/agents.js +56 -0
  43. package/dist/src/api/routes/agents.js.map +1 -0
  44. package/dist/src/api/routes/batch.d.ts +2 -0
  45. package/dist/src/api/routes/batch.d.ts.map +1 -0
  46. package/dist/src/api/routes/batch.js +63 -0
  47. package/dist/src/api/routes/batch.js.map +1 -0
  48. package/dist/src/api/routes/dev.d.ts +2 -0
  49. package/dist/src/api/routes/dev.d.ts.map +1 -0
  50. package/dist/src/api/routes/dev.js +29 -0
  51. package/dist/src/api/routes/dev.js.map +1 -0
  52. package/dist/src/api/routes/knowledge.d.ts +4 -0
  53. package/dist/src/api/routes/knowledge.d.ts.map +1 -0
  54. package/dist/src/api/routes/knowledge.js +184 -0
  55. package/dist/src/api/routes/knowledge.js.map +1 -0
  56. package/dist/src/api/routes/memory.d.ts +4 -0
  57. package/dist/src/api/routes/memory.d.ts.map +1 -0
  58. package/dist/src/api/routes/memory.js +150 -0
  59. package/dist/src/api/routes/memory.js.map +1 -0
  60. package/dist/src/api/server.d.ts +2 -0
  61. package/dist/src/api/server.d.ts.map +1 -0
  62. package/dist/src/api/server.js +191 -0
  63. package/dist/src/api/server.js.map +1 -0
  64. package/dist/src/archivist/index.d.ts +10 -0
  65. package/dist/src/archivist/index.d.ts.map +1 -0
  66. package/dist/src/archivist/index.js +232 -0
  67. package/dist/src/archivist/index.js.map +1 -0
  68. package/dist/src/attendant/AttendantInstance.d.ts +96 -0
  69. package/dist/src/attendant/AttendantInstance.d.ts.map +1 -0
  70. package/dist/src/attendant/AttendantInstance.js +808 -0
  71. package/dist/src/attendant/AttendantInstance.js.map +1 -0
  72. package/dist/src/attendant/index.d.ts +12 -0
  73. package/dist/src/attendant/index.d.ts.map +1 -0
  74. package/dist/src/attendant/index.js +39 -0
  75. package/dist/src/attendant/index.js.map +1 -0
  76. package/dist/src/attendant/registry.d.ts +6 -0
  77. package/dist/src/attendant/registry.d.ts.map +1 -0
  78. package/dist/src/attendant/registry.js +27 -0
  79. package/dist/src/attendant/registry.js.map +1 -0
  80. package/dist/src/generated/prisma/browser.d.ts +35 -0
  81. package/dist/src/generated/prisma/browser.d.ts.map +1 -0
  82. package/dist/src/generated/prisma/browser.js +57 -0
  83. package/dist/src/generated/prisma/browser.js.map +1 -0
  84. package/dist/src/generated/prisma/client.d.ts +54 -0
  85. package/dist/src/generated/prisma/client.d.ts.map +1 -0
  86. package/dist/src/generated/prisma/client.js +71 -0
  87. package/dist/src/generated/prisma/client.js.map +1 -0
  88. package/dist/src/generated/prisma/commonInputTypes.d.ts +415 -0
  89. package/dist/src/generated/prisma/commonInputTypes.d.ts.map +1 -0
  90. package/dist/src/generated/prisma/commonInputTypes.js +12 -0
  91. package/dist/src/generated/prisma/commonInputTypes.js.map +1 -0
  92. package/dist/src/generated/prisma/enums.d.ts +2 -0
  93. package/dist/src/generated/prisma/enums.d.ts.map +1 -0
  94. package/dist/src/generated/prisma/enums.js +12 -0
  95. package/dist/src/generated/prisma/enums.js.map +1 -0
  96. package/dist/src/generated/prisma/internal/class.d.ts +186 -0
  97. package/dist/src/generated/prisma/internal/class.d.ts.map +1 -0
  98. package/dist/src/generated/prisma/internal/class.js +86 -0
  99. package/dist/src/generated/prisma/internal/class.js.map +1 -0
  100. package/dist/src/generated/prisma/internal/prismaNamespace.d.ts +1015 -0
  101. package/dist/src/generated/prisma/internal/prismaNamespace.d.ts.map +1 -0
  102. package/dist/src/generated/prisma/internal/prismaNamespace.js +220 -0
  103. package/dist/src/generated/prisma/internal/prismaNamespace.js.map +1 -0
  104. package/dist/src/generated/prisma/internal/prismaNamespaceBrowser.d.ts +152 -0
  105. package/dist/src/generated/prisma/internal/prismaNamespaceBrowser.d.ts.map +1 -0
  106. package/dist/src/generated/prisma/internal/prismaNamespaceBrowser.js +191 -0
  107. package/dist/src/generated/prisma/internal/prismaNamespaceBrowser.js.map +1 -0
  108. package/dist/src/generated/prisma/models/Archive.d.ts +1425 -0
  109. package/dist/src/generated/prisma/models/Archive.d.ts.map +1 -0
  110. package/dist/src/generated/prisma/models/Archive.js +3 -0
  111. package/dist/src/generated/prisma/models/Archive.js.map +1 -0
  112. package/dist/src/generated/prisma/models/Entity.d.ts +1129 -0
  113. package/dist/src/generated/prisma/models/Entity.d.ts.map +1 -0
  114. package/dist/src/generated/prisma/models/Entity.js +3 -0
  115. package/dist/src/generated/prisma/models/Entity.js.map +1 -0
  116. package/dist/src/generated/prisma/models/EntityAlias.d.ts +1347 -0
  117. package/dist/src/generated/prisma/models/EntityAlias.d.ts.map +1 -0
  118. package/dist/src/generated/prisma/models/EntityAlias.js +3 -0
  119. package/dist/src/generated/prisma/models/EntityAlias.js.map +1 -0
  120. package/dist/src/generated/prisma/models/EntityRelationship.d.ts +1143 -0
  121. package/dist/src/generated/prisma/models/EntityRelationship.d.ts.map +1 -0
  122. package/dist/src/generated/prisma/models/EntityRelationship.js +3 -0
  123. package/dist/src/generated/prisma/models/EntityRelationship.js.map +1 -0
  124. package/dist/src/generated/prisma/models/KnowledgeEntry.d.ts +1322 -0
  125. package/dist/src/generated/prisma/models/KnowledgeEntry.d.ts.map +1 -0
  126. package/dist/src/generated/prisma/models/KnowledgeEntry.js +3 -0
  127. package/dist/src/generated/prisma/models/KnowledgeEntry.js.map +1 -0
  128. package/dist/src/generated/prisma/models/WriteReceipt.d.ts +1147 -0
  129. package/dist/src/generated/prisma/models/WriteReceipt.d.ts.map +1 -0
  130. package/dist/src/generated/prisma/models/WriteReceipt.js +3 -0
  131. package/dist/src/generated/prisma/models/WriteReceipt.js.map +1 -0
  132. package/dist/src/generated/prisma/models.d.ts +8 -0
  133. package/dist/src/generated/prisma/models.d.ts.map +1 -0
  134. package/dist/src/generated/prisma/models.js +3 -0
  135. package/dist/src/generated/prisma/models.js.map +1 -0
  136. package/dist/src/lib/escalationPaths.d.ts +9 -0
  137. package/dist/src/lib/escalationPaths.d.ts.map +1 -0
  138. package/dist/src/lib/escalationPaths.js +38 -0
  139. package/dist/src/lib/escalationPaths.js.map +1 -0
  140. package/dist/src/lib/llm.d.ts +32 -0
  141. package/dist/src/lib/llm.d.ts.map +1 -0
  142. package/dist/src/lib/llm.js +161 -0
  143. package/dist/src/lib/llm.js.map +1 -0
  144. package/dist/src/lib/metrics.d.ts +21 -0
  145. package/dist/src/lib/metrics.d.ts.map +1 -0
  146. package/dist/src/lib/metrics.js +46 -0
  147. package/dist/src/lib/metrics.js.map +1 -0
  148. package/dist/src/lib/providers/claude.d.ts +7 -0
  149. package/dist/src/lib/providers/claude.d.ts.map +1 -0
  150. package/dist/src/lib/providers/claude.js +9 -0
  151. package/dist/src/lib/providers/claude.js.map +1 -0
  152. package/dist/src/lib/providers/gemini.d.ts +10 -0
  153. package/dist/src/lib/providers/gemini.d.ts.map +1 -0
  154. package/dist/src/lib/providers/gemini.js +40 -0
  155. package/dist/src/lib/providers/gemini.js.map +1 -0
  156. package/dist/src/lib/providers/groq.d.ts +10 -0
  157. package/dist/src/lib/providers/groq.d.ts.map +1 -0
  158. package/dist/src/lib/providers/groq.js +39 -0
  159. package/dist/src/lib/providers/groq.js.map +1 -0
  160. package/dist/src/lib/providers/mistral.d.ts +10 -0
  161. package/dist/src/lib/providers/mistral.d.ts.map +1 -0
  162. package/dist/src/lib/providers/mistral.js +39 -0
  163. package/dist/src/lib/providers/mistral.js.map +1 -0
  164. package/dist/src/lib/providers/mock.d.ts +24 -0
  165. package/dist/src/lib/providers/mock.d.ts.map +1 -0
  166. package/dist/src/lib/providers/mock.js +129 -0
  167. package/dist/src/lib/providers/mock.js.map +1 -0
  168. package/dist/src/lib/providers/ollama.d.ts +10 -0
  169. package/dist/src/lib/providers/ollama.d.ts.map +1 -0
  170. package/dist/src/lib/providers/ollama.js +39 -0
  171. package/dist/src/lib/providers/ollama.js.map +1 -0
  172. package/dist/src/lib/providers/openai.d.ts +11 -0
  173. package/dist/src/lib/providers/openai.d.ts.map +1 -0
  174. package/dist/src/lib/providers/openai.js +38 -0
  175. package/dist/src/lib/providers/openai.js.map +1 -0
  176. package/dist/src/lib/requestContext.d.ts +8 -0
  177. package/dist/src/lib/requestContext.d.ts.map +1 -0
  178. package/dist/src/lib/requestContext.js +10 -0
  179. package/dist/src/lib/requestContext.js.map +1 -0
  180. package/dist/src/lib/router.d.ts +16 -0
  181. package/dist/src/lib/router.d.ts.map +1 -0
  182. package/dist/src/lib/router.js +63 -0
  183. package/dist/src/lib/router.js.map +1 -0
  184. package/dist/src/librarian/chunker.d.ts +16 -0
  185. package/dist/src/librarian/chunker.d.ts.map +1 -0
  186. package/dist/src/librarian/chunker.js +67 -0
  187. package/dist/src/librarian/chunker.js.map +1 -0
  188. package/dist/src/librarian/getPolicy.d.ts +3 -0
  189. package/dist/src/librarian/getPolicy.d.ts.map +1 -0
  190. package/dist/src/librarian/getPolicy.js +22 -0
  191. package/dist/src/librarian/getPolicy.js.map +1 -0
  192. package/dist/src/librarian/guards.d.ts +9 -0
  193. package/dist/src/librarian/guards.d.ts.map +1 -0
  194. package/dist/src/librarian/guards.js +52 -0
  195. package/dist/src/librarian/guards.js.map +1 -0
  196. package/dist/src/librarian/index.d.ts +20 -0
  197. package/dist/src/librarian/index.d.ts.map +1 -0
  198. package/dist/src/librarian/index.js +512 -0
  199. package/dist/src/librarian/index.js.map +1 -0
  200. package/dist/src/librarian/policy.d.ts +13 -0
  201. package/dist/src/librarian/policy.d.ts.map +1 -0
  202. package/dist/src/librarian/policy.js +20 -0
  203. package/dist/src/librarian/policy.js.map +1 -0
  204. package/dist/src/librarian/scoring.d.ts +8 -0
  205. package/dist/src/librarian/scoring.d.ts.map +1 -0
  206. package/dist/src/librarian/scoring.js +10 -0
  207. package/dist/src/librarian/scoring.js.map +1 -0
  208. package/dist/src/librarian/source-reliability.d.ts +8 -0
  209. package/dist/src/librarian/source-reliability.d.ts.map +1 -0
  210. package/dist/src/librarian/source-reliability.js +105 -0
  211. package/dist/src/librarian/source-reliability.js.map +1 -0
  212. package/dist/src/library/agent-registry.d.ts +31 -0
  213. package/dist/src/library/agent-registry.d.ts.map +1 -0
  214. package/dist/src/library/agent-registry.js +197 -0
  215. package/dist/src/library/agent-registry.js.map +1 -0
  216. package/dist/src/library/client.d.ts +5 -0
  217. package/dist/src/library/client.d.ts.map +1 -0
  218. package/dist/src/library/client.js +39 -0
  219. package/dist/src/library/client.js.map +1 -0
  220. package/dist/src/library/entity-resolution.d.ts +47 -0
  221. package/dist/src/library/entity-resolution.d.ts.map +1 -0
  222. package/dist/src/library/entity-resolution.js +344 -0
  223. package/dist/src/library/entity-resolution.js.map +1 -0
  224. package/dist/src/library/locks.d.ts +9 -0
  225. package/dist/src/library/locks.d.ts.map +1 -0
  226. package/dist/src/library/locks.js +38 -0
  227. package/dist/src/library/locks.js.map +1 -0
  228. package/dist/src/library/queries.d.ts +66 -0
  229. package/dist/src/library/queries.d.ts.map +1 -0
  230. package/dist/src/library/queries.js +169 -0
  231. package/dist/src/library/queries.js.map +1 -0
  232. package/dist/src/library/relationships.d.ts +30 -0
  233. package/dist/src/library/relationships.d.ts.map +1 -0
  234. package/dist/src/library/relationships.js +97 -0
  235. package/dist/src/library/relationships.js.map +1 -0
  236. package/dist/src/sdk/index.d.ts +108 -0
  237. package/dist/src/sdk/index.d.ts.map +1 -0
  238. package/dist/src/sdk/index.js +323 -0
  239. package/dist/src/sdk/index.js.map +1 -0
  240. package/dist/src/security/apiKeys.d.ts +48 -0
  241. package/dist/src/security/apiKeys.d.ts.map +1 -0
  242. package/dist/src/security/apiKeys.js +279 -0
  243. package/dist/src/security/apiKeys.js.map +1 -0
  244. package/dist/src/types.d.ts +54 -0
  245. package/dist/src/types.d.ts.map +1 -0
  246. package/dist/src/types.js +4 -0
  247. package/dist/src/types.js.map +1 -0
  248. package/package.json +86 -0
  249. package/prisma/migrations/20260228090200_init/migration.sql +49 -0
  250. package/prisma/migrations/20260228121746_add_properties_and_relationships/migration.sql +29 -0
  251. package/prisma/migrations/20260301223834_add_superseded_by_pointer/migration.sql +4 -0
  252. package/prisma/migrations/20260301225152_add_write_receipts/migration.sql +20 -0
  253. package/prisma/migrations/20260302135650_entity_resolution/migration.sql +33 -0
  254. package/prisma/migrations/migration_lock.toml +3 -0
  255. package/prisma/schema.prisma +118 -0
@@ -0,0 +1,808 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AttendantInstance = void 0;
4
+ const router_1 = require("../lib/router");
5
+ const queries_1 = require("../library/queries");
6
+ const relationships_1 = require("../library/relationships");
7
+ const entity_resolution_1 = require("../library/entity-resolution");
8
+ const client_1 = require("../library/client");
9
+ const metrics_1 = require("../lib/metrics");
10
+ const getPolicy_1 = require("../librarian/getPolicy");
11
+ // ─── Constants ───────────────────────────────────────────────────────────────
12
+ const ATTENDANT_RULES_QUERY = {
13
+ entityType: 'system',
14
+ entityId: 'attendant',
15
+ key: 'operating_rules',
16
+ };
17
+ const CONTEXT_RECOVERY_THRESHOLD = 20; // LLM calls before context recovery
18
+ const ENTITY_DETECTION_WINDOW_CHARS = 1500;
19
+ const MIN_ENTITY_CONFIDENCE = 0.75;
20
+ const MEMORY_DECISION_CONTEXT_WINDOW_CHARS = 2000;
21
+ const MEMORY_NEED_POSITIVE_PATTERNS = [
22
+ /\bwhat(?:'s| is| was)?\s+my\b/i,
23
+ /\bdo you remember\b/i,
24
+ /\bremind me\b/i,
25
+ /\bmy\s+(?:favorite|favourite|name|email|phone|address|city|country|movie|snack|color|colour)\b/i,
26
+ /\bwe decided\b/i,
27
+ /\bearlier\b/i,
28
+ /\bprevious(?:ly)?\b/i,
29
+ /\bagain\b/i,
30
+ ];
31
+ const MEMORY_NEED_NEGATIVE_PATTERNS = [
32
+ /^\s*(hi|hello|hey|yo|sup|good (?:morning|afternoon|evening))\b[!.?\s]*$/i,
33
+ /^\s*(thanks|thank you|cool|great|nice)\b[!.?\s]*$/i,
34
+ ];
35
+ function heuristicEntityId(name) {
36
+ return name
37
+ .toLowerCase()
38
+ .replace(/[^a-z0-9]+/g, '_')
39
+ .replace(/^_+|_+$/g, '');
40
+ }
41
+ function extractFallbackCandidates(text) {
42
+ const candidates = [];
43
+ const seen = new Set();
44
+ // Explicit typed entities, e.g. project/atlas_2026
45
+ const typedRegex = /\b([a-z][a-z0-9_]*)\/([A-Za-z0-9][A-Za-z0-9_\-]{1,80})\b/g;
46
+ for (const match of text.matchAll(typedRegex)) {
47
+ const type = match[1];
48
+ const idGuess = heuristicEntityId(match[2]);
49
+ if (!idGuess)
50
+ continue;
51
+ const evidence = match[0];
52
+ const key = `${type}/${idGuess}`;
53
+ if (seen.has(key))
54
+ continue;
55
+ seen.add(key);
56
+ candidates.push({
57
+ type,
58
+ name: idGuess.replace(/_/g, ' '),
59
+ id_guess: idGuess,
60
+ confidence: 0.8,
61
+ evidence,
62
+ start: typeof match.index === 'number' ? match.index : undefined,
63
+ end: typeof match.index === 'number' ? match.index + evidence.length : undefined,
64
+ });
65
+ }
66
+ // Named project mentions, e.g. "Project Atlas 2026"
67
+ const projectRegex = /\bProject\s+([A-Z0-9][A-Za-z0-9_\-]*(?:\s+[A-Z0-9][A-Za-z0-9_\-]*){0,4})\b/g;
68
+ for (const match of text.matchAll(projectRegex)) {
69
+ const name = `Project ${match[1]}`.trim();
70
+ const normalized = heuristicEntityId(match[1]);
71
+ const idGuess = normalized ? `project_${normalized}` : '';
72
+ if (!idGuess)
73
+ continue;
74
+ const key = `project/${idGuess}`;
75
+ if (seen.has(key))
76
+ continue;
77
+ seen.add(key);
78
+ candidates.push({
79
+ type: 'project',
80
+ name,
81
+ id_guess: idGuess,
82
+ confidence: 0.78,
83
+ evidence: name,
84
+ start: typeof match.index === 'number' ? match.index : undefined,
85
+ end: typeof match.index === 'number' ? match.index + name.length : undefined,
86
+ });
87
+ }
88
+ // Capitalized multi-word names fallback, e.g. "Atlas Initiative"
89
+ const titleCaseRegex = /\b([A-Z][A-Za-z0-9]*(?:\s+[A-Z][A-Za-z0-9]*){1,3})\b/g;
90
+ for (const match of text.matchAll(titleCaseRegex)) {
91
+ const name = match[1].trim();
92
+ const normalized = heuristicEntityId(name);
93
+ if (!normalized)
94
+ continue;
95
+ const idGuess = `project_${normalized}`;
96
+ const key = `project/${idGuess}`;
97
+ if (seen.has(key))
98
+ continue;
99
+ seen.add(key);
100
+ candidates.push({
101
+ type: 'project',
102
+ name,
103
+ id_guess: idGuess,
104
+ confidence: 0.75,
105
+ evidence: name,
106
+ start: typeof match.index === 'number' ? match.index : undefined,
107
+ end: typeof match.index === 'number' ? match.index + name.length : undefined,
108
+ });
109
+ }
110
+ return candidates;
111
+ }
112
+ function normalizeMessage(message) {
113
+ return (message ?? '').trim();
114
+ }
115
+ function heuristicMemoryNeed(message) {
116
+ const normalized = normalizeMessage(message);
117
+ if (!normalized) {
118
+ return {
119
+ needed: null,
120
+ confidence: 0.5,
121
+ explanation: 'no_latest_message',
122
+ };
123
+ }
124
+ if (MEMORY_NEED_NEGATIVE_PATTERNS.some((pattern) => pattern.test(normalized))) {
125
+ return {
126
+ needed: false,
127
+ confidence: 0.95,
128
+ explanation: 'simple_greeting_or_ack',
129
+ };
130
+ }
131
+ if (MEMORY_NEED_POSITIVE_PATTERNS.some((pattern) => pattern.test(normalized))) {
132
+ return {
133
+ needed: true,
134
+ confidence: 0.92,
135
+ explanation: 'memory_reference_detected',
136
+ };
137
+ }
138
+ const hasQuestion = normalized.includes('?');
139
+ const hasPersonalReference = /\b(my|our|we)\b/i.test(normalized);
140
+ if (!hasQuestion && !hasPersonalReference) {
141
+ return {
142
+ needed: false,
143
+ confidence: 0.8,
144
+ explanation: 'general_statement_without_memory_signal',
145
+ };
146
+ }
147
+ return {
148
+ needed: null,
149
+ confidence: 0.55,
150
+ explanation: 'ambiguous',
151
+ };
152
+ }
153
+ // ─── AttendantInstance ───────────────────────────────────────────────────────
154
+ class AttendantInstance {
155
+ constructor(agentId) {
156
+ this.brief = null;
157
+ this.contextCallCount = 0;
158
+ this.sessionStarted = new Date().toISOString();
159
+ this.agentId = agentId;
160
+ }
161
+ // ── Handshake ────────────────────────────────────────────────────────────
162
+ async handshake(context) {
163
+ const t0 = (0, metrics_1.timeStart)();
164
+ // Try to resume from persisted state first
165
+ const persisted = await this.loadPersistedState();
166
+ // Load operating rules from Staff Namespace
167
+ const operatingRules = await this.loadOperatingRules();
168
+ // Infer task type
169
+ const inferredTaskType = await this.inferTask(context);
170
+ // Load knowledge — agent entries + related entities
171
+ const workingMemory = await this.buildWorkingMemory(inferredTaskType);
172
+ this.brief = {
173
+ agentId: this.agentId,
174
+ operatingRules,
175
+ inferredTaskType,
176
+ workingMemory,
177
+ sessionStarted: persisted?.sessionStarted ?? this.sessionStarted,
178
+ briefGeneratedAt: new Date().toISOString(),
179
+ contextCallCount: this.contextCallCount,
180
+ };
181
+ await this.persistState();
182
+ (0, metrics_1.timeEnd)('attendant.handshake_ms', t0);
183
+ return this.brief;
184
+ }
185
+ // ── Reconvene ────────────────────────────────────────────────────────────
186
+ async reconvene(context) {
187
+ const t0 = (0, metrics_1.timeStart)();
188
+ if (!this.brief) {
189
+ const result = await this.handshake(context);
190
+ (0, metrics_1.timeEnd)('attendant.reconvene_ms', t0);
191
+ return result;
192
+ }
193
+ const newTaskType = await this.inferTask(context);
194
+ // Task hasn't shifted — update timestamp only
195
+ if (newTaskType.toLowerCase() === this.brief.inferredTaskType.toLowerCase()) {
196
+ this.brief = {
197
+ ...this.brief,
198
+ briefGeneratedAt: new Date().toISOString(),
199
+ contextCallCount: this.contextCallCount,
200
+ };
201
+ await this.persistState();
202
+ (0, metrics_1.timeEnd)('attendant.reconvene_ms', t0);
203
+ return this.brief;
204
+ }
205
+ // Task has shifted — rebuild working memory
206
+ const workingMemory = await this.buildWorkingMemory(newTaskType);
207
+ this.brief = {
208
+ ...this.brief,
209
+ inferredTaskType: newTaskType,
210
+ workingMemory,
211
+ briefGeneratedAt: new Date().toISOString(),
212
+ contextCallCount: this.contextCallCount,
213
+ };
214
+ await this.persistState();
215
+ (0, metrics_1.timeEnd)('attendant.reconvene_ms', t0);
216
+ return this.brief;
217
+ }
218
+ // ── Context Update (fast, in-memory) ─────────────────────────────────────
219
+ updateWorkingMemory(entry) {
220
+ if (!this.brief)
221
+ return;
222
+ const existing = this.brief.workingMemory.findIndex((e) => e.entityKey === entry.entityKey);
223
+ if (existing >= 0) {
224
+ // Keep higher confidence entry
225
+ if (entry.confidence >= this.brief.workingMemory[existing].confidence) {
226
+ this.brief.workingMemory[existing] = entry;
227
+ }
228
+ }
229
+ else {
230
+ this.brief.workingMemory.push(entry);
231
+ }
232
+ }
233
+ // ── Context Recovery ─────────────────────────────────────────────────────
234
+ async onContextLow() {
235
+ const rulesResult = await (0, queries_1.queryEntry)(ATTENDANT_RULES_QUERY);
236
+ const operatingRules = rulesResult.found && rulesResult.entry
237
+ ? rulesResult.entry.valueSummary
238
+ : 'No operating rules found.';
239
+ if (this.brief) {
240
+ this.brief.operatingRules = operatingRules;
241
+ this.brief.contextCallCount = 0;
242
+ }
243
+ this.contextCallCount = 0;
244
+ await this.persistState();
245
+ }
246
+ // ── Getters ──────────────────────────────────────────────────────────────
247
+ getBrief() {
248
+ return this.brief;
249
+ }
250
+ getAgentId() {
251
+ return this.agentId;
252
+ }
253
+ async attend(input) {
254
+ const t0 = (0, metrics_1.timeStart)();
255
+ const currentContext = input.currentContext ?? '';
256
+ const latestMessage = normalizeMessage(input.latestMessage);
257
+ const forceInject = input.forceInject === true;
258
+ const decision = await this.decideMemoryNeed({
259
+ currentContext,
260
+ latestMessage,
261
+ forceInject,
262
+ });
263
+ if (!decision.needed) {
264
+ (0, metrics_1.timeEnd)('attendant.attend_ms', t0);
265
+ return {
266
+ shouldInject: false,
267
+ reason: 'memory_not_needed',
268
+ decision,
269
+ facts: [],
270
+ entitiesDetected: [],
271
+ alreadyPresent: 0,
272
+ totalFound: 0,
273
+ entitiesResolved: [],
274
+ debug: {
275
+ skipped: 'empty_context',
276
+ contextLength: currentContext.length,
277
+ detectionWindowChars: Math.min(currentContext.length, ENTITY_DETECTION_WINDOW_CHARS),
278
+ detectedCandidates: 0,
279
+ keptCandidates: 0,
280
+ hintsProvided: input.entityHints?.length ?? 0,
281
+ hintsResolved: 0,
282
+ dropped: [{ name: latestMessage || '(none)', reason: 'memory_not_needed' }],
283
+ },
284
+ };
285
+ }
286
+ const observed = await this.observe({
287
+ currentContext,
288
+ maxFacts: input.maxFacts,
289
+ entityHints: input.entityHints,
290
+ });
291
+ let reason = 'memory_needed_injected';
292
+ const shouldInject = observed.facts.length > 0;
293
+ if (!shouldInject) {
294
+ const allAlreadyInContext = observed.totalFound > 0 && observed.alreadyPresent >= observed.totalFound;
295
+ reason = allAlreadyInContext ? 'memory_needed_but_in_context' : 'memory_needed_no_facts';
296
+ }
297
+ else if (forceInject) {
298
+ reason = 'forced';
299
+ }
300
+ (0, metrics_1.timeEnd)('attendant.attend_ms', t0);
301
+ return {
302
+ ...observed,
303
+ shouldInject,
304
+ reason,
305
+ decision,
306
+ };
307
+ }
308
+ // Context Window Observation
309
+ async observe(input) {
310
+ const t0 = (0, metrics_1.timeStart)();
311
+ const maxFacts = input.maxFacts ?? 5;
312
+ const currentContext = input.currentContext ?? '';
313
+ const entityHints = Array.isArray(input.entityHints)
314
+ ? input.entityHints.filter((hint) => typeof hint === 'string' && hint.trim().length > 0)
315
+ : [];
316
+ if (currentContext.trim().length === 0 && entityHints.length === 0) {
317
+ (0, metrics_1.timeEnd)('attendant.observe_ms', t0);
318
+ return {
319
+ facts: [],
320
+ entitiesDetected: [],
321
+ alreadyPresent: 0,
322
+ totalFound: 0,
323
+ entitiesResolved: [],
324
+ debug: {
325
+ skipped: 'empty_context',
326
+ contextLength: 0,
327
+ detectionWindowChars: 0,
328
+ detectedCandidates: 0,
329
+ keptCandidates: 0,
330
+ hintsProvided: 0,
331
+ hintsResolved: 0,
332
+ dropped: [],
333
+ },
334
+ };
335
+ }
336
+ const detectionWindow = currentContext.length <= ENTITY_DETECTION_WINDOW_CHARS
337
+ ? currentContext
338
+ : currentContext.slice(-ENTITY_DETECTION_WINDOW_CHARS);
339
+ const droppedCandidates = [];
340
+ // Step 1 — extract entity mentions from context (if any text is available)
341
+ let parsedCandidates = [];
342
+ if (detectionWindow.trim().length > 0) {
343
+ const entityResponse = await (0, router_1.route)('extraction', [
344
+ {
345
+ role: 'user',
346
+ content: `Extract explicitly named entities from the text.
347
+ An entity can be a person, organization, project, technology, or named concept.
348
+
349
+ Return ONLY valid JSON as an array of objects in this exact shape:
350
+ [
351
+ {
352
+ "type": "project",
353
+ "name": "Project Atlas",
354
+ "id_guess": "project_atlas",
355
+ "confidence": 0.92,
356
+ "evidence": "Project Atlas",
357
+ "start": 123,
358
+ "end": 136
359
+ }
360
+ ]
361
+
362
+ Rules:
363
+ - Only include entities explicitly named in the provided text.
364
+ - Do not infer or carry over entities not present in the text.
365
+ - If uncertain, omit.
366
+ - If none are present, return [].
367
+
368
+ Text:
369
+ ${detectionWindow}`,
370
+ },
371
+ ], 512);
372
+ try {
373
+ const clean = entityResponse.text.replace(/```json|```/g, '').trim();
374
+ const parsed = JSON.parse(clean);
375
+ if (Array.isArray(parsed)) {
376
+ for (const item of parsed) {
377
+ if (typeof item === 'string') {
378
+ const raw = item.trim();
379
+ if (!raw)
380
+ continue;
381
+ if (raw.includes('/')) {
382
+ const [type, ...rest] = raw.split('/');
383
+ const idGuess = heuristicEntityId(rest.join('/'));
384
+ if (!type || !idGuess)
385
+ continue;
386
+ parsedCandidates.push({
387
+ type,
388
+ name: idGuess.replace(/_/g, ' '),
389
+ id_guess: idGuess,
390
+ confidence: 0.9,
391
+ evidence: raw,
392
+ });
393
+ }
394
+ else {
395
+ const idGuess = heuristicEntityId(raw);
396
+ if (!idGuess)
397
+ continue;
398
+ parsedCandidates.push({
399
+ type: 'project',
400
+ name: raw,
401
+ id_guess: `project_${idGuess}`,
402
+ confidence: 0.76,
403
+ evidence: raw,
404
+ });
405
+ }
406
+ continue;
407
+ }
408
+ if (!item || typeof item !== 'object')
409
+ continue;
410
+ const candidate = item;
411
+ if (typeof candidate.type === 'string' &&
412
+ typeof candidate.name === 'string' &&
413
+ typeof candidate.id_guess === 'string' &&
414
+ typeof candidate.confidence === 'number' &&
415
+ typeof candidate.evidence === 'string') {
416
+ parsedCandidates.push({
417
+ type: candidate.type,
418
+ name: candidate.name,
419
+ id_guess: candidate.id_guess,
420
+ confidence: candidate.confidence,
421
+ evidence: candidate.evidence,
422
+ start: candidate.start,
423
+ end: candidate.end,
424
+ });
425
+ }
426
+ }
427
+ }
428
+ }
429
+ catch {
430
+ droppedCandidates.push({ name: 'parse_error', reason: 'invalid_json' });
431
+ }
432
+ if (parsedCandidates.length === 0) {
433
+ parsedCandidates = extractFallbackCandidates(detectionWindow);
434
+ if (parsedCandidates.length > 0) {
435
+ droppedCandidates.push({ name: 'fallback_extraction', reason: 'heuristic_used' });
436
+ }
437
+ }
438
+ }
439
+ const gatedCandidates = [];
440
+ for (const candidate of parsedCandidates) {
441
+ if (candidate.confidence < MIN_ENTITY_CONFIDENCE) {
442
+ droppedCandidates.push({ name: candidate.name, reason: 'low_confidence' });
443
+ continue;
444
+ }
445
+ const evidenceLower = candidate.evidence.toLowerCase().trim();
446
+ if (!evidenceLower || !detectionWindow.toLowerCase().includes(evidenceLower)) {
447
+ droppedCandidates.push({ name: candidate.name, reason: 'missing_evidence' });
448
+ continue;
449
+ }
450
+ gatedCandidates.push(candidate);
451
+ }
452
+ if (gatedCandidates.length === 0 && entityHints.length === 0) {
453
+ (0, metrics_1.timeEnd)('attendant.observe_ms', t0);
454
+ return {
455
+ facts: [],
456
+ entitiesDetected: [],
457
+ alreadyPresent: 0,
458
+ totalFound: 0,
459
+ entitiesResolved: [],
460
+ debug: {
461
+ contextLength: currentContext.length,
462
+ detectionWindowChars: detectionWindow.length,
463
+ detectedCandidates: parsedCandidates.length,
464
+ keptCandidates: 0,
465
+ hintsProvided: entityHints.length,
466
+ hintsResolved: 0,
467
+ dropped: droppedCandidates,
468
+ },
469
+ };
470
+ }
471
+ // Step 2 — resolve hints and candidates to canonical entities, then query Library
472
+ const policy = await (0, getPolicy_1.getConflictPolicy)();
473
+ const maxEntities = policy.maxEntitiesPerObserve ?? 5;
474
+ const maxKeysPerEntity = policy.maxKeysPerEntity ?? 5;
475
+ const allFacts = [];
476
+ const entitiesResolved = [];
477
+ const entitiesDetected = new Set();
478
+ const resolvedEntities = new Map();
479
+ for (const hint of entityHints) {
480
+ try {
481
+ const parsedHint = (0, entity_resolution_1.parseEntityString)(hint);
482
+ const resolved = await (0, entity_resolution_1.resolveEntity)({
483
+ entityType: parsedHint.entityType,
484
+ entityId: parsedHint.entityId,
485
+ rawName: hint,
486
+ aliases: [hint, parsedHint.entityId],
487
+ source: 'observe_hint',
488
+ confidence: 100,
489
+ createIfMissing: false,
490
+ });
491
+ if (!resolvedEntities.has(resolved.canonicalEntity)) {
492
+ resolvedEntities.set(resolved.canonicalEntity, {
493
+ entityType: resolved.entityType,
494
+ entityId: resolved.entityId,
495
+ canonicalEntity: resolved.canonicalEntity,
496
+ name: parsedHint.entityId.replace(/_/g, ' '),
497
+ input: hint,
498
+ confidence: 1,
499
+ matchedBy: 'hint',
500
+ });
501
+ }
502
+ }
503
+ catch {
504
+ droppedCandidates.push({ name: hint, reason: 'invalid_or_unresolved_hint' });
505
+ continue;
506
+ }
507
+ }
508
+ for (const candidate of gatedCandidates.slice(0, maxEntities)) {
509
+ const fallbackEntity = `${candidate.type}/${candidate.id_guess}`;
510
+ try {
511
+ const resolved = await (0, entity_resolution_1.resolveEntity)({
512
+ entityType: candidate.type,
513
+ entityId: candidate.id_guess,
514
+ rawName: candidate.name,
515
+ aliases: [
516
+ candidate.name,
517
+ candidate.evidence,
518
+ fallbackEntity,
519
+ ],
520
+ source: 'observe',
521
+ confidence: Math.round(candidate.confidence * 100),
522
+ createIfMissing: false,
523
+ });
524
+ if (!resolvedEntities.has(resolved.canonicalEntity)) {
525
+ resolvedEntities.set(resolved.canonicalEntity, {
526
+ entityType: resolved.entityType,
527
+ entityId: resolved.entityId,
528
+ canonicalEntity: resolved.canonicalEntity,
529
+ name: candidate.name,
530
+ input: fallbackEntity,
531
+ confidence: candidate.confidence,
532
+ matchedBy: resolved.matchedBy,
533
+ });
534
+ }
535
+ }
536
+ catch {
537
+ droppedCandidates.push({ name: candidate.name, reason: 'unresolved' });
538
+ continue;
539
+ }
540
+ }
541
+ for (const resolvedInfo of Array.from(resolvedEntities.values()).slice(0, maxEntities)) {
542
+ entitiesDetected.add(resolvedInfo.canonicalEntity);
543
+ entitiesResolved?.push({
544
+ name: resolvedInfo.name,
545
+ input: resolvedInfo.input,
546
+ canonicalEntity: resolvedInfo.canonicalEntity,
547
+ confidence: resolvedInfo.confidence,
548
+ matchedBy: resolvedInfo.matchedBy,
549
+ });
550
+ const allEntries = await (0, queries_1.findEntriesByEntity)(resolvedInfo.entityType, resolvedInfo.entityId);
551
+ // Priority keys first
552
+ const priorityKeys = policy.observeKeyPriority?.[resolvedInfo.entityType] ?? [];
553
+ const priorityEntries = allEntries.filter((e) => priorityKeys.includes(e.key));
554
+ const remainingEntries = allEntries
555
+ .filter((e) => !priorityKeys.includes(e.key))
556
+ .sort((a, b) => b.confidence - a.confidence);
557
+ const selectedEntries = [...priorityEntries, ...remainingEntries].slice(0, maxKeysPerEntity);
558
+ for (const entry of selectedEntries) {
559
+ allFacts.push({
560
+ entityKey: `${resolvedInfo.entityType}/${resolvedInfo.entityId}/${entry.key}`,
561
+ summary: entry.valueSummary,
562
+ value: entry.valueRaw,
563
+ confidence: entry.confidence,
564
+ source: entry.source,
565
+ });
566
+ }
567
+ }
568
+ // Step 3 — filter out facts already present in context
569
+ const contextLower = currentContext.toLowerCase();
570
+ let alreadyPresent = 0;
571
+ const newFacts = [];
572
+ for (const fact of allFacts) {
573
+ // Check if summary key words appear in context
574
+ const summaryWords = fact.summary.toLowerCase().split(' ').filter((w) => w.length > 4);
575
+ const alreadyInContext = summaryWords.length > 0 &&
576
+ summaryWords.filter((w) => contextLower.includes(w)).length >= Math.ceil(summaryWords.length * 0.6);
577
+ if (alreadyInContext) {
578
+ alreadyPresent++;
579
+ }
580
+ else {
581
+ newFacts.push(fact);
582
+ }
583
+ }
584
+ // Step 4 — return top facts by confidence
585
+ const topFacts = newFacts
586
+ .sort((a, b) => b.confidence - a.confidence)
587
+ .slice(0, maxFacts);
588
+ (0, metrics_1.timeEnd)('attendant.observe_ms', t0);
589
+ return {
590
+ facts: topFacts,
591
+ entitiesDetected: Array.from(entitiesDetected),
592
+ alreadyPresent,
593
+ totalFound: allFacts.length,
594
+ entitiesResolved,
595
+ debug: {
596
+ contextLength: currentContext.length,
597
+ detectionWindowChars: detectionWindow.length,
598
+ detectedCandidates: parsedCandidates.length,
599
+ keptCandidates: gatedCandidates.length,
600
+ hintsProvided: entityHints.length,
601
+ hintsResolved: entitiesResolved?.filter((e) => e.matchedBy === 'hint').length ?? 0,
602
+ dropped: droppedCandidates,
603
+ },
604
+ };
605
+ }
606
+ // ── Private ──────────────────────────────────────────────────────────────
607
+ async decideMemoryNeed(input) {
608
+ if (input.forceInject) {
609
+ return {
610
+ needed: true,
611
+ confidence: 1,
612
+ method: 'forced',
613
+ explanation: 'force_inject',
614
+ };
615
+ }
616
+ const heuristic = heuristicMemoryNeed(input.latestMessage);
617
+ if (heuristic.needed !== null) {
618
+ return {
619
+ needed: heuristic.needed,
620
+ confidence: heuristic.confidence,
621
+ method: 'heuristic',
622
+ explanation: heuristic.explanation,
623
+ };
624
+ }
625
+ const contextWindow = input.currentContext.length <= MEMORY_DECISION_CONTEXT_WINDOW_CHARS
626
+ ? input.currentContext
627
+ : input.currentContext.slice(-MEMORY_DECISION_CONTEXT_WINDOW_CHARS);
628
+ const response = await (0, router_1.route)('classification', [
629
+ {
630
+ role: 'user',
631
+ content: `Decide whether this assistant should fetch persistent memory before replying.
632
+
633
+ Latest user message:
634
+ ${input.latestMessage || '(none)'}
635
+
636
+ Recent context excerpt:
637
+ ${contextWindow || '(empty)'}
638
+
639
+ Return ONLY valid JSON with this exact shape:
640
+ {"needsMemory":true,"confidence":0.81,"reason":"short_reason"}
641
+
642
+ Rules:
643
+ - needsMemory=true when the answer likely depends on user-specific or session-specific facts.
644
+ - needsMemory=false for generic chit-chat, open-domain facts, or when no memory lookup is needed.
645
+ - confidence is a float from 0 to 1.`,
646
+ },
647
+ ], 128);
648
+ const parsed = this.parseMemoryDecision(response.text);
649
+ if (parsed) {
650
+ return {
651
+ needed: parsed.needsMemory,
652
+ confidence: parsed.confidence,
653
+ method: 'llm',
654
+ explanation: parsed.reason,
655
+ };
656
+ }
657
+ return {
658
+ needed: false,
659
+ confidence: 0.5,
660
+ method: 'heuristic',
661
+ explanation: 'classification_parse_failed_default_false',
662
+ };
663
+ }
664
+ parseMemoryDecision(raw) {
665
+ try {
666
+ const cleaned = raw.replace(/```json|```/g, '').trim();
667
+ const parsed = JSON.parse(cleaned);
668
+ if (typeof parsed.needsMemory !== 'boolean')
669
+ return null;
670
+ const confidence = typeof parsed.confidence === 'number'
671
+ ? Math.max(0, Math.min(1, parsed.confidence))
672
+ : 0.6;
673
+ const reason = typeof parsed.reason === 'string' && parsed.reason.trim().length > 0
674
+ ? parsed.reason.trim()
675
+ : 'llm_classification';
676
+ return {
677
+ needsMemory: parsed.needsMemory,
678
+ confidence,
679
+ reason,
680
+ };
681
+ }
682
+ catch {
683
+ return null;
684
+ }
685
+ }
686
+ async inferTask(context) {
687
+ this.contextCallCount++;
688
+ if (this.contextCallCount >= CONTEXT_RECOVERY_THRESHOLD) {
689
+ await this.onContextLow();
690
+ }
691
+ const response = await (0, router_1.route)('task_inference', [
692
+ {
693
+ role: 'user',
694
+ content: `You are analyzing what an AI agent is currently working on.
695
+
696
+ Agent ID: ${this.agentId}
697
+ Task description: ${context.task}
698
+ Recent messages:
699
+ ${context.recentMessages.map((m, i) => `${i + 1}. ${m}`).join('\n')}
700
+
701
+ In one short sentence, describe the specific type of task this agent is currently performing.
702
+ Be specific and concrete.`,
703
+ },
704
+ ], 256);
705
+ return response.text;
706
+ }
707
+ async loadOperatingRules() {
708
+ const rulesResult = await (0, queries_1.queryEntry)(ATTENDANT_RULES_QUERY);
709
+ return rulesResult.found && rulesResult.entry
710
+ ? rulesResult.entry.valueSummary
711
+ : 'No operating rules found.';
712
+ }
713
+ async buildWorkingMemory(taskType) {
714
+ this.contextCallCount++;
715
+ // Fetch agent entries + related entity entries
716
+ const agentEntries = await (0, queries_1.findEntriesByEntity)('agent', this.agentId);
717
+ const relatedEntities = await (0, relationships_1.getRelatedDeep)('agent', this.agentId, 2);
718
+ const relatedEntries = await Promise.all(relatedEntities.map((r) => (0, queries_1.findEntriesByEntity)(r.entityType, r.entityId)));
719
+ const allEntries = [...agentEntries, ...relatedEntries.flat()];
720
+ if (allEntries.length === 0)
721
+ return [];
722
+ const entryInputs = allEntries.map((e) => ({
723
+ key: `${e.entityType}/${e.entityId}/${e.key}`,
724
+ valueSummary: e.valueSummary,
725
+ confidence: e.confidence,
726
+ source: e.source,
727
+ }));
728
+ // Filter to relevant entries for current task
729
+ const response = await (0, router_1.route)('relevance_filtering', [
730
+ {
731
+ role: 'user',
732
+ content: `You are deciding what knowledge an AI agent needs for its current task.
733
+
734
+ Agent task: ${taskType}
735
+
736
+ Available knowledge entries:
737
+ ${entryInputs.map((e, i) => `${i + 1}. [${e.key}] ${e.valueSummary} (confidence: ${e.confidence})`).join('\n')}
738
+
739
+ Return only the numbers of entries that are directly relevant to the current task.
740
+ Format: comma-separated numbers only. Example: 1,3,5
741
+ If nothing is relevant, return: none`,
742
+ },
743
+ ], 128);
744
+ if (response.text.trim() === 'none')
745
+ return [];
746
+ const indices = response.text
747
+ .split(',')
748
+ .map((s) => parseInt(s.trim()) - 1)
749
+ .filter((i) => i >= 0 && i < entryInputs.length);
750
+ return indices.map((i) => ({
751
+ entityKey: entryInputs[i].key,
752
+ summary: entryInputs[i].valueSummary,
753
+ confidence: entryInputs[i].confidence,
754
+ source: entryInputs[i].source,
755
+ lastUpdated: new Date().toISOString(),
756
+ }));
757
+ }
758
+ async persistState() {
759
+ if (!this.brief)
760
+ return;
761
+ await (0, client_1.getDb)().knowledgeEntry.upsert({
762
+ where: {
763
+ entityType_entityId_key: {
764
+ entityType: 'agent',
765
+ entityId: this.agentId,
766
+ key: 'attendant_state',
767
+ },
768
+ },
769
+ update: {
770
+ valueRaw: this.brief,
771
+ valueSummary: `Attendant state for ${this.agentId}`,
772
+ updatedAt: new Date(),
773
+ },
774
+ create: {
775
+ entityType: 'agent',
776
+ entityId: this.agentId,
777
+ key: 'attendant_state',
778
+ valueRaw: this.brief,
779
+ valueSummary: `Attendant state for ${this.agentId}`,
780
+ confidence: 100,
781
+ source: 'attendant',
782
+ createdBy: 'attendant',
783
+ isProtected: false,
784
+ conflictLog: [],
785
+ },
786
+ });
787
+ }
788
+ async loadPersistedState() {
789
+ const entry = await (0, client_1.getDb)().knowledgeEntry.findUnique({
790
+ where: {
791
+ entityType_entityId_key: {
792
+ entityType: 'agent',
793
+ entityId: this.agentId,
794
+ key: 'attendant_state',
795
+ },
796
+ },
797
+ });
798
+ if (!entry)
799
+ return null;
800
+ const state = entry.valueRaw;
801
+ this.sessionStarted = state.sessionStarted;
802
+ this.contextCallCount = state.contextCallCount ?? 0;
803
+ this.brief = state;
804
+ return state;
805
+ }
806
+ }
807
+ exports.AttendantInstance = AttendantInstance;
808
+ //# sourceMappingURL=AttendantInstance.js.map