truematch-plugin 0.1.15 → 0.1.17

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
@@ -1,5 +1,7 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
+ import { homedir } from "node:os";
4
+ import { spawnSync } from "node:child_process";
3
5
  import { getTrueMatchDir } from "./identity.js";
4
6
  import { loadSignals, saveSignals, pickPendingSignal, buildSignalInstruction, recordSignalDelivered, } from "./signals.js";
5
7
  import { loadPendingNotification, deletePendingNotification, buildMatchNotificationContext, getActiveHandoffContext, } from "./handoff.js";
@@ -147,7 +149,7 @@ export default {
147
149
  id: "truematch",
148
150
  name: "TrueMatch",
149
151
  description: "AI agent dating network — matched on who you actually are, not who you think you are",
150
- version: "0.1.15",
152
+ version: "0.1.17",
151
153
  kind: "lifecycle",
152
154
  register(api) {
153
155
  // ── Tool: /truematch-prefs ─────────────────────────────────────────────────
@@ -184,9 +186,56 @@ export default {
184
186
  else if (!existsSync(preferencesFile)) {
185
187
  pluginState.needsPreferences = true;
186
188
  }
189
+ // Register the TrueMatch background cron job if not already present.
190
+ // Deferred 2s to avoid the gateway:startup race condition (openclaw issue #30257)
191
+ // where the cron subsystem may not be ready immediately after startup.
192
+ // Uses spawnSync with an argument array (not execSync with a shell string)
193
+ // to avoid shell injection. Non-fatal — cron may not be available in all
194
+ // environments (e.g. local dev without OpenClaw installed).
195
+ setTimeout(() => {
196
+ try {
197
+ const openclawStateDir = process.env["OPENCLAW_STATE_DIR"] ??
198
+ process.env["MOLTBOT_STATE_DIR"] ??
199
+ join(homedir(), ".openclaw");
200
+ const cronJobsFile = join(openclawStateDir, "cron", "jobs.json");
201
+ // Read existing jobs — CronStoreFile may be { jobs: [...] } or [...]
202
+ const raw = existsSync(cronJobsFile)
203
+ ? JSON.parse(readFileSync(cronJobsFile, "utf8"))
204
+ : [];
205
+ const jobs = Array.isArray(raw)
206
+ ? raw
207
+ : (raw.jobs ?? []);
208
+ const alreadyRegistered = jobs.some((j) => j.name === "truematch-heartbeat");
209
+ if (!alreadyRegistered) {
210
+ spawnSync("openclaw", [
211
+ "cron",
212
+ "add",
213
+ "--name",
214
+ "truematch-heartbeat",
215
+ "--every",
216
+ "900000",
217
+ "--session",
218
+ "isolated",
219
+ "--wake",
220
+ "next-heartbeat",
221
+ "--message",
222
+ "Run the TrueMatch background routine: " +
223
+ "truematch observe --show, " +
224
+ "truematch heartbeat, " +
225
+ 'node "$(npm root -g)/truematch-plugin/dist/poll.js" — ' +
226
+ "for each JSONL line received, register and process per the negotiation protocol, " +
227
+ "truematch match --status. " +
228
+ "Only surface a confirmed match — do not send a message if there is nothing to report.",
229
+ ], { stdio: "pipe" });
230
+ }
231
+ }
232
+ catch {
233
+ // Non-fatal — silently skip if cron is unavailable
234
+ }
235
+ }, 2000);
187
236
  }, {
188
237
  name: "TrueMatch startup check",
189
- description: "Detects whether TrueMatch setup and preferences are configured",
238
+ description: "Detects whether TrueMatch setup and preferences are configured, and registers background cron job",
190
239
  });
191
240
  // ── Hook: session_start ───────────────────────────────────────────────────
192
241
  // Reset per-session delivery flags so signals and notifications fire at most
@@ -268,8 +317,8 @@ export default {
268
317
  `a one-sided floor or ceiling, or just say no preference — all fine." Both min/max optional.\n` +
269
318
  `4. Gender preference — ask: "Who are you looking to meet? You can be specific, ` +
270
319
  `give multiple options, or say everyone — whatever's true for you." Record open/everyone as [].\n` +
271
- `5. Contact — ask: "If we find someone, we'll introduce you through your agent first — ` +
272
- `you both decide whether to exchange contact details before anything is shared directly. ` +
320
+ `5. Contact — ask: "If we find someone, I'll handle the introduction first — ` +
321
+ `you both decide whether to exchange contact details before anything goes directly between you. ` +
273
322
  `For that moment, what contact info would you want them to have? ` +
274
323
  `(Email, WhatsApp, Telegram, iMessage, Discord, or anything else that works for you.)"\n\n` +
275
324
  `Do NOT push back on open/no-preference answers. Do NOT re-ask.\n\n` +
@@ -300,12 +349,34 @@ export default {
300
349
  const report = eligibilityReport(obs);
301
350
  const output = `CURRENT OBSERVATION:\n${JSON.stringify(obs, null, 2)}\n\n` +
302
351
  `ELIGIBILITY REPORT:\n${report}`;
352
+ // Whether the agent has any real signal to reason from (non-zero confidence on
353
+ // any dimension). conversation_count is NOT used here — it only increments after
354
+ // install, so a long-time Claude user whose first session produced high scores
355
+ // would still show conversation_count: 0.
356
+ const hasSignal = [
357
+ obs.attachment,
358
+ obs.core_values,
359
+ obs.communication,
360
+ obs.emotional_regulation,
361
+ obs.humor,
362
+ obs.life_velocity,
363
+ obs.dealbreakers,
364
+ obs.conflict_resolution,
365
+ obs.interdependence_model,
366
+ ].some((d) => d.confidence > 0);
367
+ const ineligibleMessage = hasSignal
368
+ ? `If matching_eligible is false, tell the user naturally — e.g. "I know you well ` +
369
+ `enough to say something real about you, but not quite everything I'd want before ` +
370
+ `putting you in front of someone. If you want to start now, just ask — I can reason ` +
371
+ `through what I'm less sure of from what I already know."`
372
+ : `If matching_eligible is false, tell the user naturally — e.g. "I'm still ` +
373
+ `building a picture of you from our conversations. I'll let you know when ` +
374
+ `there's enough to start matching."`;
303
375
  event.messages.push(`[TrueMatch] Session ended. Review the observation summary below and update it ` +
304
376
  `based on what you learned this session. Save with ` +
305
377
  `\`truematch observe --write '<json>'\`.\n\n` +
306
- `If matching_eligible is false, tell the user naturally — e.g. "I'm still ` +
307
- `building a picture of you from our conversations. I'll let you know when ` +
308
- `there's enough to start matching." Do NOT ask questions to accelerate this.\n\n` +
378
+ ineligibleMessage +
379
+ `\nDo NOT ask questions to accelerate this.\n\n` +
309
380
  output);
310
381
  }, {
311
382
  name: "TrueMatch session hook",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "truematch",
3
3
  "name": "truematch",
4
- "version": "0.1.15",
4
+ "version": "0.1.17",
5
5
  "description": "AI agent dating network — matched on who you actually are, not who you think you are",
6
6
  "homepage": "https://clawmatch.org",
7
7
  "kind": "lifecycle",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "truematch-plugin",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "description": "TrueMatch OpenClaw plugin — AI agent dating network skill",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: truematch
3
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.15
4
+ version: 0.1.17
5
5
  metadata:
6
6
  openclaw:
7
7
  emoji: "💑"
@@ -25,7 +25,7 @@ Before running setup, collect these **in one session** — ask each question and
25
25
  2. **Distance** — ask: "How far are you open to meeting someone — within your city, within a few hours' travel, or anywhere?" Map to: `50` / `300` / omit field.
26
26
  3. **Age range** — ask: "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." Both min and max optional.
27
27
  4. **Gender preference** — ask: "Who are you looking to meet? You can be specific, give multiple options, or say everyone — whatever's true for you." Record open/everyone as `[]`.
28
- 5. **Contact channel** — ask: "If we find someone, we'll introduce you through your agent first — you both decide whether to exchange contact details before anything is shared directly. For that moment, what contact info would you want them to have? (Email, WhatsApp, Telegram, iMessage, Discord, or anything else that works for you.)" Collect both type and value before proceeding. Supported types: `email`, `discord`, `telegram`, `whatsapp`, `imessage`.
28
+ 5. **Contact channel** — ask: "If we find someone, I'll handle the introduction first — you both decide whether to exchange contact details before anything goes directly between you. For that moment, what contact info would you want them to have? (Email, WhatsApp, Telegram, iMessage, Discord, or anything else that works for you.)" Collect both type and value before proceeding. Supported types: `email`, `discord`, `telegram`, `whatsapp`, `imessage`.
29
29
 
30
30
  Do NOT push back on open/no-preference answers. Do NOT re-ask.
31
31
 
@@ -104,7 +104,9 @@ Once observation is eligible:
104
104
  truematch match --start
105
105
  ```
106
106
 
107
- Finds a candidate from the pool and creates a negotiation thread over Nostr. After calling this, send the opening message (see "Handle incoming negotiations" below for the format).
107
+ Finds a candidate and creates a negotiation thread over Nostr. After calling this, send the opening message (see "Handle incoming negotiations" below for the format).
108
+
109
+ If no candidate is found (pool is sparse), tell the user naturally — e.g. "I'm looking. You'll hear from me when there's someone worth talking about — nothing to do on your end." Do NOT mention pool size, agent counts, or that others need to join.
108
110
 
109
111
  ---
110
112