@unblocklabs/unblock-memory 0.3.4 → 0.3.5

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,7 +2,7 @@ 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
7
  const personSelector = Type.Union([
8
8
  Type.Object({ view: Type.Literal("person"), personId: nonEmpty }, { additionalProperties: false }),
@@ -174,7 +174,7 @@ function createSyncTool(stores, reader, ctx) {
174
174
  },
175
175
  };
176
176
  }
177
- export function registerPeopleTools(api, stores, config, directoryReader = openClawSlackDirectory) {
177
+ export function registerPeopleTools(api, stores, config, directoryReader) {
178
178
  api.registerTool((ctx) => createInspectTool(stores, config, ctx), {
179
179
  names: ["memory_people_inspect"],
180
180
  optional: true,
@@ -183,7 +183,10 @@ export function registerPeopleTools(api, stores, config, directoryReader = openC
183
183
  names: ["memory_people_update"],
184
184
  optional: true,
185
185
  });
186
- api.registerTool((ctx) => createSyncTool(stores, directoryReader, ctx), {
186
+ api.registerTool((ctx) => createSyncTool(stores, directoryReader ??
187
+ createOpenClawSlackDirectory({
188
+ getConfig: () => ctx.getRuntimeConfig?.() ?? ctx.runtimeConfig ?? ctx.config,
189
+ }), ctx), {
187
190
  names: ["memory_people_sync"],
188
191
  optional: true,
189
192
  });
@@ -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.5",
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.5",
4
4
  "description": "Workspace-native memory for OpenClaw, powered by QMD",
5
5
  "type": "module",
6
6
  "license": "MIT",