hyperstack-core 1.3.0 → 1.5.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.
@@ -1,221 +1,221 @@
1
- /**
2
- * hyperstack-core/adapters/openclaw.js
3
- *
4
- * OpenClaw adapter for HyperStack.
5
- * Replaces GOALS.md / DECISIONS.md with typed graph memory.
6
- *
7
- * Usage in OpenClaw config (openclaw.json):
8
- * {
9
- * "skills": ["hyperstack-core/adapters/openclaw"],
10
- * "env": {
11
- * "HYPERSTACK_API_KEY": "hs_your_key"
12
- * }
13
- * }
14
- *
15
- * Or use programmatically:
16
- * import { createOpenClawAdapter } from "hyperstack-core/adapters/openclaw";
17
- * const adapter = createOpenClawAdapter({ agentId: "researcher" });
18
- */
19
-
20
- import { HyperStackClient } from "../src/client.js";
21
-
22
- /**
23
- * Create an OpenClaw-compatible adapter that provides tools
24
- * for multi-agent coordination via HyperStack.
25
- *
26
- * @param {object} opts
27
- * @param {string} opts.agentId — this agent's unique ID
28
- * @param {string} [opts.apiKey] — HyperStack API key
29
- * @param {string} [opts.workspace] — workspace slug
30
- */
31
- function createOpenClawAdapter(opts = {}) {
32
- const agentId = opts.agentId || process.env.OPENCLAW_AGENT_ID || "main";
33
-
34
- const client = new HyperStackClient({
35
- apiKey: opts.apiKey,
36
- workspace: opts.workspace,
37
- agentId,
38
- });
39
-
40
- return {
41
- client,
42
- agentId,
43
-
44
- /**
45
- * Tools that get exposed to the OpenClaw agent.
46
- * These can be called via OpenClaw's tool system.
47
- */
48
- tools: {
49
- /**
50
- * Search the shared knowledge graph.
51
- */
52
- async hs_search({ query }) {
53
- const result = await client.search(query);
54
- const cards = result.results || [];
55
- if (!cards.length) return { text: "No matching cards found." };
56
-
57
- return {
58
- text: cards.slice(0, 5).map(c => {
59
- let line = `[${c.slug}] ${c.title} (${c.cardType || "general"})`;
60
- if (c.body) line += `\n ${c.body.slice(0, 200)}`;
61
- if (c.links?.length) {
62
- line += `\n Links: ${c.links.map(l => `${l.relation}→${l.target}`).join(", ")}`;
63
- }
64
- return line;
65
- }).join("\n\n"),
66
- cards,
67
- };
68
- },
69
-
70
- /**
71
- * Store a card in the shared graph. Auto-tags with agent ID.
72
- */
73
- async hs_store({ slug, title, body, type, links, keywords }) {
74
- const parsedLinks = [];
75
- if (links) {
76
- // Accept "target:relation,target:relation" format
77
- for (const l of (typeof links === "string" ? links.split(",") : links)) {
78
- if (typeof l === "string") {
79
- const [target, relation] = l.trim().split(":");
80
- parsedLinks.push({ target: target.trim(), relation: (relation || "related").trim() });
81
- } else {
82
- parsedLinks.push(l);
83
- }
84
- }
85
- }
86
-
87
- const result = await client.store({
88
- slug,
89
- title,
90
- body: body || "",
91
- cardType: type || "general",
92
- keywords: typeof keywords === "string" ? keywords.split(",").map(k => k.trim()) : (keywords || []),
93
- links: parsedLinks,
94
- });
95
-
96
- return {
97
- text: `${result.updated ? "Updated" : "Created"} [${slug}]: ${title}`,
98
- result,
99
- };
100
- },
101
-
102
- /**
103
- * Record a decision with full provenance.
104
- */
105
- async hs_decide({ slug, title, rationale, affects, blocks }) {
106
- const result = await client.decide({
107
- slug,
108
- title,
109
- body: rationale,
110
- decidedBy: `agent-${agentId}`,
111
- affects: affects ? (typeof affects === "string" ? affects.split(",").map(s => s.trim()) : affects) : [],
112
- blocks: blocks ? (typeof blocks === "string" ? blocks.split(",").map(s => s.trim()) : blocks) : [],
113
- });
114
-
115
- return {
116
- text: `Decision recorded: [${slug}] ${title} (by ${agentId})`,
117
- result,
118
- };
119
- },
120
-
121
- /**
122
- * Check what blocks a task/card.
123
- */
124
- async hs_blockers({ slug }) {
125
- const result = await client.blockers(slug);
126
- const blockers = result.blockers || [];
127
-
128
- if (!blockers.length) {
129
- return { text: `Nothing blocks [${slug}].`, blockers: [] };
130
- }
131
-
132
- return {
133
- text: `${blockers.length} blocker(s) for [${slug}]:\n` +
134
- blockers.map(b => ` [${b.slug}] ${b.title || "?"}`).join("\n"),
135
- blockers,
136
- };
137
- },
138
-
139
- /**
140
- * Traverse the graph from a card.
141
- */
142
- async hs_graph({ from, depth, relation }) {
143
- const result = await client.graph(from, {
144
- depth: depth || 2,
145
- relation: relation || undefined,
146
- });
147
-
148
- const nodes = result.nodes || [];
149
- const edges = result.edges || [];
150
-
151
- if (!nodes.length) {
152
- return { text: `No graph found from [${from}].`, nodes: [], edges: [] };
153
- }
154
-
155
- let text = `Graph from [${from}]: ${nodes.length} nodes, ${edges.length} edges\n\n`;
156
- text += "Nodes:\n" + nodes.map(n =>
157
- ` [${n.slug}] ${n.title || "?"} (${n.cardType || "?"})`
158
- ).join("\n");
159
- text += "\n\nEdges:\n" + edges.map(e =>
160
- ` ${e.from} --${e.relation}--> ${e.to}`
161
- ).join("\n");
162
-
163
- return { text, nodes, edges };
164
- },
165
-
166
- /**
167
- * List all cards by this agent.
168
- */
169
- async hs_my_cards() {
170
- const result = await client.agentCards(agentId);
171
- const cards = result.results || [];
172
-
173
- return {
174
- text: `${cards.length} cards by agent "${agentId}":\n` +
175
- cards.map(c => ` [${c.slug}] ${c.title}`).join("\n"),
176
- cards,
177
- };
178
- },
179
- },
180
-
181
- /**
182
- * Hook: called when agent session starts.
183
- * Registers the agent and loads relevant context.
184
- */
185
- async onSessionStart({ agentName, agentRole }) {
186
- // Register this agent in the graph
187
- await client.registerAgent({
188
- id: agentId,
189
- name: agentName || agentId,
190
- role: agentRole || "OpenClaw agent",
191
- });
192
-
193
- // Load recent context for this agent
194
- const context = await client.search(`agent:${agentId}`);
195
- return {
196
- cards: context.results || [],
197
- text: `HyperStack: ${(context.results || []).length} cards loaded for agent "${agentId}"`,
198
- };
199
- },
200
-
201
- /**
202
- * Hook: called when agent session ends.
203
- * Good place to save working state.
204
- */
205
- async onSessionEnd({ summary }) {
206
- if (summary) {
207
- await client.store({
208
- slug: `session-${agentId}-${Date.now()}`,
209
- title: `Session summary (${agentId})`,
210
- body: summary.slice(0, 500),
211
- cardType: "event",
212
- stack: "general",
213
- keywords: ["session", "summary"],
214
- });
215
- }
216
- },
217
- };
218
- }
219
-
220
- export { createOpenClawAdapter };
221
- export default createOpenClawAdapter;
1
+ /**
2
+ * hyperstack-core/adapters/openclaw.js
3
+ *
4
+ * OpenClaw adapter for HyperStack.
5
+ * Replaces GOALS.md / DECISIONS.md with typed graph memory.
6
+ *
7
+ * Usage in OpenClaw config (openclaw.json):
8
+ * {
9
+ * "skills": ["hyperstack-core/adapters/openclaw"],
10
+ * "env": {
11
+ * "HYPERSTACK_API_KEY": "hs_your_key"
12
+ * }
13
+ * }
14
+ *
15
+ * Or use programmatically:
16
+ * import { createOpenClawAdapter } from "hyperstack-core/adapters/openclaw";
17
+ * const adapter = createOpenClawAdapter({ agentId: "researcher" });
18
+ */
19
+
20
+ import { HyperStackClient } from "../src/client.js";
21
+
22
+ /**
23
+ * Create an OpenClaw-compatible adapter that provides tools
24
+ * for multi-agent coordination via HyperStack.
25
+ *
26
+ * @param {object} opts
27
+ * @param {string} opts.agentId — this agent's unique ID
28
+ * @param {string} [opts.apiKey] — HyperStack API key
29
+ * @param {string} [opts.workspace] — workspace slug
30
+ */
31
+ function createOpenClawAdapter(opts = {}) {
32
+ const agentId = opts.agentId || process.env.OPENCLAW_AGENT_ID || "main";
33
+
34
+ const client = new HyperStackClient({
35
+ apiKey: opts.apiKey,
36
+ workspace: opts.workspace,
37
+ agentId,
38
+ });
39
+
40
+ return {
41
+ client,
42
+ agentId,
43
+
44
+ /**
45
+ * Tools that get exposed to the OpenClaw agent.
46
+ * These can be called via OpenClaw's tool system.
47
+ */
48
+ tools: {
49
+ /**
50
+ * Search the shared knowledge graph.
51
+ */
52
+ async hs_search({ query }) {
53
+ const result = await client.search(query);
54
+ const cards = result.results || [];
55
+ if (!cards.length) return { text: "No matching cards found." };
56
+
57
+ return {
58
+ text: cards.slice(0, 5).map(c => {
59
+ let line = `[${c.slug}] ${c.title} (${c.cardType || "general"})`;
60
+ if (c.body) line += `\n ${c.body.slice(0, 200)}`;
61
+ if (c.links?.length) {
62
+ line += `\n Links: ${c.links.map(l => `${l.relation}→${l.target}`).join(", ")}`;
63
+ }
64
+ return line;
65
+ }).join("\n\n"),
66
+ cards,
67
+ };
68
+ },
69
+
70
+ /**
71
+ * Store a card in the shared graph. Auto-tags with agent ID.
72
+ */
73
+ async hs_store({ slug, title, body, type, links, keywords }) {
74
+ const parsedLinks = [];
75
+ if (links) {
76
+ // Accept "target:relation,target:relation" format
77
+ for (const l of (typeof links === "string" ? links.split(",") : links)) {
78
+ if (typeof l === "string") {
79
+ const [target, relation] = l.trim().split(":");
80
+ parsedLinks.push({ target: target.trim(), relation: (relation || "related").trim() });
81
+ } else {
82
+ parsedLinks.push(l);
83
+ }
84
+ }
85
+ }
86
+
87
+ const result = await client.store({
88
+ slug,
89
+ title,
90
+ body: body || "",
91
+ cardType: type || "general",
92
+ keywords: typeof keywords === "string" ? keywords.split(",").map(k => k.trim()) : (keywords || []),
93
+ links: parsedLinks,
94
+ });
95
+
96
+ return {
97
+ text: `${result.updated ? "Updated" : "Created"} [${slug}]: ${title}`,
98
+ result,
99
+ };
100
+ },
101
+
102
+ /**
103
+ * Record a decision with full provenance.
104
+ */
105
+ async hs_decide({ slug, title, rationale, affects, blocks }) {
106
+ const result = await client.decide({
107
+ slug,
108
+ title,
109
+ body: rationale,
110
+ decidedBy: `agent-${agentId}`,
111
+ affects: affects ? (typeof affects === "string" ? affects.split(",").map(s => s.trim()) : affects) : [],
112
+ blocks: blocks ? (typeof blocks === "string" ? blocks.split(",").map(s => s.trim()) : blocks) : [],
113
+ });
114
+
115
+ return {
116
+ text: `Decision recorded: [${slug}] ${title} (by ${agentId})`,
117
+ result,
118
+ };
119
+ },
120
+
121
+ /**
122
+ * Check what blocks a task/card.
123
+ */
124
+ async hs_blockers({ slug }) {
125
+ const result = await client.blockers(slug);
126
+ const blockers = result.blockers || [];
127
+
128
+ if (!blockers.length) {
129
+ return { text: `Nothing blocks [${slug}].`, blockers: [] };
130
+ }
131
+
132
+ return {
133
+ text: `${blockers.length} blocker(s) for [${slug}]:\n` +
134
+ blockers.map(b => ` [${b.slug}] ${b.title || "?"}`).join("\n"),
135
+ blockers,
136
+ };
137
+ },
138
+
139
+ /**
140
+ * Traverse the graph from a card.
141
+ */
142
+ async hs_graph({ from, depth, relation }) {
143
+ const result = await client.graph(from, {
144
+ depth: depth || 2,
145
+ relation: relation || undefined,
146
+ });
147
+
148
+ const nodes = result.nodes || [];
149
+ const edges = result.edges || [];
150
+
151
+ if (!nodes.length) {
152
+ return { text: `No graph found from [${from}].`, nodes: [], edges: [] };
153
+ }
154
+
155
+ let text = `Graph from [${from}]: ${nodes.length} nodes, ${edges.length} edges\n\n`;
156
+ text += "Nodes:\n" + nodes.map(n =>
157
+ ` [${n.slug}] ${n.title || "?"} (${n.cardType || "?"})`
158
+ ).join("\n");
159
+ text += "\n\nEdges:\n" + edges.map(e =>
160
+ ` ${e.from} --${e.relation}--> ${e.to}`
161
+ ).join("\n");
162
+
163
+ return { text, nodes, edges };
164
+ },
165
+
166
+ /**
167
+ * List all cards by this agent.
168
+ */
169
+ async hs_my_cards() {
170
+ const result = await client.agentCards(agentId);
171
+ const cards = result.results || [];
172
+
173
+ return {
174
+ text: `${cards.length} cards by agent "${agentId}":\n` +
175
+ cards.map(c => ` [${c.slug}] ${c.title}`).join("\n"),
176
+ cards,
177
+ };
178
+ },
179
+ },
180
+
181
+ /**
182
+ * Hook: called when agent session starts.
183
+ * Registers the agent and loads relevant context.
184
+ */
185
+ async onSessionStart({ agentName, agentRole }) {
186
+ // Register this agent in the graph
187
+ await client.registerAgent({
188
+ id: agentId,
189
+ name: agentName || agentId,
190
+ role: agentRole || "OpenClaw agent",
191
+ });
192
+
193
+ // Load recent context for this agent
194
+ const context = await client.search(`agent:${agentId}`);
195
+ return {
196
+ cards: context.results || [],
197
+ text: `HyperStack: ${(context.results || []).length} cards loaded for agent "${agentId}"`,
198
+ };
199
+ },
200
+
201
+ /**
202
+ * Hook: called when agent session ends.
203
+ * Good place to save working state.
204
+ */
205
+ async onSessionEnd({ summary }) {
206
+ if (summary) {
207
+ await client.store({
208
+ slug: `session-${agentId}-${Date.now()}`,
209
+ title: `Session summary (${agentId})`,
210
+ body: summary.slice(0, 500),
211
+ cardType: "event",
212
+ stack: "general",
213
+ keywords: ["session", "summary"],
214
+ });
215
+ }
216
+ },
217
+ };
218
+ }
219
+
220
+ export { createOpenClawAdapter };
221
+ export default createOpenClawAdapter;