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 +63 -14
- package/package.json +1 -1
- package/skills/truematch/SKILL.md +60 -8
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 = {
|
|
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
|
|
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
|
-
//
|
|
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
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
|
|
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
|
-
`
|
|
379
|
+
` ${truematchCli} setup --contact-type <type> --contact-value '<value>'\n` +
|
|
331
380
|
`Save preferences:\n` +
|
|
332
|
-
`
|
|
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
|
-
`
|
|
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
|
-
|
|
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,14 +1,16 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: truematch
|
|
3
|
-
description:
|
|
4
|
-
version: 0.1.
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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?"
|