hyperstack-core 1.2.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,288 +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
- * Auto-extract cards from raw text (conversation, project description).
182
- * Finds preferences, people, decisions, tech stack mentions.
183
- * Zero LLM cost. Best for onboarding.
184
- */
185
- async hs_ingest({ text }) {
186
- if (!text || text.trim().length === 0) {
187
- return { text: "Error: text parameter required", cards: [] };
188
- }
189
-
190
- const result = await client.ingest(text);
191
- const cards = result.cards || [];
192
-
193
- return {
194
- text: `✅ Created ${cards.length} cards from ${text.length} chars of text:\n` +
195
- cards.map(c => ` [${c.slug}] ${c.title} (${c.cardType})`).join("\n"),
196
- cards,
197
- count: cards.length,
198
- };
199
- },
200
- },
201
-
202
- /**
203
- * Hook: called when agent session starts.
204
- * Registers the agent and loads relevant context.
205
- */
206
- /**
207
- * Check for cards directed at this agent from other agents.
208
- * Enables cross-agent coordination via shared memory.
209
- */
210
- async hs_inbox({ since } = {}) {
211
- const result = await client.inbox({ since });
212
- const cards = result.cards || [];
213
- if (!cards.length) {
214
- return { text: `No messages for agent "${agentId}".`, cards: [] };
215
- }
216
- return {
217
- text: `${cards.length} message(s) for "${agentId}":\n` +
218
- cards.map(c => ` [${c.slug}] ${c.title}${c.body ? `\n ${c.body.slice(0, 150)}` : ""}`).join("\n"),
219
- cards,
220
- };
221
- },
222
-
223
- /**
224
- * Register a webhook for real-time notifications. Requires Team plan.
225
- */
226
- async hs_webhook({ url, events, secret } = {}) {
227
- if (!url) return { text: "Error: url required" };
228
- const result = await client.registerWebhook({
229
- url,
230
- events: events ? events.split(",").map(e => e.trim()) : ["*"],
231
- secret,
232
- });
233
- if (result.error) return { text: `Webhook error: ${result.error}` };
234
- return { text: `Webhook registered for agent "${agentId}".\nURL: ${url}\nID: ${result.id}` };
235
- },
236
-
237
- /**
238
- * List all registered webhooks for this account.
239
- */
240
- async hs_webhooks() {
241
- const result = await client.listWebhooks();
242
- const hooks = result.webhooks || [];
243
- if (!hooks.length) return { text: "No webhooks registered." };
244
- return {
245
- text: `${hooks.length} webhook(s):\n` +
246
- hooks.map(h => ` [${h.agentId}] ${h.url} — events: ${h.events.join(", ")}`).join("\n"),
247
- hooks,
248
- };
249
- },
250
- },
251
-
252
- async onSessionStart({ agentName, agentRole }) {
253
- // Register this agent in the graph
254
- await client.registerAgent({
255
- id: agentId,
256
- name: agentName || agentId,
257
- role: agentRole || "OpenClaw agent",
258
- });
259
-
260
- // Load recent context for this agent
261
- const context = await client.search(`agent:${agentId}`);
262
- return {
263
- cards: context.results || [],
264
- text: `HyperStack: ${(context.results || []).length} cards loaded for agent "${agentId}"`,
265
- };
266
- },
267
-
268
- /**
269
- * Hook: called when agent session ends.
270
- * Good place to save working state.
271
- */
272
- async onSessionEnd({ summary }) {
273
- if (summary) {
274
- await client.store({
275
- slug: `session-${agentId}-${Date.now()}`,
276
- title: `Session summary (${agentId})`,
277
- body: summary.slice(0, 500),
278
- cardType: "event",
279
- stack: "general",
280
- keywords: ["session", "summary"],
281
- });
282
- }
283
- },
284
- };
285
- }
286
-
287
- export { createOpenClawAdapter };
288
- 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;