truematch-plugin 0.1.1 → 0.1.3
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/handoff.js +23 -16
- package/dist/identity.d.ts +1 -0
- package/dist/identity.js +17 -7
- package/dist/index.js +10 -1
- package/dist/negotiation.js +12 -7
- package/dist/nostr.js +3 -0
- package/package.json +1 -1
package/dist/handoff.js
CHANGED
|
@@ -14,35 +14,40 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, unlinkSync, readdirSync, } from "node:fs";
|
|
16
16
|
import { join } from "node:path";
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
import { getTrueMatchDir } from "./identity.js";
|
|
18
|
+
// Re-read each call so that TRUEMATCH_DIR_OVERRIDE changes take effect.
|
|
19
|
+
function getNotificationFile() {
|
|
20
|
+
return join(getTrueMatchDir(), "pending_notification.json");
|
|
21
|
+
}
|
|
22
|
+
function getHandoffsDir() {
|
|
23
|
+
return join(getTrueMatchDir(), "handoffs");
|
|
24
|
+
}
|
|
21
25
|
// 72 hours — matches Nostr thread expiry and spec consent window
|
|
22
26
|
const CONSENT_EXPIRY_MS = 72 * 60 * 60 * 1000;
|
|
23
27
|
// ── Pending notification ──────────────────────────────────────────────────────
|
|
24
28
|
export function loadPendingNotification() {
|
|
25
|
-
if (!existsSync(
|
|
29
|
+
if (!existsSync(getNotificationFile()))
|
|
26
30
|
return null;
|
|
27
31
|
try {
|
|
28
|
-
return JSON.parse(readFileSync(
|
|
32
|
+
return JSON.parse(readFileSync(getNotificationFile(), "utf8"));
|
|
29
33
|
}
|
|
30
34
|
catch {
|
|
31
35
|
return null;
|
|
32
36
|
}
|
|
33
37
|
}
|
|
34
38
|
export function savePendingNotification(n) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
const dir = getTrueMatchDir();
|
|
40
|
+
if (!existsSync(dir))
|
|
41
|
+
mkdirSync(dir, { recursive: true });
|
|
42
|
+
writeFileSync(getNotificationFile(), JSON.stringify(n, null, 2), {
|
|
38
43
|
encoding: "utf8",
|
|
39
44
|
mode: 0o600,
|
|
40
45
|
});
|
|
41
46
|
}
|
|
42
47
|
export function deletePendingNotification() {
|
|
43
48
|
try {
|
|
44
|
-
if (existsSync(
|
|
45
|
-
unlinkSync(
|
|
49
|
+
if (existsSync(getNotificationFile()))
|
|
50
|
+
unlinkSync(getNotificationFile());
|
|
46
51
|
}
|
|
47
52
|
catch {
|
|
48
53
|
// ignore
|
|
@@ -58,7 +63,7 @@ export function writePendingNotificationIfMatched(matchId, peerPubkey, narrative
|
|
|
58
63
|
};
|
|
59
64
|
savePendingNotification(n);
|
|
60
65
|
// Create the handoff directory and initial state
|
|
61
|
-
const handoffDir = join(
|
|
66
|
+
const handoffDir = join(getHandoffsDir(), matchId);
|
|
62
67
|
if (!existsSync(handoffDir)) {
|
|
63
68
|
mkdirSync(handoffDir, { recursive: true, mode: 0o700 });
|
|
64
69
|
}
|
|
@@ -77,7 +82,8 @@ export function writePendingNotificationIfMatched(matchId, peerPubkey, narrative
|
|
|
77
82
|
}
|
|
78
83
|
// ── Handoff state ─────────────────────────────────────────────────────────────
|
|
79
84
|
export function loadHandoffState(matchId) {
|
|
80
|
-
const
|
|
85
|
+
const handoffsDir = getHandoffsDir();
|
|
86
|
+
const path = join(handoffsDir, matchId, "state.json");
|
|
81
87
|
if (!existsSync(path))
|
|
82
88
|
return null;
|
|
83
89
|
try {
|
|
@@ -88,7 +94,7 @@ export function loadHandoffState(matchId) {
|
|
|
88
94
|
}
|
|
89
95
|
}
|
|
90
96
|
export function saveHandoffState(state) {
|
|
91
|
-
const dir = join(
|
|
97
|
+
const dir = join(getHandoffsDir(), state.match_id);
|
|
92
98
|
if (!existsSync(dir))
|
|
93
99
|
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
94
100
|
writeFileSync(join(dir, "state.json"), JSON.stringify(state, null, 2), {
|
|
@@ -98,11 +104,12 @@ export function saveHandoffState(state) {
|
|
|
98
104
|
}
|
|
99
105
|
/** Returns all active (non-complete, non-expired) handoff states. */
|
|
100
106
|
export function listActiveHandoffs() {
|
|
101
|
-
|
|
107
|
+
const handoffsDir = getHandoffsDir();
|
|
108
|
+
if (!existsSync(handoffsDir))
|
|
102
109
|
return [];
|
|
103
110
|
const results = [];
|
|
104
111
|
try {
|
|
105
|
-
for (const entry of readdirSync(
|
|
112
|
+
for (const entry of readdirSync(handoffsDir, { withFileTypes: true })) {
|
|
106
113
|
if (!entry.isDirectory())
|
|
107
114
|
continue;
|
|
108
115
|
const state = loadHandoffState(entry.name);
|
package/dist/identity.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { TrueMatchIdentity } from "./types.js";
|
|
2
|
+
export declare function getTrueMatchDir(): string;
|
|
2
3
|
export declare const TRUEMATCH_DIR: string;
|
|
3
4
|
export declare function ensureDir(): Promise<void>;
|
|
4
5
|
export declare function loadIdentity(): Promise<TrueMatchIdentity | null>;
|
package/dist/identity.js
CHANGED
|
@@ -9,17 +9,26 @@ import { generateSecretKey, getPublicKey } from "nostr-tools";
|
|
|
9
9
|
import { bytesToHex, hexToBytes } from "nostr-tools/utils";
|
|
10
10
|
import { schnorr } from "@noble/curves/secp256k1.js";
|
|
11
11
|
import { createHash } from "node:crypto";
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
// Returns the active TrueMatch data directory.
|
|
13
|
+
// When TRUEMATCH_DIR_OVERRIDE is set (e.g. in tests or simulations), that path is used
|
|
14
|
+
// instead of the default ~/.truematch. Reading it each call allows per-agent isolation
|
|
15
|
+
// without reloading modules.
|
|
16
|
+
export function getTrueMatchDir() {
|
|
17
|
+
return process.env["TRUEMATCH_DIR_OVERRIDE"] ?? join(homedir(), ".truematch");
|
|
18
|
+
}
|
|
19
|
+
export const TRUEMATCH_DIR = getTrueMatchDir();
|
|
20
|
+
const IDENTITY_FILE = join(getTrueMatchDir(), "identity.json");
|
|
14
21
|
export async function ensureDir() {
|
|
15
|
-
|
|
16
|
-
|
|
22
|
+
const dir = getTrueMatchDir();
|
|
23
|
+
if (!existsSync(dir)) {
|
|
24
|
+
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
17
25
|
}
|
|
18
26
|
}
|
|
19
27
|
export async function loadIdentity() {
|
|
20
|
-
|
|
28
|
+
const identityFile = join(getTrueMatchDir(), "identity.json");
|
|
29
|
+
if (!existsSync(identityFile))
|
|
21
30
|
return null;
|
|
22
|
-
const raw = await readFile(
|
|
31
|
+
const raw = await readFile(identityFile, "utf8");
|
|
23
32
|
return JSON.parse(raw);
|
|
24
33
|
}
|
|
25
34
|
async function generateIdentity() {
|
|
@@ -31,8 +40,9 @@ async function generateIdentity() {
|
|
|
31
40
|
npub: pubkey,
|
|
32
41
|
created_at: new Date().toISOString(),
|
|
33
42
|
};
|
|
43
|
+
const identityFile = join(getTrueMatchDir(), "identity.json");
|
|
34
44
|
// Write with 0o600 mode atomically — avoids a TOCTOU window between writeFile + chmod
|
|
35
|
-
await writeFile(
|
|
45
|
+
await writeFile(identityFile, JSON.stringify(identity, null, 2), {
|
|
36
46
|
encoding: "utf8",
|
|
37
47
|
mode: 0o600,
|
|
38
48
|
});
|
package/dist/index.js
CHANGED
|
@@ -182,7 +182,16 @@ async function cmdObserve() {
|
|
|
182
182
|
console.error("Invalid JSON");
|
|
183
183
|
process.exit(1);
|
|
184
184
|
}
|
|
185
|
-
|
|
185
|
+
try {
|
|
186
|
+
await saveObservation(obs);
|
|
187
|
+
}
|
|
188
|
+
catch (err) {
|
|
189
|
+
console.error(`Failed to save observation — check JSON schema matches ObservationSummary.\n` +
|
|
190
|
+
`Each dimension needs: { confidence, observation_count, behavioral_context_diversity }\n` +
|
|
191
|
+
`Dimensions: attachment, core_values, communication, emotional_regulation, humor, life_velocity, dealbreakers, conflict_resolution, interdependence_model\n` +
|
|
192
|
+
`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
193
|
+
process.exit(1);
|
|
194
|
+
}
|
|
186
195
|
console.log(`ObservationSummary saved. Eligible: ${isEligible(obs)}`);
|
|
187
196
|
return;
|
|
188
197
|
}
|
package/dist/negotiation.js
CHANGED
|
@@ -2,9 +2,12 @@ import { readFile, writeFile, mkdir, readdir } from "node:fs/promises";
|
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
5
|
-
import {
|
|
5
|
+
import { getTrueMatchDir } from "./identity.js";
|
|
6
6
|
import { publishMessage } from "./nostr.js";
|
|
7
|
-
|
|
7
|
+
// Re-read each call so that TRUEMATCH_DIR_OVERRIDE changes take effect (used in simulation).
|
|
8
|
+
function getThreadsDir() {
|
|
9
|
+
return join(getTrueMatchDir(), "threads");
|
|
10
|
+
}
|
|
8
11
|
// Per spec: threads with no response expire after 72 hours
|
|
9
12
|
const THREAD_EXPIRY_MS = 72 * 60 * 60 * 1000;
|
|
10
13
|
// Maximum active threads allowed from a single unknown peer pubkey.
|
|
@@ -13,8 +16,9 @@ const MAX_INBOUND_THREADS_PER_PEER = 3;
|
|
|
13
16
|
// Maximum rounds before hard termination
|
|
14
17
|
export const MAX_ROUNDS = 10;
|
|
15
18
|
async function ensureThreadsDir() {
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
const dir = getThreadsDir();
|
|
20
|
+
if (!existsSync(dir)) {
|
|
21
|
+
await mkdir(dir, { recursive: true });
|
|
18
22
|
}
|
|
19
23
|
}
|
|
20
24
|
// UUID v4 pattern — all wire-supplied thread IDs must match before being used as filenames
|
|
@@ -23,7 +27,7 @@ function threadFile(thread_id) {
|
|
|
23
27
|
if (!UUID_V4_RE.test(thread_id)) {
|
|
24
28
|
throw new Error(`Invalid thread_id format: ${thread_id}`);
|
|
25
29
|
}
|
|
26
|
-
return join(
|
|
30
|
+
return join(getThreadsDir(), `${thread_id}.json`);
|
|
27
31
|
}
|
|
28
32
|
export async function loadThread(thread_id) {
|
|
29
33
|
const path = threadFile(thread_id);
|
|
@@ -41,13 +45,14 @@ export async function saveThread(state) {
|
|
|
41
45
|
}
|
|
42
46
|
export async function listActiveThreads() {
|
|
43
47
|
await ensureThreadsDir();
|
|
44
|
-
const
|
|
48
|
+
const threadsDir = getThreadsDir();
|
|
49
|
+
const files = await readdir(threadsDir);
|
|
45
50
|
const threads = [];
|
|
46
51
|
for (const f of files) {
|
|
47
52
|
if (!f.endsWith(".json"))
|
|
48
53
|
continue;
|
|
49
54
|
try {
|
|
50
|
-
const raw = await readFile(join(
|
|
55
|
+
const raw = await readFile(join(threadsDir, f), "utf8");
|
|
51
56
|
const t = JSON.parse(raw);
|
|
52
57
|
if (t.status === "in_progress")
|
|
53
58
|
threads.push(t);
|
package/dist/nostr.js
CHANGED
|
@@ -22,6 +22,9 @@ function decryptMessage(recipientNsec, senderNpub, ciphertext) {
|
|
|
22
22
|
return JSON.parse(plaintext);
|
|
23
23
|
}
|
|
24
24
|
export async function publishMessage(senderNsec, recipientNpub, message, relays = DEFAULT_RELAYS) {
|
|
25
|
+
// No relays configured — skip publishing (useful for offline/simulation use).
|
|
26
|
+
if (relays.length === 0)
|
|
27
|
+
return;
|
|
25
28
|
const ciphertext = encryptMessage(senderNsec, recipientNpub, message);
|
|
26
29
|
const eventTemplate = {
|
|
27
30
|
kind: KIND_ENCRYPTED_DM,
|