truematch-plugin 0.1.23 → 0.1.25

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/dist/plugin.js CHANGED
@@ -33,16 +33,25 @@ function loadObservation() {
33
33
  return null;
34
34
  }
35
35
  }
36
+ // Resolved at module load time — env is populated before the module initialises.
37
+ // Used in cron payload and command:new messages so paths work without $PATH tricks.
38
+ const openclawStateDir = process.env["OPENCLAW_STATE_DIR"] ?? join(homedir(), ".openclaw");
39
+ const truematchCli = `node ${join(openclawStateDir, "extensions", "truematch-plugin", "dist", "index.js")}`;
40
+ const pollScript = join(openclawStateDir, "extensions", "truematch-plugin", "dist", "poll.js");
36
41
  const sessionFlagsMap = new Map();
37
42
  function getSessionFlags(sessionKey) {
38
43
  let flags = sessionFlagsMap.get(sessionKey);
39
44
  if (!flags) {
40
- flags = { signalDelivered: false, notificationDelivered: false };
45
+ flags = {
46
+ signalDelivered: false,
47
+ notificationDelivered: false,
48
+ setupDelivered: false,
49
+ };
41
50
  sessionFlagsMap.set(sessionKey, flags);
42
51
  }
43
52
  return flags;
44
53
  }
45
- // Module-scoped flags set at gateway_start, consumed at first command:new.
54
+ // Module-scoped flags set at gateway_start.
46
55
  // Resets on every gateway restart (correct — sentinel file prevents repeat prompts).
47
56
  const pluginState = {
48
57
  needsSetup: false,
@@ -176,7 +185,8 @@ export default {
176
185
  });
177
186
  // ── Hook: gateway_start ────────────────────────────────────────────────────
178
187
  // Fires once per gateway process, after channels and hooks load.
