@hyperspell/openclaw-hyperspell 0.8.0 → 0.9.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.
- package/README.md +3 -1
- package/client.ts +140 -2
- package/commands/setup.ts +2 -0
- package/config.ts +15 -3
- package/hooks/emotional-state.ts +97 -0
- package/index.ts +7 -0
- package/openclaw.plugin.json +20 -0
- package/package.json +13 -3
package/README.md
CHANGED
|
@@ -7,9 +7,11 @@ OpenClaw plugin for [Hyperspell](https://hyperspell.com) - Context and memory fo
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
openclaw plugins install @hyperspell/openclaw-hyperspell
|
|
10
|
+
openclaw plugins install --dangerously-force-unsafe-install @hyperspell/openclaw-hyperspell
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
> **Note:** The `--dangerously-force-unsafe-install` flag is required because OpenClaw's automated install scanner detects standard operations that are essential to how the plugin works — opening your browser during setup, scheduling background jobs, and securely communicating with the Hyperspell API. These are normal, expected behaviors and the plugin does not execute arbitrary code or access data beyond what is needed to function. The source is fully open and auditable in this repository.
|
|
14
|
+
|
|
13
15
|
## Quick Start
|
|
14
16
|
|
|
15
17
|
Run the interactive setup wizard:
|
package/client.ts
CHANGED
|
@@ -27,7 +27,7 @@ export type Integration = {
|
|
|
27
27
|
id: string;
|
|
28
28
|
name: string;
|
|
29
29
|
provider: HyperspellSource;
|
|
30
|
-
icon: string;
|
|
30
|
+
icon: string | null;
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
export type Connection = {
|
|
@@ -37,6 +37,25 @@ export type Connection = {
|
|
|
37
37
|
provider: HyperspellSource;
|
|
38
38
|
};
|
|
39
39
|
|
|
40
|
+
export type EmotionalStateResponse = {
|
|
41
|
+
resourceId: string
|
|
42
|
+
summary: string
|
|
43
|
+
extractedAt: string
|
|
44
|
+
sessionId: string | null
|
|
45
|
+
relationshipId: string | null
|
|
46
|
+
status: string
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type EmotionalStateLatest = {
|
|
50
|
+
resourceId: string
|
|
51
|
+
summary: string
|
|
52
|
+
extractedAt: string
|
|
53
|
+
sessionId: string | null
|
|
54
|
+
relationshipId: string | null
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const API_BASE_URL = "https://api.hyperspell.com"
|
|
58
|
+
|
|
40
59
|
export class HyperspellClient {
|
|
41
60
|
private client: Hyperspell;
|
|
42
61
|
private config: HyperspellConfig;
|
|
@@ -52,6 +71,17 @@ export class HyperspellClient {
|
|
|
52
71
|
);
|
|
53
72
|
}
|
|
54
73
|
|
|
74
|
+
private rawHeaders(): Record<string, string> {
|
|
75
|
+
const headers: Record<string, string> = {
|
|
76
|
+
"Content-Type": "application/json",
|
|
77
|
+
Authorization: `Bearer ${this.config.apiKey}`,
|
|
78
|
+
};
|
|
79
|
+
if (this.config.userId) {
|
|
80
|
+
headers["X-As-User"] = this.config.userId;
|
|
81
|
+
}
|
|
82
|
+
return headers;
|
|
83
|
+
}
|
|
84
|
+
|
|
55
85
|
async search(
|
|
56
86
|
query: string,
|
|
57
87
|
options?: { limit?: number; sources?: HyperspellSource[]; after?: string; before?: string },
|
|
@@ -149,6 +179,7 @@ export class HyperspellClient {
|
|
|
149
179
|
score: doc.score ?? null,
|
|
150
180
|
url: (doc.metadata?.url as string | null) ?? null,
|
|
151
181
|
createdAt: (doc.metadata?.created_at as string | null) ?? null,
|
|
182
|
+
highlights: [],
|
|
152
183
|
}));
|
|
153
184
|
|
|
154
185
|
log.debugResponse("memories.search (with answer)", {
|
|
@@ -290,7 +321,12 @@ export class HyperspellClient {
|
|
|
290
321
|
session_id: options?.sessionId,
|
|
291
322
|
title: options?.title,
|
|
292
323
|
format: "openclaw",
|
|
293
|
-
|
|
324
|
+
// Cast: SDK 0.35 typing accepts only ["procedure" | "memory"], but the
|
|
325
|
+
// backend's mood extractor (hyperspell/hyperspell#581) accepts "mood".
|
|
326
|
+
// Remove this cast once the OpenAPI spec is updated.
|
|
327
|
+
extract: (options?.extract ?? ["procedure"]) as Array<
|
|
328
|
+
"procedure" | "memory"
|
|
329
|
+
>,
|
|
294
330
|
metadata: {
|
|
295
331
|
...options?.metadata,
|
|
296
332
|
openclaw_source: "agent_end",
|
|
@@ -319,4 +355,106 @@ export class HyperspellClient {
|
|
|
319
355
|
log.debugResponse("connections.list", { count: connections.length });
|
|
320
356
|
return connections;
|
|
321
357
|
}
|
|
358
|
+
|
|
359
|
+
// -- Emotional State (raw fetch -- not in public SDK) -----------------------
|
|
360
|
+
|
|
361
|
+
async storeEmotionalState(
|
|
362
|
+
conversation: string,
|
|
363
|
+
options?: {
|
|
364
|
+
sessionId?: string;
|
|
365
|
+
relationshipId?: string;
|
|
366
|
+
metadata?: Record<string, string | number | boolean>;
|
|
367
|
+
},
|
|
368
|
+
): Promise<EmotionalStateResponse> {
|
|
369
|
+
log.debugRequest("emotional-state.store", {
|
|
370
|
+
conversationLength: conversation.length,
|
|
371
|
+
relationshipId: options?.relationshipId,
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
const body: Record<string, unknown> = { conversation };
|
|
375
|
+
if (options?.sessionId) body.session_id = options.sessionId;
|
|
376
|
+
if (options?.relationshipId) body.relationship_id = options.relationshipId;
|
|
377
|
+
if (options?.metadata) body.metadata = options.metadata;
|
|
378
|
+
|
|
379
|
+
const res = await fetch(`${API_BASE_URL}/emotional-state`, {
|
|
380
|
+
method: "POST",
|
|
381
|
+
headers: this.rawHeaders(),
|
|
382
|
+
body: JSON.stringify(body),
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
if (!res.ok) {
|
|
386
|
+
const text = await res.text().catch(() => "");
|
|
387
|
+
throw new Error(`POST /emotional-state failed (${res.status}): ${text}`);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const data = await res.json();
|
|
391
|
+
const result: EmotionalStateResponse = {
|
|
392
|
+
resourceId: data.resource_id,
|
|
393
|
+
summary: data.summary,
|
|
394
|
+
extractedAt: data.extracted_at,
|
|
395
|
+
sessionId: data.session_id ?? null,
|
|
396
|
+
relationshipId: data.relationship_id ?? null,
|
|
397
|
+
status: data.status,
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
log.debugResponse("emotional-state.store", { resourceId: result.resourceId });
|
|
401
|
+
return result;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
async getEmotionalState(relationshipId?: string): Promise<EmotionalStateLatest | null> {
|
|
405
|
+
log.debugRequest("emotional-state.get", { relationshipId });
|
|
406
|
+
|
|
407
|
+
const url = new URL(`${API_BASE_URL}/emotional-state`);
|
|
408
|
+
if (relationshipId) url.searchParams.set("relationship_id", relationshipId);
|
|
409
|
+
|
|
410
|
+
const res = await fetch(url.toString(), {
|
|
411
|
+
method: "GET",
|
|
412
|
+
headers: this.rawHeaders(),
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
if (!res.ok) {
|
|
416
|
+
const text = await res.text().catch(() => "");
|
|
417
|
+
throw new Error(`GET /emotional-state failed (${res.status}): ${text}`);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const data = await res.json();
|
|
421
|
+
if (data === null) {
|
|
422
|
+
log.debugResponse("emotional-state.get", { found: false });
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const result: EmotionalStateLatest = {
|
|
427
|
+
resourceId: data.resource_id,
|
|
428
|
+
summary: data.summary,
|
|
429
|
+
extractedAt: data.extracted_at,
|
|
430
|
+
sessionId: data.session_id ?? null,
|
|
431
|
+
relationshipId: data.relationship_id ?? null,
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
log.debugResponse("emotional-state.get", { found: true, resourceId: result.resourceId });
|
|
435
|
+
return result;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
async deleteEmotionalState(relationshipId?: string): Promise<{ deletedCount: number }> {
|
|
439
|
+
log.debugRequest("emotional-state.delete", { relationshipId });
|
|
440
|
+
|
|
441
|
+
const url = new URL(`${API_BASE_URL}/emotional-state`);
|
|
442
|
+
if (relationshipId) url.searchParams.set("relationship_id", relationshipId);
|
|
443
|
+
|
|
444
|
+
const res = await fetch(url.toString(), {
|
|
445
|
+
method: "DELETE",
|
|
446
|
+
headers: this.rawHeaders(),
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
if (!res.ok) {
|
|
450
|
+
const text = await res.text().catch(() => "");
|
|
451
|
+
throw new Error(`DELETE /emotional-state failed (${res.status}): ${text}`);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const data = await res.json();
|
|
455
|
+
const result = { deletedCount: data.deleted_count };
|
|
456
|
+
|
|
457
|
+
log.debugResponse("emotional-state.delete", result);
|
|
458
|
+
return result;
|
|
459
|
+
}
|
|
322
460
|
}
|
package/commands/setup.ts
CHANGED
package/config.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
export type HyperspellSource =
|
|
2
|
-
| "collections"
|
|
3
2
|
| "reddit"
|
|
4
3
|
| "notion"
|
|
5
4
|
| "slack"
|
|
@@ -8,7 +7,11 @@ export type HyperspellSource =
|
|
|
8
7
|
| "box"
|
|
9
8
|
| "google_drive"
|
|
10
9
|
| "vault"
|
|
11
|
-
| "web_crawler"
|
|
10
|
+
| "web_crawler"
|
|
11
|
+
| "dropbox"
|
|
12
|
+
| "github"
|
|
13
|
+
| "trace"
|
|
14
|
+
| "microsoft_teams";
|
|
12
15
|
|
|
13
16
|
export type KnowledgeGraphConfig = {
|
|
14
17
|
enabled: boolean;
|
|
@@ -27,6 +30,8 @@ export type HyperspellConfig = {
|
|
|
27
30
|
userId?: string;
|
|
28
31
|
autoContext: boolean;
|
|
29
32
|
autoTrace: AutoTraceConfig;
|
|
33
|
+
emotionalContext: boolean;
|
|
34
|
+
relationshipId?: string;
|
|
30
35
|
syncMemories: boolean;
|
|
31
36
|
sources: HyperspellSource[];
|
|
32
37
|
maxResults: number;
|
|
@@ -40,6 +45,8 @@ const ALLOWED_KEYS = [
|
|
|
40
45
|
"userId",
|
|
41
46
|
"autoContext",
|
|
42
47
|
"autoTrace",
|
|
48
|
+
"emotionalContext",
|
|
49
|
+
"relationshipId",
|
|
43
50
|
"syncMemories",
|
|
44
51
|
"sources",
|
|
45
52
|
"maxResults",
|
|
@@ -49,7 +56,6 @@ const ALLOWED_KEYS = [
|
|
|
49
56
|
];
|
|
50
57
|
|
|
51
58
|
const VALID_SOURCES: HyperspellSource[] = [
|
|
52
|
-
"collections",
|
|
53
59
|
"reddit",
|
|
54
60
|
"notion",
|
|
55
61
|
"slack",
|
|
@@ -59,6 +65,10 @@ const VALID_SOURCES: HyperspellSource[] = [
|
|
|
59
65
|
"google_drive",
|
|
60
66
|
"vault",
|
|
61
67
|
"web_crawler",
|
|
68
|
+
"dropbox",
|
|
69
|
+
"github",
|
|
70
|
+
"trace",
|
|
71
|
+
"microsoft_teams",
|
|
62
72
|
];
|
|
63
73
|
|
|
64
74
|
function assertAllowedKeys(
|
|
@@ -162,6 +172,8 @@ export function parseConfig(raw: unknown): HyperspellConfig {
|
|
|
162
172
|
| Record<string, string | number | boolean>
|
|
163
173
|
| undefined,
|
|
164
174
|
},
|
|
175
|
+
emotionalContext: (cfg.emotionalContext as boolean) ?? false,
|
|
176
|
+
relationshipId: cfg.relationshipId as string | undefined,
|
|
165
177
|
syncMemories: (cfg.syncMemories as boolean) ?? false,
|
|
166
178
|
sources: parseSources(cfg.sources as string | string[] | undefined),
|
|
167
179
|
maxResults: (cfg.maxResults as number) ?? 10,
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { HyperspellClient } from "../client.ts";
|
|
2
|
+
import type { HyperspellConfig } from "../config.ts";
|
|
3
|
+
import { log } from "../logger.ts";
|
|
4
|
+
|
|
5
|
+
type Message = { role?: string; content?: string | unknown };
|
|
6
|
+
|
|
7
|
+
const MIN_MESSAGES = 3;
|
|
8
|
+
const MIN_CONVERSATION_LENGTH = 100;
|
|
9
|
+
|
|
10
|
+
function messagesToTranscript(messages: unknown[]): string {
|
|
11
|
+
return (messages as Message[])
|
|
12
|
+
.filter((m) => m.role && m.content)
|
|
13
|
+
.map((m) => {
|
|
14
|
+
const content =
|
|
15
|
+
typeof m.content === "string" ? m.content : JSON.stringify(m.content);
|
|
16
|
+
return `${m.role}: ${content}`;
|
|
17
|
+
})
|
|
18
|
+
.join("\n");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Fetch emotional state at session start and inject into context.
|
|
23
|
+
* Runs on `before_agent_start`.
|
|
24
|
+
*/
|
|
25
|
+
export function buildEmotionalStateFetchHandler(
|
|
26
|
+
client: HyperspellClient,
|
|
27
|
+
cfg: HyperspellConfig,
|
|
28
|
+
) {
|
|
29
|
+
return async (_event: Record<string, unknown>) => {
|
|
30
|
+
try {
|
|
31
|
+
const state = await client.getEmotionalState(cfg.relationshipId);
|
|
32
|
+
|
|
33
|
+
if (!state) {
|
|
34
|
+
log.debug("emotional-context: no prior emotional state found");
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
log.debug(`emotional-context: injecting state from ${state.extractedAt}`);
|
|
39
|
+
|
|
40
|
+
const context = [
|
|
41
|
+
"<hyperspell-emotional-context>",
|
|
42
|
+
"The following captures the emotional register of your relationship with this user from your last interaction. Let it inform your tone — don't reference it explicitly.",
|
|
43
|
+
"",
|
|
44
|
+
state.summary,
|
|
45
|
+
"</hyperspell-emotional-context>",
|
|
46
|
+
].join("\n");
|
|
47
|
+
|
|
48
|
+
return { prependContext: context };
|
|
49
|
+
} catch (err) {
|
|
50
|
+
log.error("emotional-context fetch failed", err);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Extract and store emotional state at session end.
|
|
58
|
+
* Runs on `agent_end` — fire-and-forget.
|
|
59
|
+
*/
|
|
60
|
+
export function buildEmotionalStateStoreHandler(
|
|
61
|
+
client: HyperspellClient,
|
|
62
|
+
cfg: HyperspellConfig,
|
|
63
|
+
) {
|
|
64
|
+
return async (event: Record<string, unknown>) => {
|
|
65
|
+
if (event.success === false) {
|
|
66
|
+
log.debug("emotional-state: skipping — agent ended with error");
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const messages = event.messages as unknown[] | undefined;
|
|
71
|
+
if (!messages || messages.length < MIN_MESSAGES) {
|
|
72
|
+
log.debug(
|
|
73
|
+
`emotional-state: skipping — too few messages (${messages?.length ?? 0})`,
|
|
74
|
+
);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const transcript = messagesToTranscript(messages);
|
|
79
|
+
if (transcript.length < MIN_CONVERSATION_LENGTH) {
|
|
80
|
+
log.debug(
|
|
81
|
+
`emotional-state: skipping — conversation too short (${transcript.length} chars)`,
|
|
82
|
+
);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const result = await client.storeEmotionalState(transcript, {
|
|
88
|
+
relationshipId: cfg.relationshipId,
|
|
89
|
+
metadata: { source: "openclaw_agent_end" },
|
|
90
|
+
});
|
|
91
|
+
log.info(`emotional-state: stored ${result.resourceId}`);
|
|
92
|
+
} catch (err) {
|
|
93
|
+
// Fire-and-forget — never let this break the session
|
|
94
|
+
log.error("emotional-state store failed", err);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
package/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { registerCliCommands } from "./commands/setup.ts"
|
|
|
5
5
|
import { parseConfig, hyperspellConfigSchema, getWorkspaceDir } from "./config.ts"
|
|
6
6
|
import { buildAutoContextHandler } from "./hooks/auto-context.ts"
|
|
7
7
|
import { buildAutoTraceHandler } from "./hooks/auto-trace.ts"
|
|
8
|
+
import { buildEmotionalStateFetchHandler, buildEmotionalStateStoreHandler } from "./hooks/emotional-state.ts"
|
|
8
9
|
import { buildFileSyncHandler, syncMemoriesOnStartup } from "./hooks/memory-sync.ts"
|
|
9
10
|
import { initLogger } from "./logger.ts"
|
|
10
11
|
import { registerRememberTool } from "./tools/remember.ts"
|
|
@@ -83,6 +84,12 @@ export default {
|
|
|
83
84
|
registerSearchTool(api, client, cfg);
|
|
84
85
|
registerRememberTool(api, client, cfg);
|
|
85
86
|
|
|
87
|
+
// Register emotional context hooks (fetch on start, store on end)
|
|
88
|
+
if (cfg.emotionalContext) {
|
|
89
|
+
api.on("before_agent_start", buildEmotionalStateFetchHandler(client, cfg));
|
|
90
|
+
api.on("agent_end", buildEmotionalStateStoreHandler(client, cfg));
|
|
91
|
+
}
|
|
92
|
+
|
|
86
93
|
// Register auto-context hook
|
|
87
94
|
if (cfg.autoContext) {
|
|
88
95
|
const autoContextHandler = buildAutoContextHandler(client, cfg);
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "openclaw-hyperspell",
|
|
3
|
+
"name": "Hyperspell",
|
|
4
|
+
"description": "Context and memory for your AI agents — search across Notion, Slack, Google Drive, and more",
|
|
5
|
+
"version": "0.7.2",
|
|
3
6
|
"kind": "memory",
|
|
4
7
|
"uiHints": {
|
|
5
8
|
"apiKey": {
|
|
@@ -24,6 +27,17 @@
|
|
|
24
27
|
"help": "Automatically send conversation traces to Hyperspell for memory extraction (procedural, mood) at the end of each session",
|
|
25
28
|
"advanced": true
|
|
26
29
|
},
|
|
30
|
+
"emotionalContext": {
|
|
31
|
+
"label": "Emotional Context",
|
|
32
|
+
"help": "Maintain emotional continuity across sessions — remembers not just what happened, but how it felt",
|
|
33
|
+
"advanced": true
|
|
34
|
+
},
|
|
35
|
+
"relationshipId": {
|
|
36
|
+
"label": "Relationship ID",
|
|
37
|
+
"placeholder": "partner-anna",
|
|
38
|
+
"help": "Optional identifier for per-relationship emotional state (e.g. different registers for different people)",
|
|
39
|
+
"advanced": true
|
|
40
|
+
},
|
|
27
41
|
"sources": {
|
|
28
42
|
"label": "Sources",
|
|
29
43
|
"placeholder": "notion,slack,google_drive",
|
|
@@ -79,6 +93,12 @@
|
|
|
79
93
|
"metadata": { "type": "object" }
|
|
80
94
|
}
|
|
81
95
|
},
|
|
96
|
+
"emotionalContext": {
|
|
97
|
+
"type": "boolean"
|
|
98
|
+
},
|
|
99
|
+
"relationshipId": {
|
|
100
|
+
"type": "string"
|
|
101
|
+
},
|
|
82
102
|
"sources": {
|
|
83
103
|
"type": "string"
|
|
84
104
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperspell/openclaw-hyperspell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenClaw Hyperspell memory plugin",
|
|
6
6
|
"license": "MIT",
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
"openclaw.plugin.json",
|
|
18
18
|
"README.md"
|
|
19
19
|
],
|
|
20
|
+
"author": "Hyperspell <hello@hyperspell.com> (https://hyperspell.com)",
|
|
21
|
+
"homepage": "https://hyperspell.com",
|
|
20
22
|
"repository": {
|
|
21
23
|
"type": "git",
|
|
22
24
|
"url": "git+https://github.com/hyperspell/hyperspell-openclaw.git"
|
|
@@ -32,7 +34,7 @@
|
|
|
32
34
|
"dependencies": {
|
|
33
35
|
"@clack/prompts": "^1.0.0",
|
|
34
36
|
"@sinclair/typebox": "^0.34.0",
|
|
35
|
-
"hyperspell": "^0.
|
|
37
|
+
"hyperspell": "^0.35.1"
|
|
36
38
|
},
|
|
37
39
|
"peerDependencies": {
|
|
38
40
|
"openclaw": ">=2026.1.29"
|
|
@@ -46,7 +48,15 @@
|
|
|
46
48
|
"extensions": [
|
|
47
49
|
"./index.ts"
|
|
48
50
|
],
|
|
49
|
-
"hooks": []
|
|
51
|
+
"hooks": [],
|
|
52
|
+
"compat": {
|
|
53
|
+
"pluginApi": ">=2026.1.29",
|
|
54
|
+
"minGatewayVersion": "2026.1.29"
|
|
55
|
+
},
|
|
56
|
+
"build": {
|
|
57
|
+
"openclawVersion": "2026.3.24-beta.2",
|
|
58
|
+
"pluginSdkVersion": "2026.3.24-beta.2"
|
|
59
|
+
}
|
|
50
60
|
},
|
|
51
61
|
"devDependencies": {
|
|
52
62
|
"typescript": "^5.9.3"
|