@rubytech/create-sitedesk-code 0.1.530 → 0.1.531
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/package.json +1 -1
- package/payload/platform/lib/graph-write/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/graph-write/dist/index.js +4 -0
- package/payload/platform/lib/graph-write/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-write/src/index.ts +4 -0
- package/payload/platform/lib/learning-constants/dist/index.d.ts +35 -0
- package/payload/platform/lib/learning-constants/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/learning-constants/dist/index.js +50 -0
- package/payload/platform/lib/learning-constants/dist/index.js.map +1 -0
- package/payload/platform/lib/learning-constants/package.json +7 -0
- package/payload/platform/lib/learning-constants/src/index.test.ts +47 -0
- package/payload/platform/lib/learning-constants/src/index.ts +57 -0
- package/payload/platform/lib/learning-constants/tsconfig.json +9 -0
- package/payload/platform/neo4j/schema.cypher +24 -0
- package/payload/platform/package.json +1 -1
- package/payload/platform/plugins/admin/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/admin/PLUGIN.md +1 -1
- package/payload/platform/plugins/admin/mcp/dist/index.js +2 -2
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/skills/insight/SKILL.md +1 -1
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +58 -10
- package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +7 -0
- package/payload/platform/plugins/docs/references/linkedin-extension.md +18 -9
- package/payload/platform/plugins/docs/references/session-retrospective.md +39 -0
- package/payload/platform/plugins/linkedin-extension/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/linkedin-extension/PLUGIN.md +7 -10
- package/payload/platform/plugins/linkedin-extension/extension/README.md +20 -17
- package/payload/platform/plugins/linkedin-extension/extension/background/sw.js +96 -13
- package/payload/platform/plugins/linkedin-extension/extension/manifest.json +3 -18
- package/payload/platform/plugins/linkedin-extension/extension/options/options.html +2 -2
- package/payload/platform/plugins/linkedin-extension/extension/options/options.js +1 -1
- package/payload/platform/plugins/memory/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/memory/PLUGIN.md +15 -1
- package/payload/platform/plugins/memory/mcp/dist/index.js +169 -0
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/session-jsonl.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/session-jsonl.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/session-jsonl.test.js +95 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/session-jsonl.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/session-jsonl.d.ts +60 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/session-jsonl.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/session-jsonl.js +153 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/session-jsonl.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/cross-account.test.js +6 -4
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/cross-account.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/learnings-derive.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/learnings-derive.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/learnings-derive.test.js +160 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/learnings-derive.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/learnings-recall.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/learnings-recall.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/learnings-recall.test.js +64 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/learnings-recall.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/learnings-scan.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/learnings-scan.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/learnings-scan.test.js +78 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/learnings-scan.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/learnings-derive.d.ts +41 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/learnings-derive.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/learnings-derive.js +225 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/learnings-derive.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/learnings-recall.d.ts +31 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/learnings-recall.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/learnings-recall.js +67 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/learnings-recall.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/learnings-scan.d.ts +40 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/learnings-scan.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/learnings-scan.js +55 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/learnings-scan.js.map +1 -0
- package/payload/platform/plugins/memory/references/schema-base.md +3 -0
- package/payload/platform/plugins/memory/skills/learnings-recall/SKILL.md +27 -0
- package/payload/platform/plugins/whatsapp/mcp/dist/index.js +4 -4
- package/payload/platform/plugins/whatsapp/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +3 -3
- package/payload/platform/plugins/whatsapp/skills/connect-whatsapp/SKILL.md +6 -2
- package/payload/platform/plugins/whatsapp/skills/manage-whatsapp-config/SKILL.md +5 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +3 -0
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/learning-index.d.ts +28 -0
- package/payload/platform/services/claude-session-manager/dist/learning-index.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/learning-index.js +70 -0
- package/payload/platform/services/claude-session-manager/dist/learning-index.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +18 -0
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/system-prompt.d.ts +6 -0
- package/payload/platform/services/claude-session-manager/dist/system-prompt.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/system-prompt.js +17 -0
- package/payload/platform/services/claude-session-manager/dist/system-prompt.js.map +1 -1
- package/payload/platform/templates/specialists/agents/librarian.md +1 -1
- package/payload/server/{chunk-2YMGNYA3.js → chunk-SVB5NZ64.js} +41 -30
- package/payload/server/{manager-XPRN5UF5.js → manager-4Q3DTLXO.js} +5 -1
- package/payload/server/server.js +65 -17
- package/payload/platform/plugins/linkedin-extension/extension/assets/pill.css +0 -52
- package/payload/platform/plugins/linkedin-extension/extension/content/profile.js +0 -100
- package/payload/platform/plugins/linkedin-extension/extension/content/thread.js +0 -102
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"learnings-scan.test.js","sourceRoot":"","sources":["../../../src/tools/__tests__/learnings-scan.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* learnings-derive — turn the turn's per-chunk claims into :Learning writes,
|
|
3
|
+
* then decay what nobody re-observed.
|
|
4
|
+
*
|
|
5
|
+
* Reconcile is the two-tier shape profile-update.ts:708-755 already uses for
|
|
6
|
+
* preferences: embed the claim, take the nearest same-account neighbour over
|
|
7
|
+
* the vector index, convert the normalised index score back to raw cosine, and
|
|
8
|
+
* compare against SAME_SUBJECT_COSINE. The claim's stance is explicit
|
|
9
|
+
* (`reinforce` | `contradict`) exactly as profile-update takes `mode` — the
|
|
10
|
+
* tool never infers a stance from prose.
|
|
11
|
+
*
|
|
12
|
+
* Decay runs here, inside the routine's own pass, NOT at session start.
|
|
13
|
+
* Preference decay fires only inside loadUserProfile (neo4j-store.ts:2704),
|
|
14
|
+
* which means an account nobody opens never decays; this does not inherit that.
|
|
15
|
+
*/
|
|
16
|
+
export type ClaimMode = "reinforce" | "contradict";
|
|
17
|
+
export interface Claim {
|
|
18
|
+
title: string;
|
|
19
|
+
body: string;
|
|
20
|
+
mode: ClaimMode;
|
|
21
|
+
}
|
|
22
|
+
export interface LearningsDeriveParams {
|
|
23
|
+
accountId: string;
|
|
24
|
+
sessionId: string;
|
|
25
|
+
claims: Claim[];
|
|
26
|
+
/** Run the decay pass after the claims land. The overnight routine sets it. */
|
|
27
|
+
runDecay?: boolean;
|
|
28
|
+
}
|
|
29
|
+
export interface DecaySummary {
|
|
30
|
+
examined: number;
|
|
31
|
+
decayed: number;
|
|
32
|
+
floored: number;
|
|
33
|
+
}
|
|
34
|
+
export interface LearningsDeriveResult {
|
|
35
|
+
created: number;
|
|
36
|
+
amplified: number;
|
|
37
|
+
relegated: number;
|
|
38
|
+
decay: DecaySummary | null;
|
|
39
|
+
}
|
|
40
|
+
export declare function learningsDerive(params: LearningsDeriveParams): Promise<LearningsDeriveResult>;
|
|
41
|
+
//# sourceMappingURL=learnings-derive.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"learnings-derive.d.ts","sourceRoot":"","sources":["../../src/tools/learnings-derive.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAaH,MAAM,MAAM,SAAS,GAAG,WAAW,GAAG,YAAY,CAAC;AAGnD,MAAM,WAAW,KAAK;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,SAAS,CAAC;CACjB;AAED,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;CAC5B;AAkMD,wBAAsB,eAAe,CACnC,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC,CA8DhC"}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* learnings-derive — turn the turn's per-chunk claims into :Learning writes,
|
|
3
|
+
* then decay what nobody re-observed.
|
|
4
|
+
*
|
|
5
|
+
* Reconcile is the two-tier shape profile-update.ts:708-755 already uses for
|
|
6
|
+
* preferences: embed the claim, take the nearest same-account neighbour over
|
|
7
|
+
* the vector index, convert the normalised index score back to raw cosine, and
|
|
8
|
+
* compare against SAME_SUBJECT_COSINE. The claim's stance is explicit
|
|
9
|
+
* (`reinforce` | `contradict`) exactly as profile-update takes `mode` — the
|
|
10
|
+
* tool never infers a stance from prose.
|
|
11
|
+
*
|
|
12
|
+
* Decay runs here, inside the routine's own pass, NOT at session start.
|
|
13
|
+
* Preference decay fires only inside loadUserProfile (neo4j-store.ts:2704),
|
|
14
|
+
* which means an account nobody opens never decays; this does not inherit that.
|
|
15
|
+
*/
|
|
16
|
+
import { randomUUID } from "node:crypto";
|
|
17
|
+
import { getSession } from "../lib/neo4j.js";
|
|
18
|
+
import { embed } from "../lib/embeddings.js";
|
|
19
|
+
import { SAME_SUBJECT_COSINE } from "../../../../../lib/active-rules/dist/index.js";
|
|
20
|
+
import { LEARNING_CONTRADICTION_DECREMENT, LEARNING_INITIAL_CONFIDENCE, LEARNING_REINFORCEMENT_INCREMENT, decayedConfidence, } from "../../../../../lib/learning-constants/dist/index.js";
|
|
21
|
+
const VALID_MODES = new Set(["reinforce", "contradict"]);
|
|
22
|
+
async function nearestNeighbour(accountId, embedding) {
|
|
23
|
+
const session = getSession();
|
|
24
|
+
try {
|
|
25
|
+
// Over-fetch 25 then filter to this account: the vector index cannot
|
|
26
|
+
// pre-filter by accountId, and a house install co-resides many
|
|
27
|
+
// sub-accounts' learnings in one index, so a small topK could let another
|
|
28
|
+
// account's rows crowd out this account's real same-subject neighbour.
|
|
29
|
+
const res = await session.run(`
|
|
30
|
+
CALL db.index.vector.queryNodes('learning_embedding', 25, $embedding)
|
|
31
|
+
YIELD node AS l, score
|
|
32
|
+
WHERE l.accountId = $accountId
|
|
33
|
+
RETURN l.learningId AS learningId, l.title AS title,
|
|
34
|
+
l.confidence AS confidence, score
|
|
35
|
+
ORDER BY score DESC
|
|
36
|
+
LIMIT 1
|
|
37
|
+
`, { embedding, accountId });
|
|
38
|
+
const r = res.records[0];
|
|
39
|
+
if (!r)
|
|
40
|
+
return null;
|
|
41
|
+
return {
|
|
42
|
+
learningId: r.get("learningId"),
|
|
43
|
+
title: r.get("title"),
|
|
44
|
+
confidence: r.get("confidence"),
|
|
45
|
+
// Neo4j's cosine vector index returns a NORMALIZED score (1 + cosine)/2,
|
|
46
|
+
// not raw cosine. Comparing the raw score directly would fire at raw
|
|
47
|
+
// cosine ~0.70 and over-merge distinct learnings.
|
|
48
|
+
similarity: 2 * r.get("score") - 1,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
await session.close();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function createLearning(accountId, sessionId, claim, embedding, nowIso) {
|
|
56
|
+
const session = getSession();
|
|
57
|
+
try {
|
|
58
|
+
// The :Learning is CREATEd unconditionally and the parent edge is attached
|
|
59
|
+
// only if a UserProfile exists. Neither of the obvious shapes is safe:
|
|
60
|
+
// `MATCH (p:UserProfile …) CREATE …` silently no-ops the whole write when
|
|
61
|
+
// the profile is absent (the hazard profile-update.ts:309-311 documents for
|
|
62
|
+
// preferences), and `MERGE (p:UserProfile { accountId })` would mint a
|
|
63
|
+
// profile missing its required `userId` (schema-base.md:32). Account scope
|
|
64
|
+
// is the `accountId` property, not the edge, so the edge is a convenience
|
|
65
|
+
// and its absence costs nothing.
|
|
66
|
+
//
|
|
67
|
+
// `embedding` is omitted entirely rather than written as [] when the
|
|
68
|
+
// embedder returned nothing. :Learning is vector-indexed, and a zero-length
|
|
69
|
+
// vector makes the node permanently invisible to the neighbour query — it
|
|
70
|
+
// could never be amplified, so every re-observation would mint a duplicate.
|
|
71
|
+
// That is exactly the rising-total/flat-amplified signature, so it is
|
|
72
|
+
// logged rather than left to be inferred.
|
|
73
|
+
await session.run(`
|
|
74
|
+
CREATE (l:Learning {
|
|
75
|
+
accountId: $accountId,
|
|
76
|
+
learningId: $learningId,
|
|
77
|
+
title: $title,
|
|
78
|
+
body: $body,
|
|
79
|
+
confidence: $confidence,
|
|
80
|
+
observedAt: $observedAt,
|
|
81
|
+
observationCount: 1,
|
|
82
|
+
sourceSessionIds: [$sessionId]
|
|
83
|
+
})
|
|
84
|
+
WITH l
|
|
85
|
+
FOREACH (_ IN CASE WHEN $embedding IS NULL THEN [] ELSE [1] END |
|
|
86
|
+
SET l.embedding = $embedding
|
|
87
|
+
)
|
|
88
|
+
WITH l
|
|
89
|
+
OPTIONAL MATCH (p:UserProfile { accountId: $accountId })
|
|
90
|
+
FOREACH (_ IN CASE WHEN p IS NULL THEN [] ELSE [1] END |
|
|
91
|
+
CREATE (p)-[:HAS_LEARNING]->(l)
|
|
92
|
+
)
|
|
93
|
+
`, {
|
|
94
|
+
accountId,
|
|
95
|
+
learningId: randomUUID(),
|
|
96
|
+
title: claim.title,
|
|
97
|
+
body: claim.body,
|
|
98
|
+
confidence: LEARNING_INITIAL_CONFIDENCE,
|
|
99
|
+
observedAt: nowIso,
|
|
100
|
+
sessionId,
|
|
101
|
+
embedding,
|
|
102
|
+
});
|
|
103
|
+
if (embedding === null) {
|
|
104
|
+
process.stderr.write(`[learnings] op=derive-embed-missing accountId=${accountId} ` +
|
|
105
|
+
`title=${JSON.stringify(claim.title)} reason=embedder-returned-nothing\n`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
await session.close();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async function applyToExisting(accountId, sessionId, match, mode, nowIso) {
|
|
113
|
+
const confidence = mode === "reinforce"
|
|
114
|
+
? match.confidence + LEARNING_REINFORCEMENT_INCREMENT * (1 - match.confidence)
|
|
115
|
+
: Math.max(0, match.confidence - LEARNING_CONTRADICTION_DECREMENT);
|
|
116
|
+
const contradictedClause = mode === "contradict" ? ",\n l.contradictedAt = $observedAt" : "";
|
|
117
|
+
const session = getSession();
|
|
118
|
+
try {
|
|
119
|
+
await session.run(`
|
|
120
|
+
MATCH (l:Learning { accountId: $accountId, learningId: $learningId })
|
|
121
|
+
SET l.confidence = $confidence,
|
|
122
|
+
l.observedAt = $observedAt,
|
|
123
|
+
l.observationCount = coalesce(l.observationCount, 0) + 1,
|
|
124
|
+
l.sourceSessionIds =
|
|
125
|
+
CASE WHEN $sessionId IN coalesce(l.sourceSessionIds, [])
|
|
126
|
+
THEN l.sourceSessionIds
|
|
127
|
+
ELSE coalesce(l.sourceSessionIds, []) + $sessionId END${contradictedClause}
|
|
128
|
+
`, {
|
|
129
|
+
accountId,
|
|
130
|
+
learningId: match.learningId,
|
|
131
|
+
confidence,
|
|
132
|
+
observedAt: nowIso,
|
|
133
|
+
sessionId,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
finally {
|
|
137
|
+
await session.close();
|
|
138
|
+
}
|
|
139
|
+
return confidence;
|
|
140
|
+
}
|
|
141
|
+
async function runDecayPass(accountId, now) {
|
|
142
|
+
const session = getSession();
|
|
143
|
+
try {
|
|
144
|
+
// observedAt is an ISO STRING. Comparing it to datetime() in the predicate
|
|
145
|
+
// yields null for EVERY row, so the pass would report 0 decayed behind an
|
|
146
|
+
// otherwise healthy log line. The curve is applied in JS instead, off the
|
|
147
|
+
// same pure function the tests and the injection read use.
|
|
148
|
+
const res = await session.run(`
|
|
149
|
+
MATCH (l:Learning { accountId: $accountId })
|
|
150
|
+
RETURN l.learningId AS learningId, l.confidence AS confidence,
|
|
151
|
+
l.observedAt AS observedAt
|
|
152
|
+
`, { accountId });
|
|
153
|
+
let decayed = 0;
|
|
154
|
+
let floored = 0;
|
|
155
|
+
for (const r of res.records) {
|
|
156
|
+
const learningId = r.get("learningId");
|
|
157
|
+
const before = r.get("confidence");
|
|
158
|
+
const observedAt = String(r.get("observedAt"));
|
|
159
|
+
const after = decayedConfidence(before, observedAt, now);
|
|
160
|
+
if (after === before)
|
|
161
|
+
continue;
|
|
162
|
+
decayed += 1;
|
|
163
|
+
if (after === 0)
|
|
164
|
+
floored += 1;
|
|
165
|
+
await session.run(`MATCH (l:Learning { accountId: $accountId, learningId: $learningId })
|
|
166
|
+
SET l.confidence = $confidence`, { accountId, learningId, confidence: after });
|
|
167
|
+
}
|
|
168
|
+
return { examined: res.records.length, decayed, floored };
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
await session.close();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
export async function learningsDerive(params) {
|
|
175
|
+
const { accountId, sessionId, claims } = params;
|
|
176
|
+
if (!accountId)
|
|
177
|
+
throw new Error("learnings-derive: accountId is required");
|
|
178
|
+
if (!sessionId)
|
|
179
|
+
throw new Error("learnings-derive: sessionId is required");
|
|
180
|
+
// Validate every claim before writing any of them, so a bad mode halfway
|
|
181
|
+
// down the list cannot leave a half-applied batch behind.
|
|
182
|
+
for (const c of claims) {
|
|
183
|
+
if (!VALID_MODES.has(c.mode)) {
|
|
184
|
+
throw new Error(`learnings-derive: Invalid mode "${c.mode}". Must be one of: reinforce, contradict`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const now = new Date();
|
|
188
|
+
const nowIso = now.toISOString();
|
|
189
|
+
let created = 0;
|
|
190
|
+
let amplified = 0;
|
|
191
|
+
let relegated = 0;
|
|
192
|
+
for (const claim of claims) {
|
|
193
|
+
const embedding = await embed(`${claim.title}\n${claim.body}`);
|
|
194
|
+
const match = embedding ? await nearestNeighbour(accountId, embedding) : null;
|
|
195
|
+
if (match && match.similarity >= SAME_SUBJECT_COSINE) {
|
|
196
|
+
const after = await applyToExisting(accountId, sessionId, match, claim.mode, nowIso);
|
|
197
|
+
const action = claim.mode === "reinforce" ? "amplify" : "relegate";
|
|
198
|
+
if (claim.mode === "reinforce")
|
|
199
|
+
amplified += 1;
|
|
200
|
+
else
|
|
201
|
+
relegated += 1;
|
|
202
|
+
process.stderr.write(`[learnings] op=derive accountId=${accountId} action=${action} ` +
|
|
203
|
+
`learningId=${match.learningId} similarity=${match.similarity.toFixed(3)} ` +
|
|
204
|
+
`confBefore=${match.confidence} confAfter=${after}\n`);
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
await createLearning(accountId, sessionId, claim, embedding ?? null, nowIso);
|
|
208
|
+
created += 1;
|
|
209
|
+
process.stderr.write(`[learnings] op=derive accountId=${accountId} action=create ` +
|
|
210
|
+
`title=${JSON.stringify(claim.title)} confidence=${LEARNING_INITIAL_CONFIDENCE}\n`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// Sustained amplified=0 against a rising total is the duplicate signature —
|
|
214
|
+
// the match path never firing and every claim minting a new node.
|
|
215
|
+
process.stderr.write(`[learnings] op=derive-summary accountId=${accountId} created=${created} ` +
|
|
216
|
+
`amplified=${amplified} relegated=${relegated}\n`);
|
|
217
|
+
let decay = null;
|
|
218
|
+
if (params.runDecay === true) {
|
|
219
|
+
decay = await runDecayPass(accountId, now);
|
|
220
|
+
process.stderr.write(`[learnings] op=decay accountId=${accountId} examined=${decay.examined} ` +
|
|
221
|
+
`decayed=${decay.decayed} floored=${decay.floored}\n`);
|
|
222
|
+
}
|
|
223
|
+
return { created, amplified, relegated, decay };
|
|
224
|
+
}
|
|
225
|
+
//# sourceMappingURL=learnings-derive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"learnings-derive.js","sourceRoot":"","sources":["../../src/tools/learnings-derive.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,+CAA+C,CAAC;AACpF,OAAO,EACL,gCAAgC,EAChC,2BAA2B,EAC3B,gCAAgC,EAChC,iBAAiB,GAClB,MAAM,qDAAqD,CAAC;AAG7D,MAAM,WAAW,GAAG,IAAI,GAAG,CAAS,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC,CAAC;AAoCjE,KAAK,UAAU,gBAAgB,CAC7B,SAAiB,EACjB,SAAmB;IAEnB,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,IAAI,CAAC;QACH,qEAAqE;QACrE,+DAA+D;QAC/D,0EAA0E;QAC1E,uEAAuE;QACvE,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,CAC3B;;;;;;;;OAQC,EACD,EAAE,SAAS,EAAE,SAAS,EAAE,CACzB,CAAC;QACF,MAAM,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QACzB,IAAI,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;QACpB,OAAO;YACL,UAAU,EAAE,CAAC,CAAC,GAAG,CAAC,YAAY,CAAW;YACzC,KAAK,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,CAAW;YAC/B,UAAU,EAAE,CAAC,CAAC,GAAG,CAAC,YAAY,CAAW;YACzC,yEAAyE;YACzE,qEAAqE;YACrE,kDAAkD;YAClD,UAAU,EAAE,CAAC,GAAI,CAAC,CAAC,GAAG,CAAC,OAAO,CAAY,GAAG,CAAC;SAC/C,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;AACH,CAAC;AAED,KAAK,UAAU,cAAc,CAC3B,SAAiB,EACjB,SAAiB,EACjB,KAAY,EACZ,SAA0B,EAC1B,MAAc;IAEd,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,IAAI,CAAC;QACH,2EAA2E;QAC3E,uEAAuE;QACvE,0EAA0E;QAC1E,4EAA4E;QAC5E,uEAAuE;QACvE,2EAA2E;QAC3E,0EAA0E;QAC1E,iCAAiC;QACjC,EAAE;QACF,qEAAqE;QACrE,4EAA4E;QAC5E,0EAA0E;QAC1E,4EAA4E;QAC5E,sEAAsE;QACtE,0CAA0C;QAC1C,MAAM,OAAO,CAAC,GAAG,CACf;;;;;;;;;;;;;;;;;;;;OAoBC,EACD;YACE,SAAS;YACT,UAAU,EAAE,UAAU,EAAE;YACxB,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,UAAU,EAAE,2BAA2B;YACvC,UAAU,EAAE,MAAM;YAClB,SAAS;YACT,SAAS;SACV,CACF,CAAC;QACF,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;YACvB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,iDAAiD,SAAS,GAAG;gBAC3D,SAAS,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,qCAAqC,CAC5E,CAAC;QACJ,CAAC;IACH,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;AACH,CAAC;AAED,KAAK,UAAU,eAAe,CAC5B,SAAiB,EACjB,SAAiB,EACjB,KAAgB,EAChB,IAAe,EACf,MAAc;IAEd,MAAM,UAAU,GACd,IAAI,KAAK,WAAW;QAClB,CAAC,CAAC,KAAK,CAAC,UAAU,GAAG,gCAAgC,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,UAAU,CAAC;QAC9E,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,GAAG,gCAAgC,CAAC,CAAC;IAEvE,MAAM,kBAAkB,GACtB,IAAI,KAAK,YAAY,CAAC,CAAC,CAAC,6CAA6C,CAAC,CAAC,CAAC,EAAE,CAAC;IAE7E,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,IAAI,CAAC;QACH,MAAM,OAAO,CAAC,GAAG,CACf;;;;;;;;yEAQmE,kBAAkB;OACpF,EACD;YACE,SAAS;YACT,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,UAAU;YACV,UAAU,EAAE,MAAM;YAClB,SAAS;SACV,CACF,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,KAAK,UAAU,YAAY,CAAC,SAAiB,EAAE,GAAS;IACtD,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,IAAI,CAAC;QACH,2EAA2E;QAC3E,0EAA0E;QAC1E,0EAA0E;QAC1E,2DAA2D;QAC3D,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,CAC3B;;;;OAIC,EACD,EAAE,SAAS,EAAE,CACd,CAAC;QACF,IAAI,OAAO,GAAG,CAAC,CAAC;QAChB,IAAI,OAAO,GAAG,CAAC,CAAC;QAChB,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;YAC5B,MAAM,UAAU,GAAG,CAAC,CAAC,GAAG,CAAC,YAAY,CAAW,CAAC;YACjD,MAAM,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,YAAY,CAAW,CAAC;YAC7C,MAAM,UAAU,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC;YAC/C,MAAM,KAAK,GAAG,iBAAiB,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC;YACzD,IAAI,KAAK,KAAK,MAAM;gBAAE,SAAS;YAC/B,OAAO,IAAI,CAAC,CAAC;YACb,IAAI,KAAK,KAAK,CAAC;gBAAE,OAAO,IAAI,CAAC,CAAC;YAC9B,MAAM,OAAO,CAAC,GAAG,CACf;wCACgC,EAChC,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,CAC7C,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;IAC5D,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,MAA6B;IAE7B,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;IAChD,IAAI,CAAC,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;IAC3E,IAAI,CAAC,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;IAE3E,yEAAyE;IACzE,0DAA0D;IAC1D,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;QACvB,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7B,MAAM,IAAI,KAAK,CACb,mCAAmC,CAAC,CAAC,IAAI,0CAA0C,CACpF,CAAC;QACJ,CAAC;IACH,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IACvB,MAAM,MAAM,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;IACjC,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,SAAS,GAAG,CAAC,CAAC;IAElB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,SAAS,GAAG,MAAM,KAAK,CAAC,GAAG,KAAK,CAAC,KAAK,KAAK,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;QAC/D,MAAM,KAAK,GAAG,SAAS,CAAC,CAAC,CAAC,MAAM,gBAAgB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAE9E,IAAI,KAAK,IAAI,KAAK,CAAC,UAAU,IAAI,mBAAmB,EAAE,CAAC;YACrD,MAAM,KAAK,GAAG,MAAM,eAAe,CAAC,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YACrF,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC;YACnE,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW;gBAAE,SAAS,IAAI,CAAC,CAAC;;gBAC1C,SAAS,IAAI,CAAC,CAAC;YACpB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,mCAAmC,SAAS,WAAW,MAAM,GAAG;gBAC9D,cAAc,KAAK,CAAC,UAAU,eAAe,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG;gBAC3E,cAAc,KAAK,CAAC,UAAU,cAAc,KAAK,IAAI,CACxD,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,MAAM,cAAc,CAAC,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,IAAI,IAAI,EAAE,MAAM,CAAC,CAAC;YAC7E,OAAO,IAAI,CAAC,CAAC;YACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,mCAAmC,SAAS,iBAAiB;gBAC3D,SAAS,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,eAAe,2BAA2B,IAAI,CACrF,CAAC;QACJ,CAAC;IACH,CAAC;IAED,4EAA4E;IAC5E,kEAAkE;IAClE,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,2CAA2C,SAAS,YAAY,OAAO,GAAG;QACxE,aAAa,SAAS,cAAc,SAAS,IAAI,CACpD,CAAC;IAEF,IAAI,KAAK,GAAwB,IAAI,CAAC;IACtC,IAAI,MAAM,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;QAC7B,KAAK,GAAG,MAAM,YAAY,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;QAC3C,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,kCAAkC,SAAS,aAAa,KAAK,CAAC,QAAQ,GAAG;YACvE,WAAW,KAAK,CAAC,OAAO,YAAY,KAAK,CAAC,OAAO,IAAI,CACxD,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;AAClD,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* learnings-recall — the only path from an injected learning title to its body.
|
|
3
|
+
*
|
|
4
|
+
* The system prompt carries titles and never bodies (Task 2220). When a turn
|
|
5
|
+
* matches an indexed title, the learnings-recall skill tells the agent to call
|
|
6
|
+
* this. The log line lives here, not in the skill: a SKILL.md is prose loaded
|
|
7
|
+
* into a turn and writes nothing to stderr, so without this tool the
|
|
8
|
+
* "index injected but never used" failure would be unobservable — and that
|
|
9
|
+
* measurement is what decides whether MAX_INDEXED_LEARNINGS should fall.
|
|
10
|
+
*/
|
|
11
|
+
export interface RecallRow {
|
|
12
|
+
learningId: string;
|
|
13
|
+
title: string;
|
|
14
|
+
body: string;
|
|
15
|
+
confidence: number;
|
|
16
|
+
observedAt: string;
|
|
17
|
+
observationCount: number;
|
|
18
|
+
/** Set when a later session disputed this learning. Surfaced so the agent
|
|
19
|
+
* can say so rather than acting on it as settled. */
|
|
20
|
+
contradictedAt: string | null;
|
|
21
|
+
}
|
|
22
|
+
export interface LearningsRecallParams {
|
|
23
|
+
accountId: string;
|
|
24
|
+
/** Case-insensitive substring match against title. */
|
|
25
|
+
title?: string;
|
|
26
|
+
learningId?: string;
|
|
27
|
+
}
|
|
28
|
+
export declare function learningsRecall(params: LearningsRecallParams): Promise<{
|
|
29
|
+
rows: RecallRow[];
|
|
30
|
+
}>;
|
|
31
|
+
//# sourceMappingURL=learnings-recall.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"learnings-recall.d.ts","sourceRoot":"","sources":["../../src/tools/learnings-recall.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,MAAM,WAAW,SAAS;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB;0DACsD;IACtD,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,sDAAsD;IACtD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAID,wBAAsB,eAAe,CACnC,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC;IAAE,IAAI,EAAE,SAAS,EAAE,CAAA;CAAE,CAAC,CA+DhC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* learnings-recall — the only path from an injected learning title to its body.
|
|
3
|
+
*
|
|
4
|
+
* The system prompt carries titles and never bodies (Task 2220). When a turn
|
|
5
|
+
* matches an indexed title, the learnings-recall skill tells the agent to call
|
|
6
|
+
* this. The log line lives here, not in the skill: a SKILL.md is prose loaded
|
|
7
|
+
* into a turn and writes nothing to stderr, so without this tool the
|
|
8
|
+
* "index injected but never used" failure would be unobservable — and that
|
|
9
|
+
* measurement is what decides whether MAX_INDEXED_LEARNINGS should fall.
|
|
10
|
+
*/
|
|
11
|
+
import { getSession } from "../lib/neo4j.js";
|
|
12
|
+
const MAX_ROWS = 5;
|
|
13
|
+
export async function learningsRecall(params) {
|
|
14
|
+
const { accountId, title, learningId } = params;
|
|
15
|
+
if (!accountId)
|
|
16
|
+
throw new Error("learnings-recall: accountId is required");
|
|
17
|
+
if (!title && !learningId) {
|
|
18
|
+
throw new Error("learnings-recall: title or learningId is required");
|
|
19
|
+
}
|
|
20
|
+
const session = getSession();
|
|
21
|
+
try {
|
|
22
|
+
const res = await session.run(`
|
|
23
|
+
MATCH (l:Learning { accountId: $accountId })
|
|
24
|
+
WHERE ($learningId IS NULL OR l.learningId = $learningId)
|
|
25
|
+
AND ($title IS NULL OR toLower(l.title) CONTAINS toLower($title))
|
|
26
|
+
RETURN l.learningId AS learningId, l.title AS title, l.body AS body,
|
|
27
|
+
l.confidence AS confidence, l.observedAt AS observedAt,
|
|
28
|
+
coalesce(l.observationCount, 0) AS observationCount,
|
|
29
|
+
l.contradictedAt AS contradictedAt
|
|
30
|
+
ORDER BY l.confidence DESC
|
|
31
|
+
LIMIT toInteger($limit)
|
|
32
|
+
`, {
|
|
33
|
+
accountId,
|
|
34
|
+
title: title ?? null,
|
|
35
|
+
learningId: learningId ?? null,
|
|
36
|
+
limit: MAX_ROWS,
|
|
37
|
+
});
|
|
38
|
+
const rows = res.records.map((r) => {
|
|
39
|
+
const contradictedAt = r.get("contradictedAt");
|
|
40
|
+
return {
|
|
41
|
+
learningId: r.get("learningId"),
|
|
42
|
+
title: r.get("title"),
|
|
43
|
+
body: r.get("body"),
|
|
44
|
+
confidence: r.get("confidence"),
|
|
45
|
+
observedAt: String(r.get("observedAt")),
|
|
46
|
+
observationCount: Number(r.get("observationCount")),
|
|
47
|
+
contradictedAt: contradictedAt === null || contradictedAt === undefined
|
|
48
|
+
? null
|
|
49
|
+
: String(contradictedAt),
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
for (const row of rows) {
|
|
53
|
+
process.stderr.write(`[learnings] op=recall accountId=${accountId} learningId=${row.learningId} ` +
|
|
54
|
+
`matchedTitle=${JSON.stringify(row.title)}\n`);
|
|
55
|
+
}
|
|
56
|
+
// A miss is still an invocation. Without this line, a week of zero recalls
|
|
57
|
+
// could not be told apart from a week of recalls that all missed.
|
|
58
|
+
if (rows.length === 0) {
|
|
59
|
+
process.stderr.write(`[learnings] op=recall accountId=${accountId} learningId=- matchedTitle=- rows=0\n`);
|
|
60
|
+
}
|
|
61
|
+
return { rows };
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
await session.close();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=learnings-recall.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"learnings-recall.js","sourceRoot":"","sources":["../../src/tools/learnings-recall.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAqB7C,MAAM,QAAQ,GAAG,CAAC,CAAC;AAEnB,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,MAA6B;IAE7B,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,MAAM,CAAC;IAChD,IAAI,CAAC,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;IAC3E,IAAI,CAAC,KAAK,IAAI,CAAC,UAAU,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;IACvE,CAAC;IAED,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,CAC3B;;;;;;;;;;OAUC,EACD;YACE,SAAS;YACT,KAAK,EAAE,KAAK,IAAI,IAAI;YACpB,UAAU,EAAE,UAAU,IAAI,IAAI;YAC9B,KAAK,EAAE,QAAQ;SAChB,CACF,CAAC;QAEF,MAAM,IAAI,GAAgB,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YAC9C,MAAM,cAAc,GAAG,CAAC,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;YAC/C,OAAO;gBACL,UAAU,EAAE,CAAC,CAAC,GAAG,CAAC,YAAY,CAAW;gBACzC,KAAK,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,CAAW;gBAC/B,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,MAAM,CAAW;gBAC7B,UAAU,EAAE,CAAC,CAAC,GAAG,CAAC,YAAY,CAAW;gBACzC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;gBACvC,gBAAgB,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;gBACnD,cAAc,EACZ,cAAc,KAAK,IAAI,IAAI,cAAc,KAAK,SAAS;oBACrD,CAAC,CAAC,IAAI;oBACN,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC;aAC7B,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,mCAAmC,SAAS,eAAe,GAAG,CAAC,UAAU,GAAG;gBAC1E,gBAAgB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAChD,CAAC;QACJ,CAAC;QACD,2EAA2E;QAC3E,kEAAkE;QAClE,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACtB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,mCAAmC,SAAS,uCAAuC,CACpF,CAAC;QACJ,CAAC;QAED,OAAO,EAAE,IAAI,EAAE,CAAC;IAClB,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* learnings-scan — page session transcripts to the caller's turn.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors `conversation-archive-list-chunks` in shape: a read-only pager that
|
|
5
|
+
* hands bodies to the agent, which emits per-chunk claims in-turn and passes
|
|
6
|
+
* them to `learnings-derive`. The difference is the source — disk, not Neo4j.
|
|
7
|
+
*
|
|
8
|
+
* Counter contract (Task 2220 observability item 1): `candidateFiles` is the
|
|
9
|
+
* on-disk .jsonl count BEFORE filtering. candidateFiles>0 with sessions=0 is a
|
|
10
|
+
* broken sinceIso comparison; both zero on an install with known-closed
|
|
11
|
+
* sessions is a broken enumerator path. Without the pre-filter count the two
|
|
12
|
+
* are indistinguishable and both read as a quiet night.
|
|
13
|
+
*/
|
|
14
|
+
export interface LearningsScanParams {
|
|
15
|
+
accountId: string;
|
|
16
|
+
/** Only sessions whose last write is strictly after this. Omit for all. */
|
|
17
|
+
sinceIso?: string;
|
|
18
|
+
sessionOffset?: number;
|
|
19
|
+
sessionLimit?: number;
|
|
20
|
+
/** Zero-based chunk within each returned session. Default 0. */
|
|
21
|
+
chunkIndex?: number;
|
|
22
|
+
/** Turns per chunk (1..200). Default 40. */
|
|
23
|
+
chunkTurns?: number;
|
|
24
|
+
}
|
|
25
|
+
export interface ScanRow {
|
|
26
|
+
sessionId: string;
|
|
27
|
+
lastWriteIso: string;
|
|
28
|
+
chunkIndex: number;
|
|
29
|
+
totalChunks: number;
|
|
30
|
+
body: string;
|
|
31
|
+
}
|
|
32
|
+
export interface LearningsScanResult {
|
|
33
|
+
candidateFiles: number;
|
|
34
|
+
sessions: number;
|
|
35
|
+
skipped: number;
|
|
36
|
+
sinceIso: string | null;
|
|
37
|
+
rows: ScanRow[];
|
|
38
|
+
}
|
|
39
|
+
export declare function learningsScan(params: LearningsScanParams): Promise<LearningsScanResult>;
|
|
40
|
+
//# sourceMappingURL=learnings-scan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"learnings-scan.d.ts","sourceRoot":"","sources":["../../src/tools/learnings-scan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAeH,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gEAAgE;IAChE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4CAA4C;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,OAAO;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,mBAAmB;IAClC,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,IAAI,EAAE,OAAO,EAAE,CAAC;CACjB;AAED,wBAAsB,aAAa,CACjC,MAAM,EAAE,mBAAmB,GAC1B,OAAO,CAAC,mBAAmB,CAAC,CAsD9B"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* learnings-scan — page session transcripts to the caller's turn.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors `conversation-archive-list-chunks` in shape: a read-only pager that
|
|
5
|
+
* hands bodies to the agent, which emits per-chunk claims in-turn and passes
|
|
6
|
+
* them to `learnings-derive`. The difference is the source — disk, not Neo4j.
|
|
7
|
+
*
|
|
8
|
+
* Counter contract (Task 2220 observability item 1): `candidateFiles` is the
|
|
9
|
+
* on-disk .jsonl count BEFORE filtering. candidateFiles>0 with sessions=0 is a
|
|
10
|
+
* broken sinceIso comparison; both zero on an install with known-closed
|
|
11
|
+
* sessions is a broken enumerator path. Without the pre-filter count the two
|
|
12
|
+
* are indistinguishable and both read as a quiet night.
|
|
13
|
+
*/
|
|
14
|
+
import { chunkTranscript, claudeProjectsRoot, enumerateSessionJsonls, readSidecarAccountId, sessionLastWriteIso, } from "../lib/session-jsonl.js";
|
|
15
|
+
const DEFAULT_SESSION_LIMIT = 5;
|
|
16
|
+
const MAX_SESSION_LIMIT = 25;
|
|
17
|
+
const DEFAULT_CHUNK_TURNS = 40;
|
|
18
|
+
const MAX_CHUNK_TURNS = 200;
|
|
19
|
+
export async function learningsScan(params) {
|
|
20
|
+
const { accountId } = params;
|
|
21
|
+
if (!accountId)
|
|
22
|
+
throw new Error("learnings-scan: accountId is required");
|
|
23
|
+
const sinceIso = params.sinceIso ?? null;
|
|
24
|
+
const sinceMs = sinceIso === null ? null : Date.parse(sinceIso);
|
|
25
|
+
if (sinceMs !== null && Number.isNaN(sinceMs)) {
|
|
26
|
+
// Fail rather than fall through to "scan everything": a malformed
|
|
27
|
+
// watermark that silently widened the scan would re-process every session
|
|
28
|
+
// and amplify every learning toward 1.0.
|
|
29
|
+
throw new Error(`learnings-scan: sinceIso is not a valid ISO timestamp: ${sinceIso}`);
|
|
30
|
+
}
|
|
31
|
+
const sessionLimit = Math.min(Math.max(1, params.sessionLimit ?? DEFAULT_SESSION_LIMIT), MAX_SESSION_LIMIT);
|
|
32
|
+
const sessionOffset = Math.max(0, params.sessionOffset ?? 0);
|
|
33
|
+
const chunkTurns = Math.min(Math.max(1, params.chunkTurns ?? DEFAULT_CHUNK_TURNS), MAX_CHUNK_TURNS);
|
|
34
|
+
const chunkIndex = Math.max(0, params.chunkIndex ?? 0);
|
|
35
|
+
// Throws when CLAUDE_CONFIG_DIR is absent. Never defaulted.
|
|
36
|
+
const projectsRoot = claudeProjectsRoot();
|
|
37
|
+
const { files, candidateFiles, skipped } = enumerateSessionJsonls(projectsRoot);
|
|
38
|
+
const eligible = files
|
|
39
|
+
.filter((f) => !f.isSubagent && !f.archived)
|
|
40
|
+
.filter((f) => readSidecarAccountId(f.path) === accountId)
|
|
41
|
+
.map((f) => ({ file: f, lastWriteIso: sessionLastWriteIso(f.path) }))
|
|
42
|
+
.filter((e) => e.lastWriteIso !== null)
|
|
43
|
+
.filter((e) => sinceMs === null || Date.parse(e.lastWriteIso) > sinceMs)
|
|
44
|
+
.sort((a, b) => a.lastWriteIso.localeCompare(b.lastWriteIso));
|
|
45
|
+
const page = eligible.slice(sessionOffset, sessionOffset + sessionLimit);
|
|
46
|
+
const rows = page.map(({ file, lastWriteIso }) => {
|
|
47
|
+
const { body, totalChunks } = chunkTranscript(file.path, chunkIndex, chunkTurns);
|
|
48
|
+
return { sessionId: file.sessionId, lastWriteIso, chunkIndex, totalChunks, body };
|
|
49
|
+
});
|
|
50
|
+
process.stderr.write(`[learnings] op=scan accountId=${accountId} sinceIso=${sinceIso ?? "-"} ` +
|
|
51
|
+
`candidateFiles=${candidateFiles} sessions=${eligible.length} skipped=${skipped} ` +
|
|
52
|
+
`returned=${rows.length}\n`);
|
|
53
|
+
return { candidateFiles, sessions: eligible.length, skipped, sinceIso, rows };
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=learnings-scan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"learnings-scan.js","sourceRoot":"","sources":["../../src/tools/learnings-scan.ts"],"names":[],"mappings":"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"}
|
|
@@ -30,6 +30,7 @@ When loading this reference, confirm which schema files were consulted by noting
|
|
|
30
30
|
| Address | `PostalAddress` | `schema:PostalAddress` | — | `streetAddress`, `addressLocality`, `postalCode` |
|
|
31
31
|
| Task | `Task` | `schema:Action` (semantics) | — | `accountId`, `taskId`, `name`, `status`, `priority` |
|
|
32
32
|
| UserProfile | `UserProfile` | platform-native | — | `accountId`, `userId` |
|
|
33
|
+
| Learning | `Learning` | platform-native | — | `accountId`, `learningId`, `title`, `body`, `confidence`, `observedAt`, `observationCount`, `sourceSessionIds` |
|
|
33
34
|
| Lead | `Lead` | `cdm:Lead` | CDM only | `accountId`, `subject`, `customer` (id), `status` |
|
|
34
35
|
| Case | `Case` | `cdm:Case` | CDM only | `accountId`, `title`, `customer` (id), `status`, `priority` |
|
|
35
36
|
| OpeningHours | `OpeningHoursSpecification` | `schema:OpeningHoursSpecification` | Schema.org JSON-LD | `accountId`, `dayOfWeek`, `opens`, `closes` |
|
|
@@ -66,6 +67,8 @@ When loading this reference, confirm which schema files were consulted by noting
|
|
|
66
67
|
|
|
67
68
|
**DefinedTerm** covers reusable concepts, learnings, and glossary entries — e.g. project retrospective learnings (`name` is a short handle, `description` is the full learning). Additional properties like `category` are encouraged but not required.
|
|
68
69
|
|
|
70
|
+
**Learning** is not a DefinedTerm and the two must not be conflated. A `DefinedTerm` is a glossary entry: it is written once, carries no confidence, never decays, and is retrieved by search. A `Learning` is a claim with a lifecycle — it is created at `0.5`, amplified each time a later session re-observes it, decremented and stamped `contradictedAt` when a session disputes it, and decayed linearly once nobody has re-observed it for fourteen days. Write a `DefinedTerm` when the operator is defining what a thing *is*; write a `Learning` when a session established that something *holds*, and you expect future sessions to confirm or dispute it. A `Learning` hangs off the account's `UserProfile` by `(:UserProfile)-[:HAS_LEARNING]->(:Learning)`, the same parenting `Preference` uses. `confidence` is a float in `[0,1]`; `observedAt` is an ISO string; `observationCount` is the number of distinct sessions that produced the claim; `sourceSessionIds` is the list of those session ids. `contradictedAt` is optional and stamped only when a later claim contradicted the learning. Only the `title` reaches a system prompt; the `body` is fetched on demand through the `learnings-recall` skill.
|
|
71
|
+
|
|
69
72
|
**Position** carries a single role held by a `UserProfile` at an `Organization` — its title, dates, and per-role properties (e.g. achievement bullets in `description`, `endDate` for a finished role). Each role is its own node so it can carry its own structure; multiple Positions per UserProfile (career history); one Organization may host many Positions across many UserProfiles. Written by the `document-ingest` skill when a CV section classifies as `Position`. The natural pattern is `(:UserProfile)-[:HAS_POSITION]->(:Position)-[:AT]->(:Organization)` — never set the employer name as a property on Position; always create the AT edge to the (MERGEd) Organization.
|
|
70
73
|
|
|
71
74
|
**`authorshipMode` on content labels.** Every `:KnowledgeDocument`, `:ConversationArchive` (every chat transcript parent, including email threads under `source:'email'`), `:Message`, `:SocialPost`, and `:Section` write carries an `authorshipMode` enum drawn from `human-only | human-led-agent-assisted | agent-led-human-reviewed | agent-only | unknown`. The value records who actually wrote the content body, not who triggered the write. `human-only` means every word of the body came from the operator; `human-led-agent-assisted` is operator-authored with light agent polish (the operator chose every claim and structure); `agent-led-human-reviewed` is agent-drafted then operator-edited before send; `agent-only` is pure agent output the operator did not touch. The voice-mirror distillation walks the corpus where `authorshipMode IN ['human-only', 'human-led-agent-assisted']` — both modes carry voice signal. `:ConversationArchive` `:Section` chunks (WhatsApp / Telegram / Signal / Granola / Otter / Circleback exports ingested under `(:ConversationArchive)-[:HAS_SECTION]->(:Section)`) are first-class voice corpus and the highest-leverage signal source because chat captures the unguarded voice in a way email rarely does. Legacy nodes default to `unknown` until reclassified via the voice-mirror backfill flow. The per-label authorshipMode index on `:Section` covers transcript chunks without a secondary label. Writers that cannot determine the mode (e.g. bulk archive imports) write `unknown` rather than guessing.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: learnings-recall
|
|
3
|
+
description: "Fetch the full text of a learning whose title is already in your system prompt. Activate when the current turn is about a topic named in the learnings index, when you are about to repeat work a past session already resolved, or when the operator says 'what did we learn about X', 'have we hit this before', 'did we solve this already', or 'check the learnings'."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Learnings recall
|
|
7
|
+
|
|
8
|
+
Your system prompt carries a list of learning **titles**, not their text. A
|
|
9
|
+
title is a handle, not the knowledge. When the turn in front of you is about
|
|
10
|
+
one of those titles, fetch the body before acting.
|
|
11
|
+
|
|
12
|
+
Call `learnings-recall` with the title as written in the index, or with a
|
|
13
|
+
`learningId` if you have one. It returns the full body, the confidence, when it
|
|
14
|
+
was last observed, how many sessions produced it, and whether a later session
|
|
15
|
+
disputed it.
|
|
16
|
+
|
|
17
|
+
Judge what comes back. A learning at low confidence, or one carrying a
|
|
18
|
+
`contradictedAt` stamp, is evidence that something was once believed and later
|
|
19
|
+
disputed — say so rather than acting on it as settled. A learning observed
|
|
20
|
+
across many sessions is stronger than one seen once, and `observationCount`
|
|
21
|
+
tells you which you have.
|
|
22
|
+
|
|
23
|
+
Do not call this speculatively on every turn. The index exists so you can tell,
|
|
24
|
+
cheaply, whether a body is worth fetching. Fetching a body nobody needed is the
|
|
25
|
+
cost the title/body split was built to avoid.
|
|
26
|
+
|
|
27
|
+
If the turn matches no indexed title, this skill has nothing to add. Carry on.
|
|
@@ -51,7 +51,7 @@ function textResult(text, isError = false) {
|
|
|
51
51
|
// ─── whatsapp-login-start ──────────────────────────────────────────────
|
|
52
52
|
lifelineTool(server, "whatsapp-login-start", 'Start WhatsApp pairing. Requires the operator\'s WhatsApp phone number (E.164). Returns an 8-character pairing code the operator types into WhatsApp → Linked Devices → Link a Device → "Link with phone number instead." No QR image — this surface is an async chat where a rotating QR cannot be scanned in time.', {
|
|
53
53
|
phone: z.string().describe("WhatsApp phone number in E.164 (e.g. +441234567890) — the number being linked"),
|
|
54
|
-
accountId: z.string().optional().describe('Account ID
|
|
54
|
+
accountId: z.string().optional().describe('Account ID. Defaults to the calling session’s own account; the house admin may pass another account id explicitly.'),
|
|
55
55
|
force: z.boolean().optional().describe("Force re-link even if already connected"),
|
|
56
56
|
}, async ({ phone, accountId, force }) => {
|
|
57
57
|
try {
|
|
@@ -66,7 +66,7 @@ lifelineTool(server, "whatsapp-login-start", 'Start WhatsApp pairing. Requires t
|
|
|
66
66
|
});
|
|
67
67
|
// ─── whatsapp-login-wait ───────────────────────────────────────────────
|
|
68
68
|
lifelineTool(server, "whatsapp-login-wait", "Wait for the operator to enter the WhatsApp pairing code and the connection to establish. Call after whatsapp-login-start. Polls for up to 60 seconds; the pairing code stays valid and the login stays alive across polls, so call again to keep waiting.", {
|
|
69
|
-
accountId: z.string().optional().describe('Account ID
|
|
69
|
+
accountId: z.string().optional().describe('Account ID. Defaults to the calling session’s own account; the house admin may pass another account id explicitly.'),
|
|
70
70
|
timeoutMs: z.number().optional().describe("Max wait time in ms (default: 60000)"),
|
|
71
71
|
}, async ({ accountId, timeoutMs }) => {
|
|
72
72
|
try {
|
|
@@ -151,7 +151,7 @@ lifelineTool(server, "whatsapp-activity", "Get WhatsApp channel activity — mes
|
|
|
151
151
|
});
|
|
152
152
|
// ─── whatsapp-disconnect ───────────────────────────────────────────────
|
|
153
153
|
lifelineTool(server, "whatsapp-disconnect", "Disconnect a WhatsApp account. Closes the Baileys WebSocket connection cleanly.", {
|
|
154
|
-
accountId: z.string().optional().describe('Account ID to disconnect
|
|
154
|
+
accountId: z.string().optional().describe('Account ID to disconnect. Defaults to the calling session’s own account; the house admin may pass another account id explicitly.'),
|
|
155
155
|
}, async ({ accountId }) => {
|
|
156
156
|
try {
|
|
157
157
|
const result = await callApi("whatsapp-disconnect", "/api/whatsapp/disconnect", "POST", { accountId }, callerScopeHeaders(process.env));
|
|
@@ -206,7 +206,7 @@ lifelineTool(server, "whatsapp-config", [
|
|
|
206
206
|
mode: z.enum(["active", "passive"]).optional().describe("Account-manager mode for add-account-manager (default 'active'). 'active' = a full admin session that can reply and manage the sub-account. 'passive' = a constrained requirement-intake agent: it interprets each message, files exactly one task scoped to the sub-account, and never replies."),
|
|
207
207
|
slug: z.string().optional().describe("Agent slug (required for set-public-agent)"),
|
|
208
208
|
fields: z.string().optional().describe('JSON string of config fields to update (required for update-config, e.g. \'{"dmPolicy":"open","allowFrom":["*"]}\')'),
|
|
209
|
-
accountId: z.string().optional().describe('Account ID
|
|
209
|
+
accountId: z.string().optional().describe('Account ID. Defaults to the calling session’s own account; the house admin may pass another account id explicitly. Used by list-groups and set-dispatch-inbound.'),
|
|
210
210
|
dispatchInbound: z.boolean().optional().describe("For set-dispatch-inbound: false makes the account an observe socket (stored + readable, never dispatched); true restores agent dispatch."),
|
|
211
211
|
}, async ({ action, phone, managesAccount, mode, slug, fields, accountId, dispatchInbound }) => {
|
|
212
212
|
try {
|