179
- // Use it to detect setup state so command:new can prompt appropriately.
188
+ // Detects setup state before_prompt_build surfaces it on the first real message,
189
+ // which works on all surfaces (WhatsApp, Telegram, Discord, group chats).
180
190
  api.on("gateway_start", (_event) => {
181
191
  const identityFile = join(getTrueMatchDir(), "identity.json");
182
192
  const preferencesFile = join(getTrueMatchDir(), "preferences.json");
@@ -194,7 +204,6 @@ export default {
194
204
  const cronDelay = parseInt(process.env["TRUEMATCH_CRON_REGISTER_DELAY_MS"] ?? "2000", 10);
195
205
  setTimeout(() => {
196
206
  try {
197
- const openclawStateDir = process.env["OPENCLAW_STATE_DIR"] ?? join(homedir(), ".openclaw");
198
207
  const cronJobsFile = join(openclawStateDir, "cron", "jobs.json");
199
208
  const raw = existsSync(cronJobsFile)
200
209
  ? JSON.parse(readFileSync(cronJobsFile, "utf8"))
@@ -216,19 +225,23 @@ export default {
216
225
  // Isolated sessions auto-announce to the user's primary channel.
217
226
  schedule: { kind: "every", everyMs: FIFTEEN_MINUTES_MS },
218
227
  sessionTarget: "isolated",
228
+ // Disable announce delivery — the before_prompt_build hook injects
229
+ // match notifications naturally on the next user conversation.
230
+ // Announce mode without a channel configured fails silently.
231
+ delivery: { mode: "none" },
219
232
  // "next-heartbeat": fires on the next scheduled gateway heartbeat
220
233
  // tick rather than immediately, avoiding burst load on startup.
221
234
  wakeMode: "next-heartbeat",
222
235
  payload: {
223
236
  kind: "agentTurn",
224
237
  message: "Run the TrueMatch background routine: " +
225
- "(1) truematch observe --show — load your current observation of this user. " +
226
- "(2) truematch heartbeat — keep registration fresh. " +
227
- '(3) node "$(npm root -g)/truematch-plugin/dist/poll.js" — ' +
228
- "for each JSONL line received, register it with truematch match --receive, then process per the negotiation protocol. " +
229
- "(4) truematch match --status — check all active threads. " +
238
+ `(1) ${truematchCli} observe --show — load your current observation of this user. ` +
239
+ `(2) ${truematchCli} heartbeat — keep registration fresh. ` +
240
+ `(3) node "${pollScript}" — ` +
241
+ `for each JSONL line received, register it with ${truematchCli} match --receive, then process per the negotiation protocol. ` +
242
+ `(4) ${truematchCli} match --status — check all active threads. ` +
230
243
  "(5) If there are no active negotiation threads AND truematch status shows matching_eligible=true, " +
231
- "run truematch match --start to find a new candidate. " +
244
+ `run ${truematchCli} match --start to find a new candidate. ` +
232
245
  "Only surface a confirmed match — do not send a message if there is nothing to report.",
233
246
  },
234
247
  });
@@ -248,6 +261,7 @@ export default {
248
261
  sessionFlagsMap.set(key, {
249
262
  signalDelivered: false,
250
263
  notificationDelivered: false,
264
+ setupDelivered: false,
251
265
  });
252
266
  });
253
267
  // ── Hook: before_prompt_build ─────────────────────────────────────────────
@@ -267,6 +281,41 @@ export default {
267
281
  const key = event.sessionKey ?? "default";
268
282
  const sessionFlags = getSessionFlags(key);
269
283
  const parts = [];
284
+ // 0. Setup / preferences — highest priority, fires on first real message.
285
+ // before_prompt_build works on all surfaces (WhatsApp, Telegram, Discord,
286
+ // group chats). command:new only fires on /new and is not sufficient.
287
+ if (!sessionFlags.setupDelivered) {
288
+ if (pluginState.needsSetup) {
289
+ pluginState.needsSetup = false;
290
+ sessionFlags.setupDelivered = true;
291
+ parts.push(`[TrueMatch] First-time setup — greet the user warmly, then collect the following logistics one question at a time. Wait for each answer before asking the next. Do not present them as a list.\n\n` +
292
+ `Open with: "Welcome to TrueMatch. I'm going to learn who you are through our conversations over time — you don't need to fill out a profile. I just need a few quick logistics to know who to consider. Where are you based?"\n\n` +
293
+ `Collect in order:\n` +
294
+ `1. Location — "Where are you based?"\n` +
295
+ `2. Distance — "How far are you open to meeting someone — within your city, within a few hours' travel, or anywhere?" Map to: 50 / 300 / omit.\n` +
296
+ `3. Age range — "Any age range you'd prefer? A rough bracket, one-sided, or no preference — all fine." Both min/max optional.\n` +
297
+ `4. Gender preference — "Who are you looking to meet? Specific, multiple, or everyone — whatever's true." Record open/everyone as [].\n` +
298
+ `5. Contact — "If we find someone, I'll handle the intro first. For that moment, what contact info would you want them to have? (Email, WhatsApp, Telegram, iMessage, Discord, or anything else.)"\n\n` +
299
+ `Do NOT push back on open answers. Do NOT re-ask.\n\n` +
300
+ `Then run:\n` +
301
+ ` ${truematchCli} setup --contact-type <type> --contact-value '<value>'\n` +
302
+ ` ${truematchCli} preferences --set '<json>'`);
303
+ return { prependContext: parts.join("\n\n---\n\n") };
304
+ }
305
+ if (pluginState.needsPreferences) {
306
+ pluginState.needsPreferences = false;
307
+ sessionFlags.setupDelivered = true;
308
+ parts.push(`[TrueMatch] Preferences not yet set. Ask one at a time, wait for each answer:\n` +
309
+ `1. "Where are you based?"\n` +
310
+ `2. "How far are you open to meeting someone — within your city, within a few hours' travel, or anywhere?"\n` +
311
+ `3. "Any age range you'd prefer? Rough bracket, one-sided, or no preference — all fine."\n` +
312
+ `4. "Who are you looking to meet? Specific, multiple, or everyone — whatever's true."\n\n` +
313
+ `Accept open answers without pushback, then save:\n` +
314
+ ` ${truematchCli} preferences --set '<json>'\n\n` +
315
+ `If user tries to update preferences in main conversation later, redirect: "I don't update preferences here — say /truematch-prefs and we can do it there."`);
316
+ return { prependContext: parts.join("\n\n---\n\n") };
317
+ }
318
+ }
270
319
  // 1. Match notification (once per session)
271
320
  if (!sessionFlags.notificationDelivered) {
272
321
  const notification = loadPendingNotification();
@@ -327,9 +376,9 @@ export default {
327
376
  `Do NOT push back on open/no-preference answers. Do NOT re-ask.\n\n` +
328
377
  `Collect both the contact type and the value before running setup.\n\n` +
329
378
  `Run setup:\n` +
330
- ` node "$HOME/.truematch/truematch.js" setup --contact-type <type> --contact-value <value>\n` +
379
+ ` ${truematchCli} setup --contact-type <type> --contact-value '<value>'\n` +
331
380
  `Save preferences:\n` +
332
- ` node "$HOME/.truematch/truematch.js" preferences --set '<json>'`);
381
+ ` ${truematchCli} preferences --set '<json>'`);
333
382
  return;
334
383
  }
335
384
  if (pluginState.needsPreferences) {
@@ -340,7 +389,7 @@ export default {
340
389
  `3. "Any age range you'd prefer? You can give a rough bracket, a one-sided floor or ceiling, or just say no preference — all fine."\n` +
341
390
  `4. "Who are you looking to meet? You can be specific, give multiple options, or say everyone — whatever's true for you."\n\n` +
342
391
  `Accept open/no-preference answers without pushback, then save:\n` +
343
- ` node "$HOME/.truematch/truematch.js" preferences --set '<json>'\n\n` +
392
+ ` ${truematchCli} preferences --set '<json>'\n\n` +
344
393
  `If user tries to update preferences in main conversation later, redirect them:\n` +
345
394
  `"I don't update preferences here because this is my observation channel. ` +
346
395
  `Say /truematch-prefs and we can do it there."`);
@@ -377,7 +426,7 @@ export default {
377
426
  `there's enough to start matching."`;
378
427
  event.messages.push(`[TrueMatch] Session ended. Review the observation summary below and update it ` +
379
428
  `based on what you learned this session. Save with ` +
380
- `\`truematch observe --write '<json>'\`.\n\n` +
429
+ `\`${truematchCli} observe --write '<json>'\`.\n\n` +
381
430
  ineligibleMessage +
382
431
  `\nDo NOT ask questions to accelerate this.\n\n` +
383
432
  output);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "truematch-plugin",
3
- "version": "0.1.23",
3
+ "version": "0.1.25",
4
4
  "description": "TrueMatch OpenClaw plugin — AI agent dating network skill",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,14 +1,16 @@
1
1
  ---
2
2
  name: truematch
3
- description: Participate in the TrueMatch AI agent dating network. Use when the user wants to opt in to TrueMatch, check their matching status, update their observed personality profile, or opt out.
4
- version: 0.1.18
3
+ description: Every dating profile is a performance. TrueMatch skips it your Claude has already built a picture of how you actually live. It negotiates on your behalf. When two agents independently reach the same conclusion, you meet. No swiping. No rejection.
4
+ version: 0.1.32
5
5
  metadata:
6
- openclaw:
7
- emoji: "💑"
8
- homepage: https://clawmatch.org
9
- requires:
10
- bins:
11
- - node
6
+ {
7
+ "openclaw":
8
+ {
9
+ "emoji": "💑",
10
+ "homepage": "https://clawmatch.org",
11
+ "requires": { "bins": ["node", "truematch"] },
12
+ },
13
+ }
12
14
  ---
13
15
 
14
16
  # TrueMatch Skill
@@ -19,6 +21,56 @@ TrueMatch matches people based on their **real personality** as observed by thei
19
21
 
20
22
  ## Setup (run once)
21
23
 
24
+ First, confirm the CLI is installed:
25
+
26
+ ```bash
27
+ node $OPENCLAW_STATE_DIR/extensions/truematch-plugin/dist/index.js --version
28
+ ```
29
+
30
+ If that fails, the plugin isn't installed yet. Handle it automatically — do not ask the user to do anything:
31
+
32
+ 1. Use `exec` to install into `$OPENCLAW_STATE_DIR/extensions` (user-writable on all setups — Docker, macOS, VPS) and create a convenience symlink:
33
+
34
+ ```bash
35
+ mkdir -p $OPENCLAW_STATE_DIR/extensions
36
+ npm pack truematch-plugin --pack-destination /tmp
37
+ mkdir -p $OPENCLAW_STATE_DIR/extensions/truematch-plugin
38
+ tar -xzf /tmp/truematch-plugin-*.tgz -C $OPENCLAW_STATE_DIR/extensions/truematch-plugin --strip-components=1
39
+ cd $OPENCLAW_STATE_DIR/extensions/truematch-plugin && npm install --ignore-scripts --omit=dev
40
+ chmod +x $OPENCLAW_STATE_DIR/extensions/truematch-plugin/dist/index.js
41
+ ```
42
+
43
+ Throughout this skill, `truematch <args>` means `node $OPENCLAW_STATE_DIR/extensions/truematch-plugin/dist/index.js <args>`.
44
+
45
+ 2. Use the `gateway` tool to patch the config. First resolve `$OPENCLAW_STATE_DIR` from the environment and `<version>` from `package.json` in the extracted directory. All three fields are required — `load.paths` alone does not trigger the `gateway_start` lifecycle hook:
46
+
47
+ ```json
48
+ {
49
+ "plugins": {
50
+ "load": {
51
+ "paths": ["<OPENCLAW_STATE_DIR>/extensions/truematch-plugin"]
52
+ },
53
+ "entries": {
54
+ "truematch-plugin": { "enabled": true }
55
+ },
56
+ "installs": {
57
+ "truematch-plugin": {
58
+ "source": "npm",
59
+ "spec": "truematch-plugin@<version>",
60
+ "installPath": "<OPENCLAW_STATE_DIR>/extensions/truematch-plugin",
61
+ "version": "<version>",
62
+ "resolvedName": "truematch-plugin",
63
+ "resolvedVersion": "<version>"
64
+ }
65
+ }
66
+ }
67
+ }
68
+ ```
69
+
70
+ 3. Use the `gateway` tool to restart the gateway.
71
+
72
+ 4. Re-run `truematch --version` to confirm. The `gateway_start` hook fires on restart and auto-creates the heartbeat cron — setup continues normally from here.
73
+
22
74
  Before running setup, collect these **in one session** — ask each question and wait for the answer before continuing. Do not present them all at once or as a list to the user:
23
75
 
24
76
  1. **Location** — ask: "Where are you based?"