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.
- package/.env.example +63 -0
- package/LICENSE +12 -0
- package/README.md +520 -0
- package/bin/iranti.js +24 -0
- package/dist/scripts/api-key-create.js +57 -0
- package/dist/scripts/api-key-list.js +42 -0
- package/dist/scripts/api-key-revoke.js +42 -0
- package/dist/scripts/iranti-cli.js +387 -0
- package/dist/scripts/seed-codebase.js +138 -0
- package/dist/scripts/seed.js +121 -0
- package/dist/scripts/setup.js +86 -0
- package/dist/src/api/archivistScheduler.d.ts +8 -0
- package/dist/src/api/archivistScheduler.d.ts.map +1 -0
- package/dist/src/api/archivistScheduler.js +100 -0
- package/dist/src/api/archivistScheduler.js.map +1 -0
- package/dist/src/api/diag.d.ts +2 -0
- package/dist/src/api/diag.d.ts.map +1 -0
- package/dist/src/api/diag.js +54 -0
- package/dist/src/api/diag.js.map +1 -0
- package/dist/src/api/middleware/auth.d.ts +3 -0
- package/dist/src/api/middleware/auth.d.ts.map +1 -0
- package/dist/src/api/middleware/auth.js +36 -0
- package/dist/src/api/middleware/auth.js.map +1 -0
- package/dist/src/api/middleware/authorization.d.ts +4 -0
- package/dist/src/api/middleware/authorization.d.ts.map +1 -0
- package/dist/src/api/middleware/authorization.js +54 -0
- package/dist/src/api/middleware/authorization.js.map +1 -0
- package/dist/src/api/middleware/rateLimit.d.ts +23 -0
- package/dist/src/api/middleware/rateLimit.d.ts.map +1 -0
- package/dist/src/api/middleware/rateLimit.js +70 -0
- package/dist/src/api/middleware/rateLimit.js.map +1 -0
- package/dist/src/api/middleware/validation.d.ts +128 -0
- package/dist/src/api/middleware/validation.d.ts.map +1 -0
- package/dist/src/api/middleware/validation.js +137 -0
- package/dist/src/api/middleware/validation.js.map +1 -0
- package/dist/src/api/repro.d.ts +2 -0
- package/dist/src/api/repro.d.ts.map +1 -0
- package/dist/src/api/repro.js +25 -0
- package/dist/src/api/repro.js.map +1 -0
- package/dist/src/api/routes/agents.d.ts +4 -0
- package/dist/src/api/routes/agents.d.ts.map +1 -0
- package/dist/src/api/routes/agents.js +56 -0
- package/dist/src/api/routes/agents.js.map +1 -0
- package/dist/src/api/routes/batch.d.ts +2 -0
- package/dist/src/api/routes/batch.d.ts.map +1 -0
- package/dist/src/api/routes/batch.js +63 -0
- package/dist/src/api/routes/batch.js.map +1 -0
- package/dist/src/api/routes/dev.d.ts +2 -0
- package/dist/src/api/routes/dev.d.ts.map +1 -0
- package/dist/src/api/routes/dev.js +29 -0
- package/dist/src/api/routes/dev.js.map +1 -0
- package/dist/src/api/routes/knowledge.d.ts +4 -0
- package/dist/src/api/routes/knowledge.d.ts.map +1 -0
- package/dist/src/api/routes/knowledge.js +184 -0
- package/dist/src/api/routes/knowledge.js.map +1 -0
- package/dist/src/api/routes/memory.d.ts +4 -0
- package/dist/src/api/routes/memory.d.ts.map +1 -0
- package/dist/src/api/routes/memory.js +150 -0
- package/dist/src/api/routes/memory.js.map +1 -0
- package/dist/src/api/server.d.ts +2 -0
- package/dist/src/api/server.d.ts.map +1 -0
- package/dist/src/api/server.js +191 -0
- package/dist/src/api/server.js.map +1 -0
- package/dist/src/archivist/index.d.ts +10 -0
- package/dist/src/archivist/index.d.ts.map +1 -0
- package/dist/src/archivist/index.js +232 -0
- package/dist/src/archivist/index.js.map +1 -0
- package/dist/src/attendant/AttendantInstance.d.ts +96 -0
- package/dist/src/attendant/AttendantInstance.d.ts.map +1 -0
- package/dist/src/attendant/AttendantInstance.js +808 -0
- package/dist/src/attendant/AttendantInstance.js.map +1 -0
- package/dist/src/attendant/index.d.ts +12 -0
- package/dist/src/attendant/index.d.ts.map +1 -0
- package/dist/src/attendant/index.js +39 -0
- package/dist/src/attendant/index.js.map +1 -0
- package/dist/src/attendant/registry.d.ts +6 -0
- package/dist/src/attendant/registry.d.ts.map +1 -0
- package/dist/src/attendant/registry.js +27 -0
- package/dist/src/attendant/registry.js.map +1 -0
- package/dist/src/generated/prisma/browser.d.ts +35 -0
- package/dist/src/generated/prisma/browser.d.ts.map +1 -0
- package/dist/src/generated/prisma/browser.js +57 -0
- package/dist/src/generated/prisma/browser.js.map +1 -0
- package/dist/src/generated/prisma/client.d.ts +54 -0
- package/dist/src/generated/prisma/client.d.ts.map +1 -0
- package/dist/src/generated/prisma/client.js +71 -0
- package/dist/src/generated/prisma/client.js.map +1 -0
- package/dist/src/generated/prisma/commonInputTypes.d.ts +415 -0
- package/dist/src/generated/prisma/commonInputTypes.d.ts.map +1 -0
- package/dist/src/generated/prisma/commonInputTypes.js +12 -0
- package/dist/src/generated/prisma/commonInputTypes.js.map +1 -0
- package/dist/src/generated/prisma/enums.d.ts +2 -0
- package/dist/src/generated/prisma/enums.d.ts.map +1 -0
- package/dist/src/generated/prisma/enums.js +12 -0
- package/dist/src/generated/prisma/enums.js.map +1 -0
- package/dist/src/generated/prisma/internal/class.d.ts +186 -0
- package/dist/src/generated/prisma/internal/class.d.ts.map +1 -0
- package/dist/src/generated/prisma/internal/class.js +86 -0
- package/dist/src/generated/prisma/internal/class.js.map +1 -0
- package/dist/src/generated/prisma/internal/prismaNamespace.d.ts +1015 -0
- package/dist/src/generated/prisma/internal/prismaNamespace.d.ts.map +1 -0
- package/dist/src/generated/prisma/internal/prismaNamespace.js +220 -0
- package/dist/src/generated/prisma/internal/prismaNamespace.js.map +1 -0
- package/dist/src/generated/prisma/internal/prismaNamespaceBrowser.d.ts +152 -0
- package/dist/src/generated/prisma/internal/prismaNamespaceBrowser.d.ts.map +1 -0
- package/dist/src/generated/prisma/internal/prismaNamespaceBrowser.js +191 -0
- package/dist/src/generated/prisma/internal/prismaNamespaceBrowser.js.map +1 -0
- package/dist/src/generated/prisma/models/Archive.d.ts +1425 -0
- package/dist/src/generated/prisma/models/Archive.d.ts.map +1 -0
- package/dist/src/generated/prisma/models/Archive.js +3 -0
- package/dist/src/generated/prisma/models/Archive.js.map +1 -0
- package/dist/src/generated/prisma/models/Entity.d.ts +1129 -0
- package/dist/src/generated/prisma/models/Entity.d.ts.map +1 -0
- package/dist/src/generated/prisma/models/Entity.js +3 -0
- package/dist/src/generated/prisma/models/Entity.js.map +1 -0
- package/dist/src/generated/prisma/models/EntityAlias.d.ts +1347 -0
- package/dist/src/generated/prisma/models/EntityAlias.d.ts.map +1 -0
- package/dist/src/generated/prisma/models/EntityAlias.js +3 -0
- package/dist/src/generated/prisma/models/EntityAlias.js.map +1 -0
- package/dist/src/generated/prisma/models/EntityRelationship.d.ts +1143 -0
- package/dist/src/generated/prisma/models/EntityRelationship.d.ts.map +1 -0
- package/dist/src/generated/prisma/models/EntityRelationship.js +3 -0
- package/dist/src/generated/prisma/models/EntityRelationship.js.map +1 -0
- package/dist/src/generated/prisma/models/KnowledgeEntry.d.ts +1322 -0
- package/dist/src/generated/prisma/models/KnowledgeEntry.d.ts.map +1 -0
- package/dist/src/generated/prisma/models/KnowledgeEntry.js +3 -0
- package/dist/src/generated/prisma/models/KnowledgeEntry.js.map +1 -0
- package/dist/src/generated/prisma/models/WriteReceipt.d.ts +1147 -0
- package/dist/src/generated/prisma/models/WriteReceipt.d.ts.map +1 -0
- package/dist/src/generated/prisma/models/WriteReceipt.js +3 -0
- package/dist/src/generated/prisma/models/WriteReceipt.js.map +1 -0
- package/dist/src/generated/prisma/models.d.ts +8 -0
- package/dist/src/generated/prisma/models.d.ts.map +1 -0
- package/dist/src/generated/prisma/models.js +3 -0
- package/dist/src/generated/prisma/models.js.map +1 -0
- package/dist/src/lib/escalationPaths.d.ts +9 -0
- package/dist/src/lib/escalationPaths.d.ts.map +1 -0
- package/dist/src/lib/escalationPaths.js +38 -0
- package/dist/src/lib/escalationPaths.js.map +1 -0
- package/dist/src/lib/llm.d.ts +32 -0
- package/dist/src/lib/llm.d.ts.map +1 -0
- package/dist/src/lib/llm.js +161 -0
- package/dist/src/lib/llm.js.map +1 -0
- package/dist/src/lib/metrics.d.ts +21 -0
- package/dist/src/lib/metrics.d.ts.map +1 -0
- package/dist/src/lib/metrics.js +46 -0
- package/dist/src/lib/metrics.js.map +1 -0
- package/dist/src/lib/providers/claude.d.ts +7 -0
- package/dist/src/lib/providers/claude.d.ts.map +1 -0
- package/dist/src/lib/providers/claude.js +9 -0
- package/dist/src/lib/providers/claude.js.map +1 -0
- package/dist/src/lib/providers/gemini.d.ts +10 -0
- package/dist/src/lib/providers/gemini.d.ts.map +1 -0
- package/dist/src/lib/providers/gemini.js +40 -0
- package/dist/src/lib/providers/gemini.js.map +1 -0
- package/dist/src/lib/providers/groq.d.ts +10 -0
- package/dist/src/lib/providers/groq.d.ts.map +1 -0
- package/dist/src/lib/providers/groq.js +39 -0
- package/dist/src/lib/providers/groq.js.map +1 -0
- package/dist/src/lib/providers/mistral.d.ts +10 -0
- package/dist/src/lib/providers/mistral.d.ts.map +1 -0
- package/dist/src/lib/providers/mistral.js +39 -0
- package/dist/src/lib/providers/mistral.js.map +1 -0
- package/dist/src/lib/providers/mock.d.ts +24 -0
- package/dist/src/lib/providers/mock.d.ts.map +1 -0
- package/dist/src/lib/providers/mock.js +129 -0
- package/dist/src/lib/providers/mock.js.map +1 -0
- package/dist/src/lib/providers/ollama.d.ts +10 -0
- package/dist/src/lib/providers/ollama.d.ts.map +1 -0
- package/dist/src/lib/providers/ollama.js +39 -0
- package/dist/src/lib/providers/ollama.js.map +1 -0
- package/dist/src/lib/providers/openai.d.ts +11 -0
- package/dist/src/lib/providers/openai.d.ts.map +1 -0
- package/dist/src/lib/providers/openai.js +38 -0
- package/dist/src/lib/providers/openai.js.map +1 -0
- package/dist/src/lib/requestContext.d.ts +8 -0
- package/dist/src/lib/requestContext.d.ts.map +1 -0
- package/dist/src/lib/requestContext.js +10 -0
- package/dist/src/lib/requestContext.js.map +1 -0
- package/dist/src/lib/router.d.ts +16 -0
- package/dist/src/lib/router.d.ts.map +1 -0
- package/dist/src/lib/router.js +63 -0
- package/dist/src/lib/router.js.map +1 -0
- package/dist/src/librarian/chunker.d.ts +16 -0
- package/dist/src/librarian/chunker.d.ts.map +1 -0
- package/dist/src/librarian/chunker.js +67 -0
- package/dist/src/librarian/chunker.js.map +1 -0
- package/dist/src/librarian/getPolicy.d.ts +3 -0
- package/dist/src/librarian/getPolicy.d.ts.map +1 -0
- package/dist/src/librarian/getPolicy.js +22 -0
- package/dist/src/librarian/getPolicy.js.map +1 -0
- package/dist/src/librarian/guards.d.ts +9 -0
- package/dist/src/librarian/guards.d.ts.map +1 -0
- package/dist/src/librarian/guards.js +52 -0
- package/dist/src/librarian/guards.js.map +1 -0
- package/dist/src/librarian/index.d.ts +20 -0
- package/dist/src/librarian/index.d.ts.map +1 -0
- package/dist/src/librarian/index.js +512 -0
- package/dist/src/librarian/index.js.map +1 -0
- package/dist/src/librarian/policy.d.ts +13 -0
- package/dist/src/librarian/policy.d.ts.map +1 -0
- package/dist/src/librarian/policy.js +20 -0
- package/dist/src/librarian/policy.js.map +1 -0
- package/dist/src/librarian/scoring.d.ts +8 -0
- package/dist/src/librarian/scoring.d.ts.map +1 -0
- package/dist/src/librarian/scoring.js +10 -0
- package/dist/src/librarian/scoring.js.map +1 -0
- package/dist/src/librarian/source-reliability.d.ts +8 -0
- package/dist/src/librarian/source-reliability.d.ts.map +1 -0
- package/dist/src/librarian/source-reliability.js +105 -0
- package/dist/src/librarian/source-reliability.js.map +1 -0
- package/dist/src/library/agent-registry.d.ts +31 -0
- package/dist/src/library/agent-registry.d.ts.map +1 -0
- package/dist/src/library/agent-registry.js +197 -0
- package/dist/src/library/agent-registry.js.map +1 -0
- package/dist/src/library/client.d.ts +5 -0
- package/dist/src/library/client.d.ts.map +1 -0
- package/dist/src/library/client.js +39 -0
- package/dist/src/library/client.js.map +1 -0
- package/dist/src/library/entity-resolution.d.ts +47 -0
- package/dist/src/library/entity-resolution.d.ts.map +1 -0
- package/dist/src/library/entity-resolution.js +344 -0
- package/dist/src/library/entity-resolution.js.map +1 -0
- package/dist/src/library/locks.d.ts +9 -0
- package/dist/src/library/locks.d.ts.map +1 -0
- package/dist/src/library/locks.js +38 -0
- package/dist/src/library/locks.js.map +1 -0
- package/dist/src/library/queries.d.ts +66 -0
- package/dist/src/library/queries.d.ts.map +1 -0
- package/dist/src/library/queries.js +169 -0
- package/dist/src/library/queries.js.map +1 -0
- package/dist/src/library/relationships.d.ts +30 -0
- package/dist/src/library/relationships.d.ts.map +1 -0
- package/dist/src/library/relationships.js +97 -0
- package/dist/src/library/relationships.js.map +1 -0
- package/dist/src/sdk/index.d.ts +108 -0
- package/dist/src/sdk/index.d.ts.map +1 -0
- package/dist/src/sdk/index.js +323 -0
- package/dist/src/sdk/index.js.map +1 -0
- package/dist/src/security/apiKeys.d.ts +48 -0
- package/dist/src/security/apiKeys.d.ts.map +1 -0
- package/dist/src/security/apiKeys.js +279 -0
- package/dist/src/security/apiKeys.js.map +1 -0
- package/dist/src/types.d.ts +54 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +4 -0
- package/dist/src/types.js.map +1 -0
- package/package.json +86 -0
- package/prisma/migrations/20260228090200_init/migration.sql +49 -0
- package/prisma/migrations/20260228121746_add_properties_and_relationships/migration.sql +29 -0
- package/prisma/migrations/20260301223834_add_superseded_by_pointer/migration.sql +4 -0
- package/prisma/migrations/20260301225152_add_write_receipts/migration.sql +20 -0
- package/prisma/migrations/20260302135650_entity_resolution/migration.sql +33 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- 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
|