4807aa1b462ee26cefca465597c216d82988eab3d64a373c2ab691f92b0917b7beforeNodeDeleteafterMetadataWriteenrichContext1import log from "../../seed/log.js";
2import Node from "../../seed/models/node.js";
3const MAX_DEPARTED = 50;
4
5export async function init(core) {
6 // beforeNodeDelete: write one line to the parent before the child is gone
7 core.hooks.register("beforeNodeDelete", async ({ node, nodeId }) => {
8 if (!node && !nodeId) return;
9
10 try {
11 const doc = node || await Node.findById(nodeId).select("_id name parent metadata").lean();
12 if (!doc || !doc.parent) return;
13 if (doc.systemRole) return; // don't memorialize system nodes
14
15 const parentDoc = await Node.findById(doc.parent);
16 if (!parentDoc) return;
17
18 const meta = core.metadata.getExtMeta(parentDoc, "remember") || {};
19 if (!meta.departed) meta.departed = [];
20
21 // Check if prune absorbed essence
22 const nodeMeta = doc.metadata instanceof Map
23 ? Object.fromEntries(doc.metadata)
24 : (doc.metadata || {});
25 const wasPruned = !!nodeMeta.prune;
26 const hadEssence = !!nodeMeta.compress?.essence;
27
28 let note = "";
29 if (wasPruned && hadEssence) {
30 note = "Pruned. Essence preserved in parent.";
31 } else if (wasPruned) {
32 note = "Pruned. Gone quiet.";
33 } else {
34 note = "Removed.";
35 }
36
37 meta.departed.push({
38 name: doc.name || "unnamed",
39 pruned: wasPruned,
40 date: new Date().toISOString().slice(0, 10),
41 note,
42 });
43
44 // Cap the memorial
45 if (meta.departed.length > MAX_DEPARTED) {
46 meta.departed = meta.departed.slice(-MAX_DEPARTED);
47 }
48
49 await core.metadata.setExtMeta(parentDoc, "remember", meta);
50 } catch (err) {
51 log.debug("Remember", `beforeNodeDelete memorial failed: ${err.message}`);
52 }
53 }, "remember");
54
55 // Listen for split events via afterMetadataWrite on the split extension
56 // When split writes history to a root, we can detect it and memorialize
57 core.hooks.register("afterMetadataWrite", async ({ nodeId, extName, data }) => {
58 if (extName !== "split") return;
59 if (!data?.history) return;
60
61 try {
62 const latest = Array.isArray(data.history) ? data.history[data.history.length - 1] : null;
63 if (!latest || !latest.branchName) return;
64
65 const node = await Node.findById(nodeId);
66 if (!node) return;
67
68 const meta = core.metadata.getExtMeta(node, "remember") || {};
69 if (!meta.departed) meta.departed = [];
70
71 // Don't duplicate if already memorialized
72 const already = meta.departed.some(
73 d => d.name === latest.branchName && d.splitTo
74 );
75 if (already) return;
76
77 meta.departed.push({
78 name: latest.branchName,
79 splitTo: "own root",
80 date: new Date().toISOString().slice(0, 10),
81 note: "Outgrew this tree. Became its own.",
82 });
83
84 if (meta.departed.length > MAX_DEPARTED) {
85 meta.departed = meta.departed.slice(-MAX_DEPARTED);
86 }
87
88 await core.metadata.setExtMeta(node, "remember", meta);
89 } catch (err) {
90 log.debug("Remember", `afterMetadataWrite split memorial failed: ${err.message}`);
91 }
92 }, "remember");
93
94 // enrichContext: the tree knows what used to be here
95 core.hooks.register("enrichContext", async ({ context, node, meta }) => {
96 const remember = meta?.remember;
97 if (!remember?.departed?.length) return;
98
99 context.departed = remember.departed.map(d => ({
100 name: d.name,
101 date: d.date,
102 note: d.note,
103 }));
104 }, "remember");
105
106 log.verbose("Remember", "Remember loaded");
107
108 return {};
109}
1101export default {
2 name: "remember",
3 version: "1.0.1",
4 builtFor: "TreeOS",
5 description:
6 "The tree remembers what it lost. When a child is pruned, split off, or retired, " +
7 "remember writes one line to the parent. Not the content. Not the structure. Just " +
8 "acknowledgment that something was here and now it isn't. The AI at that position " +
9 "knows what used to be below it without being told. It doesn't bring it up unprompted. " +
10 "But if you ask what used to be here, it can tell you. The tree's memorial.",
11
12 needs: {
13 services: ["hooks", "metadata"],
14 models: ["Node"],
15 },
16
17 optional: {
18 extensions: ["prune", "split"],
19 },
20
21 provides: {
22 models: {},
23 routes: false,
24 tools: false,
25 jobs: false,
26 orchestrator: false,
27 energyActions: {},
28 sessionTypes: {},
29
30 hooks: {
31 fires: [],
32 listens: ["beforeNodeDelete", "afterMetadataWrite", "enrichContext"],
33 },
34
35 cli: [],
36 },
37};
38
| Version | Published | Downloads |
|---|---|---|
| 1.0.1 | 38d ago | 0 |
| 1.0.0 | 48d ago | 0 |
treeos ext star remember
Post comments from the CLI: treeos ext comment remember "your comment"
Max 3 comments per extension. One star and one flag per user.
Loading comments...