EXTENSION for TreeOS
water
The full picture at any position. Combines perspective, codebook stats, memory, gaps, flow, and evolution into one view. What is flowing through this node right now? Perspective shows what it drinks. Memory shows who it has talked to. Gaps show what it is missing. Flow shows recent signals. Codebook shows compression stats. water land gives the operator dashboard: pulse health plus aggregated gaps plus .flow stats plus peer health. Every extension contributes one piece. water assembles the picture at any position. The tree knows its own hydration.
v1.0.0 by TreeOS Site 0 downloads 3 files 252 lines 8.2 KB published 48d ago
treeos ext install water
View changelog

Manifest

Provides

  • routes
  • 1 CLI commands

Requires

  • models: Node

Optional

  • extensions: perspective-filter, codebook, long-memory, gap-detection, flow, pulse, evolution
SHA256: 29cdedd7b33f5322b0ed6833d974bfd49bcf3c515d18ec28d34e1d0bb828bc81

Dependents

1 package depend on this

PackageTypeRelationship
treeos v1.0.1osstandalone

CLI Commands

CommandMethodDescription
waterGETThe full picture. No action shows node hydration. Actions: land.
water landGETLand-wide dashboard. Pulse, gaps, flow, peers.

Source Code

1export async function init(core) {
2  const { default: router } = await import("./routes.js");
3  return { router };
4}
5
1export default {
2  name: "water",
3  version: "1.0.0",
4  builtFor: "TreeOS",
5  description:
6    "The full picture at any position. Combines perspective, codebook stats, memory, gaps, " +
7    "flow, and evolution into one view. What is flowing through this node right now? Perspective " +
8    "shows what it drinks. Memory shows who it has talked to. Gaps show what it is missing. " +
9    "Flow shows recent signals. Codebook shows compression stats. water land gives the operator " +
10    "dashboard: pulse health plus aggregated gaps plus .flow stats plus peer health. Every " +
11    "extension contributes one piece. water assembles the picture at any position. The tree knows " +
12    "its own hydration.",
13
14  needs: {
15    models: ["Node"],
16  },
17
18  optional: {
19    extensions: [
20      "perspective-filter", "codebook", "long-memory",
21      "gap-detection", "flow", "pulse", "evolution",
22    ],
23  },
24
25  provides: {
26    models: {},
27    routes: "./routes.js",
28    tools: false,
29    jobs: false,
30    orchestrator: false,
31    energyActions: {},
32    sessionTypes: {},
33    env: [],
34
35    cli: [
36      {
37        command: "water [action]", scope: ["tree"],
38        description: "The full picture. No action shows node hydration. Actions: land.",
39        method: "GET",
40        endpoint: "/node/:nodeId/water",
41        subcommands: {
42          "land": {
43            method: "GET",
44            endpoint: "/water/land",
45            description: "Land-wide dashboard. Pulse, gaps, flow, peers.",
46          },
47        },
48      },
49    ],
50
51    hooks: {
52      fires: [],
53      listens: [],
54    },
55  },
56};
57
1import express from "express";
2import authenticate from "../../seed/middleware/authenticate.js";
3import { sendOk, sendError, ERR } from "../../seed/protocol.js";
4import Node from "../../seed/models/node.js";
5import { getExtension } from "../loader.js";
6import log from "../../seed/log.js";
7
8const router = express.Router();
9
10// GET /node/:nodeId/water - full picture at this position
11router.get("/node/:nodeId/water", authenticate, async (req, res) => {
12  try {
13    const { nodeId } = req.params;
14    const node = await Node.findById(nodeId).select("name metadata parent systemRole").lean();
15    if (!node) return sendError(res, 404, ERR.NODE_NOT_FOUND, "Node not found");
16
17    const meta = node.metadata instanceof Map
18      ? Object.fromEntries(node.metadata)
19      : (node.metadata || {});
20
21    const picture = { nodeId, nodeName: node.name };
22
23    // Perspective: what this node drinks
24    const perspectiveExt = getExtension("perspective-filter");
25    if (perspectiveExt?.exports?.resolvePerspective) {
26      try {
27        const perspective = await perspectiveExt.exports.resolvePerspective(node);
28        picture.perspective = perspective || { accept: [], reject: [] };
29      } catch (err) { log.debug("Water", "perspective section failed:", err.message); }
30    }
31
32    // Memory: who this node has talked to
33    const memoryExt = getExtension("long-memory");
34    if (memoryExt?.exports?.getMemory) {
35      try {
36        const memory = await memoryExt.exports.getMemory(nodeId);
37        if (memory && memory.totalInteractions > 0) {
38          picture.memory = {
39            lastSeen: memory.lastSeen,
40            lastStatus: memory.lastStatus,
41            totalInteractions: memory.totalInteractions,
42            recentSources: (memory.connections || []).slice(-5).map((c) => c.sourceId),
43          };
44        }
45      } catch (err) { log.debug("Water", "memory section failed:", err.message); }
46    }
47
48    // Codebook: compression stats
49    if (meta.codebook) {
50      const entries = {};
51      for (const [uid, data] of Object.entries(meta.codebook)) {
52        if (data?.dictionary && Object.keys(data.dictionary).length > 0) {
53          entries[uid] = {
54            dictionarySize: Object.keys(data.dictionary).length,
55            notesSinceCompression: data.notesSinceCompression || 0,
56            lastCompressed: data.lastCompressed,
57          };
58        }
59      }
60      if (Object.keys(entries).length > 0) picture.codebook = entries;
61    }
62
63    // Gaps: what this node is missing
64    if (Array.isArray(meta.gaps) && meta.gaps.length > 0) {
65      picture.gaps = meta.gaps
66        .filter((g) => g.count > 0)
67        .sort((a, b) => b.count - a.count)
68        .map((g) => ({ namespace: g.namespace, count: g.count }));
69    }
70
71    // Flow: recent signals
72    const flowExt = getExtension("flow");
73    if (flowExt?.exports?.getFlowForPosition) {
74      try {
75        const flow = await flowExt.exports.getFlowForPosition(nodeId, 10);
76        picture.flow = {
77          scope: flow.scope,
78          recentSignals: Object.keys(flow.results).length,
79        };
80      } catch (err) { log.debug("Water", "flow section failed:", err.message); }
81    }
82
83    // Evolution: fitness
84    if (meta.evolution) {
85      picture.fitness = {
86        notesWritten: meta.evolution.notesWritten || 0,
87        visits: meta.evolution.visits || 0,
88        cascades: (meta.evolution.cascadesOriginated || 0) + (meta.evolution.cascadesReceived || 0),
89        lastActivity: meta.evolution.lastActivity,
90      };
91    }
92
93    // Contradictions
94    if (Array.isArray(meta.contradictions)) {
95      const active = meta.contradictions.filter((c) => c.status === "active");
96      if (active.length > 0) picture.contradictions = active.length;
97    }
98
99    // Compression
100    if (meta.compress?.essence) {
101      picture.compressed = { status: meta.compress.status, hasSummary: true };
102    }
103
104    sendOk(res, picture);
105  } catch (err) {
106    sendError(res, 500, ERR.INTERNAL, err.message);
107  }
108});
109
110// GET /water/land - land-wide dashboard
111router.get("/water/land", authenticate, async (req, res) => {
112  try {
113    const picture = {};
114
115    // Pulse: land health
116    const pulseExt = getExtension("pulse");
117    if (pulseExt?.exports?.getLatestSnapshot) {
118      try {
119        const snapshot = await pulseExt.exports.getLatestSnapshot();
120        if (snapshot) {
121          picture.health = {
122            failureRate: snapshot.failureRate,
123            elevated: snapshot.elevated,
124            signals: snapshot.signals,
125            results: snapshot.results,
126            lastUpdated: snapshot.timestamp,
127            peers: snapshot.peers,
128          };
129        }
130      } catch (err) { log.debug("Water", "pulse section failed:", err.message); }
131    }
132
133    // Gaps: land-wide aggregation
134    const gapExt = getExtension("gap-detection");
135    if (gapExt?.exports?.getGaps) {
136      try {
137        const roots = await Node.find({ rootOwner: { $ne: null }, systemRole: null })
138          .select("_id").lean();
139
140        const { getDescendantIds } = await import("../../seed/tree/treeFetch.js");
141        const aggregated = {};
142
143        for (const root of roots.slice(0, 50)) { // cap to prevent overload
144          const nodeIds = await getDescendantIds(root._id);
145          for (const nid of nodeIds) {
146            const gaps = await gapExt.exports.getGaps(nid);
147            for (const gap of gaps) {
148              if (!aggregated[gap.namespace]) aggregated[gap.namespace] = 0;
149              aggregated[gap.namespace] += gap.count;
150            }
151          }
152        }
153
154        const sorted = Object.entries(aggregated)
155          .sort((a, b) => b[1] - a[1])
156          .map(([namespace, count]) => ({ namespace, count }));
157
158        if (sorted.length > 0) picture.gaps = sorted;
159      } catch (err) { log.debug("Water", "gaps section failed:", err.message); }
160    }
161
162    // Flow stats
163    const flowNode = await Node.findOne({ systemRole: "flow" }).select("_id").lean();
164    if (flowNode) {
165      const partitions = await Node.find({ parent: flowNode._id })
166        .select("name metadata").sort({ name: -1 }).limit(7).lean();
167
168      const today = new Date().toISOString().slice(0, 10);
169      const stats = partitions.map((p) => {
170        const results = p.metadata instanceof Map
171          ? p.metadata.get("results") || {}
172          : p.metadata?.results || {};
173        return { date: p.name, signals: Object.keys(results).length };
174      });
175
176      picture.flow = {
177        partitions: partitions.length,
178        recentDays: stats,
179        todaySignals: stats.find((s) => s.date === today)?.signals || 0,
180      };
181    }
182
183    sendOk(res, picture);
184  } catch (err) {
185    sendError(res, 500, ERR.INTERNAL, err.message);
186  }
187});
188
189export default router;
190
0 stars
0 flags
React from the CLI: treeos ext star water

Comments

Loading comments...

Post comments from the CLI: treeos ext comment water "your comment"
Max 3 comments per extension. One star and one flag per user.