@unblocklabs/unblock-memory 0.3.4 → 0.3.6

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.
@@ -2,24 +2,43 @@ import { jsonResult } from "openclaw/plugin-sdk/agent-runtime";
2
2
  import { Type } from "typebox";
3
3
  import { Value } from "typebox/value";
4
4
  import { renderPeopleWhisper } from "./people-hooks.js";
5
- import { openClawSlackDirectory, syncSlackDirectory, } from "./slack-directory.js";
5
+ import { createOpenClawSlackDirectory, syncSlackDirectory, } from "./slack-directory.js";
6
6
  const nonEmpty = Type.String({ pattern: "\\S", maxLength: 1000 });
7
- const personSelector = Type.Union([
8
- Type.Object({ view: Type.Literal("person"), personId: nonEmpty }, { additionalProperties: false }),
7
+ const inspectParameters = Type.Union([
8
+ Type.Object({
9
+ view: Type.Literal("person"),
10
+ personId: Type.String({
11
+ pattern: "\\S",
12
+ maxLength: 1000,
13
+ description: "PeopleSQL person ID. Use identity when only Slack IDs are known.",
14
+ }),
15
+ }, { additionalProperties: false }),
9
16
  Type.Object({
10
17
  view: Type.Literal("person"),
11
18
  identity: Type.Object({
12
19
  provider: Type.Literal("slack"),
13
- accountScope: nonEmpty,
14
- externalId: nonEmpty,
15
- }, { additionalProperties: false }),
20
+ accountScope: Type.String({
21
+ pattern: "\\S",
22
+ maxLength: 1000,
23
+ description: "Configured Slack account ID, for example default.",
24
+ }),
25
+ externalId: Type.String({
26
+ pattern: "\\S",
27
+ maxLength: 1000,
28
+ description: "Exact Slack user ID.",
29
+ }),
30
+ }, {
31
+ additionalProperties: false,
32
+ description: "Exact Slack identity for the person to inspect.",
33
+ }),
16
34
  }, { additionalProperties: false }),
17
- ]);
18
- const inspectParameters = Type.Union([
19
- personSelector,
20
35
  Type.Object({
21
36
  view: Type.Literal("todos"),
22
- limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 100 })),
37
+ limit: Type.Optional(Type.Integer({
38
+ minimum: 1,
39
+ maximum: 100,
40
+ description: "Maximum actionable todos to return.",
41
+ })),
23
42
  }, { additionalProperties: false }),
24
43
  ]);
25
44
  const updateParameters = Type.Union([
@@ -85,7 +104,7 @@ function createInspectTool(stores, config, ctx) {
85
104
  return {
86
105
  name: "memory_people_inspect",
87
106
  label: "Inspect People Memory",
88
- description: "Inspect one exact person or bounded actionable people todos.",
107
+ description: "Inspect one person by PeopleSQL personId or exact Slack identity, or list bounded actionable people todos.",
89
108
  parameters: inspectParameters,
90
109
  async execute(_toolCallId, raw) {
91
110
  const input = Value.Parse(inspectParameters, raw);
@@ -174,7 +193,7 @@ function createSyncTool(stores, reader, ctx) {
174
193
  },
175
194
  };
176
195
  }
177
- export function registerPeopleTools(api, stores, config, directoryReader = openClawSlackDirectory) {
196
+ export function registerPeopleTools(api, stores, config, directoryReader) {
178
197
  api.registerTool((ctx) => createInspectTool(stores, config, ctx), {
179
198
  names: ["memory_people_inspect"],
180
199
  optional: true,
@@ -183,7 +202,10 @@ export function registerPeopleTools(api, stores, config, directoryReader = openC
183
202
  names: ["memory_people_update"],
184
203
  optional: true,
185
204
  });
186
- api.registerTool((ctx) => createSyncTool(stores, directoryReader, ctx), {
205
+ api.registerTool((ctx) => createSyncTool(stores, directoryReader ??
206
+ createOpenClawSlackDirectory({
207
+ getConfig: () => ctx.getRuntimeConfig?.() ?? ctx.runtimeConfig ?? ctx.config,
208
+ }), ctx), {
187
209
  names: ["memory_people_sync"],
188
210
  optional: true,
189
211
  });
@@ -1,3 +1,4 @@
1
+ import type { OpenClawConfig } from "openclaw/plugin-sdk/plugin-entry";
1
2
  import type { PeopleStore } from "./people-store.js";
2
3
  type SlackDirectoryEntry = {
3
4
  id: string;
@@ -11,13 +12,22 @@ export type SlackDirectoryReader = {
11
12
  limit: number;
12
13
  }): Promise<readonly SlackDirectoryEntry[]>;
13
14
  };
14
- type DirectoryCommand = (executable: string, args: readonly string[], options: {
15
- maxBuffer: number;
16
- }) => Promise<{
17
- stdout: string;
18
- }>;
19
- export declare function createOpenClawSlackDirectory(run?: DirectoryCommand): SlackDirectoryReader;
20
- export declare const openClawSlackDirectory: SlackDirectoryReader;
15
+ type SlackAccountInspector = (params: {
16
+ channelId: "slack";
17
+ cfg: OpenClawConfig;
18
+ accountId: string;
19
+ }) => Promise<Record<string, unknown> | null>;
20
+ type SlackRequest = (input: string | URL, init: {
21
+ headers: {
22
+ authorization: string;
23
+ };
24
+ signal: AbortSignal;
25
+ }) => Promise<Pick<Response, "json" | "ok" | "status">>;
26
+ export declare function createOpenClawSlackDirectory(params: {
27
+ getConfig: () => OpenClawConfig | undefined;
28
+ inspectAccount?: SlackAccountInspector;
29
+ request?: SlackRequest;
30
+ }): SlackDirectoryReader;
21
31
  export declare function syncSlackDirectory(params: {
22
32
  store: PeopleStore;
23
33
  reader: SlackDirectoryReader;
@@ -1,55 +1,83 @@
1
- import { execFile } from "node:child_process";
2
- import { promisify } from "node:util";
3
- const execFileAsync = promisify(execFile);
1
+ import { inspectReadOnlyChannelAccount } from "openclaw/plugin-sdk/directory-runtime";
4
2
  function text(value, maxLength) {
5
3
  return typeof value === "string" && value.trim() ? value.trim().slice(0, maxLength) : undefined;
6
4
  }
7
- export function createOpenClawSlackDirectory(run = async (executable, args, options) => {
8
- const result = await execFileAsync(executable, args, options);
9
- return { stdout: result.stdout };
10
- }) {
5
+ function record(value) {
6
+ return value && typeof value === "object" && !Array.isArray(value)
7
+ ? value
8
+ : undefined;
9
+ }
10
+ function slackToken(account) {
11
+ return text(account.userToken, 10_000) ?? text(account.botToken, 10_000);
12
+ }
13
+ function slackEntry(value) {
14
+ const member = record(value);
15
+ const id = text(member?.id, 200);
16
+ if (!id)
17
+ return undefined;
18
+ const profile = record(member?.profile);
19
+ return {
20
+ id,
21
+ name: text(profile?.display_name, 500) ??
22
+ text(profile?.real_name, 500) ??
23
+ text(member?.real_name, 500) ??
24
+ text(member?.name, 500),
25
+ handle: text(member?.name, 200),
26
+ avatarUrl: text(profile?.image_512, 2_000) ??
27
+ text(profile?.image_192, 2_000) ??
28
+ text(profile?.image_72, 2_000),
29
+ };
30
+ }
31
+ export function createOpenClawSlackDirectory(params) {
32
+ const inspectAccount = params.inspectAccount ?? inspectReadOnlyChannelAccount;
33
+ const request = params.request ?? fetch;
11
34
  return {
12
35
  async listUsers({ accountId, limit }) {
13
- const { stdout } = await run("openclaw", [
14
- "directory",
15
- "peers",
16
- "list",
17
- "--channel",
18
- "slack",
19
- "--account",
20
- accountId,
21
- "--limit",
22
- String(limit),
23
- "--json",
24
- ], { maxBuffer: 1024 * 1024 });
25
- const parsed = JSON.parse(stdout);
26
- if (!Array.isArray(parsed))
27
- throw new Error("OpenClaw Slack directory returned an invalid response");
28
- return parsed.flatMap((value) => {
29
- if (!value || typeof value !== "object" || Array.isArray(value))
30
- return [];
31
- const entry = value;
32
- if (entry.kind !== "user")
33
- return [];
34
- const prefixedId = text(entry.id, 200);
35
- if (!prefixedId?.startsWith("user:"))
36
- return [];
37
- const id = text(prefixedId.slice("user:".length), 200);
38
- const handle = text(entry.handle, 200);
39
- return id
40
- ? [
41
- {
42
- id,
43
- name: text(entry.name, 500),
44
- handle: text(handle?.startsWith("@") ? handle.slice(1) : handle, 200),
45
- },
46
- ]
47
- : [];
48
- });
36
+ const cfg = params.getConfig();
37
+ if (!cfg)
38
+ throw new Error("OpenClaw runtime config is unavailable");
39
+ const account = await inspectAccount({ channelId: "slack", cfg, accountId });
40
+ const token = account ? slackToken(account) : undefined;
41
+ if (!token) {
42
+ throw new Error(`Slack credentials for account "${accountId}" are unavailable in the active runtime snapshot`);
43
+ }
44
+ const entries = [];
45
+ const cursors = new Set();
46
+ let cursor;
47
+ while (entries.length < limit) {
48
+ const url = new URL("https://slack.com/api/users.list");
49
+ url.searchParams.set("limit", String(Math.min(limit, 200)));
50
+ if (cursor)
51
+ url.searchParams.set("cursor", cursor);
52
+ const response = await request(url, {
53
+ headers: { authorization: `Bearer ${token}` },
54
+ signal: AbortSignal.timeout(30_000),
55
+ });
56
+ const payload = record(await response.json());
57
+ if (!response.ok || payload?.ok !== true) {
58
+ const detail = text(payload?.error, 200) ?? `HTTP ${response.status}`;
59
+ throw new Error(`Slack directory request failed: ${detail}`);
60
+ }
61
+ const members = Array.isArray(payload.members) ? payload.members : [];
62
+ for (const member of members) {
63
+ const entry = slackEntry(member);
64
+ if (entry)
65
+ entries.push(entry);
66
+ if (entries.length === limit)
67
+ break;
68
+ }
69
+ const next = text(record(payload.response_metadata)?.next_cursor, 2_000);
70
+ if (!next)
71
+ break;
72
+ if (cursors.has(next))
73
+ throw new Error("Slack directory returned a repeated cursor");
74
+ cursors.add(next);
75
+ cursor = next;
76
+ }
77
+ return entries;
49
78
  },
50
79
  };
51
80
  }
52
- export const openClawSlackDirectory = createOpenClawSlackDirectory();
53
81
  export async function syncSlackDirectory(params) {
54
82
  const entries = await params.reader.listUsers({
55
83
  accountId: params.accountId,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "unblock-memory",
3
3
  "name": "Unblock Memory",
4
- "version": "0.3.4",
4
+ "version": "0.3.6",
5
5
  "description": "Indexes, retrieves, and analyzes configured workspace memory with existing QMD vectors.",
6
6
  "kind": "memory",
7
7
  "activation": { "onStartup": false },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unblocklabs/unblock-memory",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "description": "Workspace-native memory for OpenClaw, powered by QMD",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -41,6 +41,7 @@
41
41
  "typebox": "1.3.6"
42
42
  },
43
43
  "devDependencies": {
44
+ "@openclaw/ai": "2026.8.1-beta.3",
44
45
  "@openclaw/plugin-inspector": "^0.3.10",
45
46
  "@types/node": "^24.6.0",
46
47
  "@types/picomatch": "^4.0.2",