@prismer/sdk 1.7.1 → 1.7.4
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/LICENSE +21 -0
- package/README.md +515 -190
- package/dist/cli.js +3559 -416
- package/dist/index.d.mts +1100 -4
- package/dist/index.d.ts +1100 -4
- package/dist/index.js +1256 -10
- package/dist/index.mjs +1252 -9
- package/package.json +3 -2
package/dist/index.mjs
CHANGED
|
@@ -196,10 +196,11 @@ var RealtimeWSClient = class extends TypedEmitter {
|
|
|
196
196
|
joinConversation(conversationId) {
|
|
197
197
|
this.sendRaw({ type: "conversation.join", payload: { conversationId } });
|
|
198
198
|
}
|
|
199
|
-
sendMessage(conversationId, content,
|
|
199
|
+
sendMessage(conversationId, content, options) {
|
|
200
|
+
const opts = typeof options === "string" ? { type: options } : options;
|
|
200
201
|
this.sendRaw({
|
|
201
202
|
type: "message.send",
|
|
202
|
-
payload: { conversationId, content, type },
|
|
203
|
+
payload: { conversationId, content, type: opts?.type ?? "text", ...opts?.metadata ? { metadata: opts.metadata } : {}, ...opts?.parentId ? { parentId: opts.parentId } : {} },
|
|
203
204
|
requestId: `msg-${++this.pingCounter}`
|
|
204
205
|
});
|
|
205
206
|
}
|
|
@@ -1206,6 +1207,28 @@ var ENVIRONMENTS = {
|
|
|
1206
1207
|
production: "https://prismer.cloud"
|
|
1207
1208
|
};
|
|
1208
1209
|
|
|
1210
|
+
// src/aip.ts
|
|
1211
|
+
import {
|
|
1212
|
+
AIPIdentity
|
|
1213
|
+
} from "@prismer/aip-sdk";
|
|
1214
|
+
import {
|
|
1215
|
+
publicKeyToDIDKey,
|
|
1216
|
+
didKeyToPublicKey,
|
|
1217
|
+
validateDIDKey
|
|
1218
|
+
} from "@prismer/aip-sdk";
|
|
1219
|
+
import {
|
|
1220
|
+
buildCredential,
|
|
1221
|
+
buildPresentation,
|
|
1222
|
+
verifyCredential,
|
|
1223
|
+
verifyPresentation
|
|
1224
|
+
} from "@prismer/aip-sdk";
|
|
1225
|
+
import {
|
|
1226
|
+
buildDelegation,
|
|
1227
|
+
buildEphemeralDelegation,
|
|
1228
|
+
verifyDelegation,
|
|
1229
|
+
verifyEphemeralDelegation
|
|
1230
|
+
} from "@prismer/aip-sdk";
|
|
1231
|
+
|
|
1209
1232
|
// src/storage.ts
|
|
1210
1233
|
var MemoryStorage = class {
|
|
1211
1234
|
constructor() {
|
|
@@ -2090,20 +2113,27 @@ var PBKDF2_ITERATIONS = 1e5;
|
|
|
2090
2113
|
var SALT_LENGTH = 16;
|
|
2091
2114
|
var IV_LENGTH = 12;
|
|
2092
2115
|
var KEY_LENGTH = 256;
|
|
2093
|
-
var
|
|
2116
|
+
var _E2EEncryption = class _E2EEncryption {
|
|
2094
2117
|
constructor() {
|
|
2095
2118
|
this.masterKey = null;
|
|
2096
2119
|
this.keyPair = null;
|
|
2097
2120
|
this.sessionKeys = /* @__PURE__ */ new Map();
|
|
2098
2121
|
// conversationId → AES key
|
|
2099
2122
|
this.salt = null;
|
|
2123
|
+
// ─── Pipeline Functions ──────────────────────────────────
|
|
2124
|
+
this.messageCount = 0;
|
|
2125
|
+
this.lastRotation = Date.now();
|
|
2100
2126
|
}
|
|
2101
2127
|
/**
|
|
2102
2128
|
* Initialize encryption with user passphrase.
|
|
2103
2129
|
* Derives a master key via PBKDF2 and generates an ECDH key pair.
|
|
2130
|
+
*
|
|
2131
|
+
* @param passphrase - User passphrase for master key derivation
|
|
2132
|
+
* @param salt - Optional Base64-encoded salt. If omitted, a random 16-byte salt is generated.
|
|
2133
|
+
* Store the salt (via exportSalt()) so you can re-derive the same master key later.
|
|
2104
2134
|
*/
|
|
2105
|
-
async init(passphrase) {
|
|
2106
|
-
this.salt = getRandomValues(new Uint8Array(SALT_LENGTH));
|
|
2135
|
+
async init(passphrase, salt) {
|
|
2136
|
+
this.salt = salt ? new Uint8Array(base64ToArrayBuffer(salt)) : getRandomValues(new Uint8Array(SALT_LENGTH));
|
|
2107
2137
|
const passphraseKey = await subtle().importKey(
|
|
2108
2138
|
"raw",
|
|
2109
2139
|
new TextEncoder().encode(passphrase),
|
|
@@ -2114,7 +2144,7 @@ var E2EEncryption = class {
|
|
|
2114
2144
|
this.masterKey = await subtle().deriveKey(
|
|
2115
2145
|
{
|
|
2116
2146
|
name: "PBKDF2",
|
|
2117
|
-
salt: this.salt,
|
|
2147
|
+
salt: new Uint8Array(this.salt.buffer, this.salt.byteOffset, this.salt.byteLength).buffer,
|
|
2118
2148
|
iterations: PBKDF2_ITERATIONS,
|
|
2119
2149
|
hash: "SHA-256"
|
|
2120
2150
|
},
|
|
@@ -2129,6 +2159,14 @@ var E2EEncryption = class {
|
|
|
2129
2159
|
["deriveKey"]
|
|
2130
2160
|
);
|
|
2131
2161
|
}
|
|
2162
|
+
/**
|
|
2163
|
+
* Export the salt as Base64 string for persistent storage.
|
|
2164
|
+
* You must store this and pass it back to init() to re-derive the same master key.
|
|
2165
|
+
*/
|
|
2166
|
+
exportSalt() {
|
|
2167
|
+
if (!this.salt) throw new Error("E2E not initialized. Call init() first.");
|
|
2168
|
+
return arrayBufferToBase64(this.salt.buffer);
|
|
2169
|
+
}
|
|
2132
2170
|
/**
|
|
2133
2171
|
* Export public key for sharing with conversation peers.
|
|
2134
2172
|
*/
|
|
@@ -2241,8 +2279,93 @@ var E2EEncryption = class {
|
|
|
2241
2279
|
this.keyPair = null;
|
|
2242
2280
|
this.sessionKeys.clear();
|
|
2243
2281
|
this.salt = null;
|
|
2282
|
+
this.messageCount = 0;
|
|
2283
|
+
}
|
|
2284
|
+
/**
|
|
2285
|
+
* High-level encrypt-for-send pipeline.
|
|
2286
|
+
* Encrypts content, builds metadata, and handles key rotation.
|
|
2287
|
+
*
|
|
2288
|
+
* Returns { encryptedContent, metadata } ready to send.
|
|
2289
|
+
*/
|
|
2290
|
+
async encryptForSend(conversationId, content) {
|
|
2291
|
+
if (!this.hasSessionKey(conversationId)) {
|
|
2292
|
+
throw new Error("No session key for this conversation. Call deriveSessionKey() first.");
|
|
2293
|
+
}
|
|
2294
|
+
const needsRotation = this.shouldRotateKey();
|
|
2295
|
+
const encryptedContent = await this.encrypt(conversationId, content);
|
|
2296
|
+
this.messageCount++;
|
|
2297
|
+
return {
|
|
2298
|
+
encryptedContent,
|
|
2299
|
+
metadata: {
|
|
2300
|
+
encrypted: true,
|
|
2301
|
+
encryptionVersion: 1,
|
|
2302
|
+
...needsRotation && { keyRotationRequested: true }
|
|
2303
|
+
}
|
|
2304
|
+
};
|
|
2305
|
+
}
|
|
2306
|
+
/**
|
|
2307
|
+
* High-level decrypt-on-receive pipeline.
|
|
2308
|
+
* Decrypts content and validates metadata.
|
|
2309
|
+
*/
|
|
2310
|
+
async decryptOnReceive(conversationId, encryptedContent, metadata) {
|
|
2311
|
+
if (!this.hasSessionKey(conversationId)) {
|
|
2312
|
+
throw new Error("No session key for this conversation. Call deriveSessionKey() first.");
|
|
2313
|
+
}
|
|
2314
|
+
return this.decrypt(conversationId, encryptedContent);
|
|
2315
|
+
}
|
|
2316
|
+
/**
|
|
2317
|
+
* High-level file encryption pipeline.
|
|
2318
|
+
*/
|
|
2319
|
+
async encryptFile(conversationId, fileData) {
|
|
2320
|
+
const base64Data = arrayBufferToBase64(fileData);
|
|
2321
|
+
const encryptedData = await this.encrypt(conversationId, base64Data);
|
|
2322
|
+
return {
|
|
2323
|
+
encryptedData,
|
|
2324
|
+
metadata: {
|
|
2325
|
+
encrypted: true,
|
|
2326
|
+
encryptionVersion: 1,
|
|
2327
|
+
fileEncrypted: true
|
|
2328
|
+
}
|
|
2329
|
+
};
|
|
2330
|
+
}
|
|
2331
|
+
/**
|
|
2332
|
+
* High-level file decryption pipeline.
|
|
2333
|
+
*/
|
|
2334
|
+
async decryptFile(conversationId, encryptedData) {
|
|
2335
|
+
const base64Data = await this.decrypt(conversationId, encryptedData);
|
|
2336
|
+
return base64ToArrayBuffer(base64Data);
|
|
2337
|
+
}
|
|
2338
|
+
/**
|
|
2339
|
+
* Check if key rotation is needed (1000 messages or 24 hours).
|
|
2340
|
+
*/
|
|
2341
|
+
shouldRotateKey() {
|
|
2342
|
+
if (this.messageCount >= _E2EEncryption.KEY_ROTATION_THRESHOLD) {
|
|
2343
|
+
return true;
|
|
2344
|
+
}
|
|
2345
|
+
if (Date.now() - this.lastRotation >= _E2EEncryption.KEY_ROTATION_INTERVAL_MS) {
|
|
2346
|
+
return true;
|
|
2347
|
+
}
|
|
2348
|
+
return false;
|
|
2349
|
+
}
|
|
2350
|
+
/**
|
|
2351
|
+
* Perform key rotation: generate new ECDH keypair and reset counters.
|
|
2352
|
+
* The caller is responsible for re-exchanging keys with peers.
|
|
2353
|
+
*/
|
|
2354
|
+
async rotateKeys() {
|
|
2355
|
+
this.keyPair = await subtle().generateKey(
|
|
2356
|
+
{ name: "ECDH", namedCurve: "P-256" },
|
|
2357
|
+
false,
|
|
2358
|
+
["deriveKey"]
|
|
2359
|
+
);
|
|
2360
|
+
this.messageCount = 0;
|
|
2361
|
+
this.lastRotation = Date.now();
|
|
2362
|
+
this.sessionKeys.clear();
|
|
2363
|
+
return this.exportPublicKey();
|
|
2244
2364
|
}
|
|
2245
2365
|
};
|
|
2366
|
+
_E2EEncryption.KEY_ROTATION_THRESHOLD = 1e3;
|
|
2367
|
+
_E2EEncryption.KEY_ROTATION_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
2368
|
+
var E2EEncryption = _E2EEncryption;
|
|
2246
2369
|
function arrayBufferToBase64(buffer) {
|
|
2247
2370
|
if (typeof btoa !== "undefined") {
|
|
2248
2371
|
const bytes = new Uint8Array(buffer);
|
|
@@ -2267,6 +2390,548 @@ function base64ToArrayBuffer(base64) {
|
|
|
2267
2390
|
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
2268
2391
|
}
|
|
2269
2392
|
|
|
2393
|
+
// src/encryption-pipeline.ts
|
|
2394
|
+
async function encryptForSend(e2e, conversationId, content, metadata) {
|
|
2395
|
+
if (!e2e.hasSessionKey(conversationId)) {
|
|
2396
|
+
return { content, metadata: metadata ?? {} };
|
|
2397
|
+
}
|
|
2398
|
+
const ciphertext = await e2e.encrypt(conversationId, content);
|
|
2399
|
+
return {
|
|
2400
|
+
content: ciphertext,
|
|
2401
|
+
metadata: { ...metadata, encrypted: true, encKeyId: `conv-${conversationId}` }
|
|
2402
|
+
};
|
|
2403
|
+
}
|
|
2404
|
+
async function decryptOnReceive(e2e, conversationId, content, metadata) {
|
|
2405
|
+
if (!metadata?.encrypted) {
|
|
2406
|
+
return { content, decrypted: false };
|
|
2407
|
+
}
|
|
2408
|
+
if (!e2e.hasSessionKey(conversationId)) {
|
|
2409
|
+
return { content, decrypted: false, error: "no_session_key" };
|
|
2410
|
+
}
|
|
2411
|
+
try {
|
|
2412
|
+
const plain = await e2e.decrypt(conversationId, content);
|
|
2413
|
+
return { content: plain, decrypted: true };
|
|
2414
|
+
} catch (err) {
|
|
2415
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2416
|
+
return { content, decrypted: false, error: message };
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
async function encryptFile(e2e, conversationId, data) {
|
|
2420
|
+
if (!e2e.hasSessionKey(conversationId)) return null;
|
|
2421
|
+
const b64 = typeof Buffer !== "undefined" ? Buffer.from(data).toString("base64") : uint8ArrayToBase64(data);
|
|
2422
|
+
const ciphertext = await e2e.encrypt(conversationId, b64);
|
|
2423
|
+
return {
|
|
2424
|
+
data: ciphertext,
|
|
2425
|
+
metadata: { encrypted: true, encKeyId: `conv-${conversationId}` }
|
|
2426
|
+
};
|
|
2427
|
+
}
|
|
2428
|
+
async function decryptFile(e2e, conversationId, ciphertext) {
|
|
2429
|
+
if (!e2e.hasSessionKey(conversationId)) return null;
|
|
2430
|
+
try {
|
|
2431
|
+
const b64 = await e2e.decrypt(conversationId, ciphertext);
|
|
2432
|
+
if (typeof Buffer !== "undefined") {
|
|
2433
|
+
return new Uint8Array(Buffer.from(b64, "base64"));
|
|
2434
|
+
}
|
|
2435
|
+
return base64ToUint8Array(b64);
|
|
2436
|
+
} catch {
|
|
2437
|
+
return null;
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
async function encryptContext(e2e, content, contextId = "context-cache") {
|
|
2441
|
+
if (!e2e.hasSessionKey(contextId)) return null;
|
|
2442
|
+
const ciphertext = await e2e.encrypt(contextId, content);
|
|
2443
|
+
return { content: ciphertext, encrypted: true };
|
|
2444
|
+
}
|
|
2445
|
+
async function decryptContext(e2e, ciphertext, contextId = "context-cache") {
|
|
2446
|
+
if (!e2e.hasSessionKey(contextId)) return null;
|
|
2447
|
+
try {
|
|
2448
|
+
return await e2e.decrypt(contextId, ciphertext);
|
|
2449
|
+
} catch {
|
|
2450
|
+
return null;
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
async function decryptMessages(e2e, messages, conversationId) {
|
|
2454
|
+
let decryptedCount = 0;
|
|
2455
|
+
const errors = [];
|
|
2456
|
+
for (let i = 0; i < messages.length; i++) {
|
|
2457
|
+
const msg = messages[i];
|
|
2458
|
+
const convId = conversationId ?? msg.conversationId;
|
|
2459
|
+
if (!convId) continue;
|
|
2460
|
+
const result = await decryptOnReceive(e2e, convId, msg.content, msg.metadata);
|
|
2461
|
+
if (result.decrypted) {
|
|
2462
|
+
msg.content = result.content;
|
|
2463
|
+
if (msg.metadata) {
|
|
2464
|
+
msg.metadata._decrypted = true;
|
|
2465
|
+
}
|
|
2466
|
+
decryptedCount++;
|
|
2467
|
+
} else if (result.error) {
|
|
2468
|
+
errors.push({ index: i, error: result.error });
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
return { decryptedCount, errors };
|
|
2472
|
+
}
|
|
2473
|
+
function uint8ArrayToBase64(bytes) {
|
|
2474
|
+
let binary = "";
|
|
2475
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
2476
|
+
binary += String.fromCharCode(bytes[i]);
|
|
2477
|
+
}
|
|
2478
|
+
return btoa(binary);
|
|
2479
|
+
}
|
|
2480
|
+
function base64ToUint8Array(b64) {
|
|
2481
|
+
const binary = atob(b64);
|
|
2482
|
+
const bytes = new Uint8Array(binary.length);
|
|
2483
|
+
for (let i = 0; i < binary.length; i++) {
|
|
2484
|
+
bytes[i] = binary.charCodeAt(i);
|
|
2485
|
+
}
|
|
2486
|
+
return bytes;
|
|
2487
|
+
}
|
|
2488
|
+
|
|
2489
|
+
// src/evolution-cache.ts
|
|
2490
|
+
var EvolutionCache = class {
|
|
2491
|
+
constructor() {
|
|
2492
|
+
this._genes = /* @__PURE__ */ new Map();
|
|
2493
|
+
this._edges = /* @__PURE__ */ new Map();
|
|
2494
|
+
// key = signal_key
|
|
2495
|
+
this._globalPrior = /* @__PURE__ */ new Map();
|
|
2496
|
+
this._cursor = 0;
|
|
2497
|
+
}
|
|
2498
|
+
get cursor() {
|
|
2499
|
+
return this._cursor;
|
|
2500
|
+
}
|
|
2501
|
+
get geneCount() {
|
|
2502
|
+
return this._genes.size;
|
|
2503
|
+
}
|
|
2504
|
+
/** Load from a full snapshot */
|
|
2505
|
+
loadSnapshot(snapshot) {
|
|
2506
|
+
this._genes.clear();
|
|
2507
|
+
this._edges.clear();
|
|
2508
|
+
this._globalPrior.clear();
|
|
2509
|
+
for (const gene of snapshot.genes) {
|
|
2510
|
+
this._genes.set(gene.id, gene);
|
|
2511
|
+
}
|
|
2512
|
+
for (const edge of snapshot.edges) {
|
|
2513
|
+
const list = this._edges.get(edge.signal_key) ?? [];
|
|
2514
|
+
list.push(edge);
|
|
2515
|
+
this._edges.set(edge.signal_key, list);
|
|
2516
|
+
}
|
|
2517
|
+
for (const [key, val] of Object.entries(snapshot.globalPrior)) {
|
|
2518
|
+
this._globalPrior.set(key, val);
|
|
2519
|
+
}
|
|
2520
|
+
this._cursor = snapshot.cursor;
|
|
2521
|
+
}
|
|
2522
|
+
/** Apply incremental delta (alias: loadDelta) */
|
|
2523
|
+
applyDelta(delta) {
|
|
2524
|
+
const pulled = delta.pulled;
|
|
2525
|
+
for (const gene of pulled.genes) {
|
|
2526
|
+
this._genes.set(gene.id, gene);
|
|
2527
|
+
}
|
|
2528
|
+
for (const id of pulled.quarantines) {
|
|
2529
|
+
this._genes.delete(id);
|
|
2530
|
+
}
|
|
2531
|
+
for (const edge of pulled.edges) {
|
|
2532
|
+
const list = this._edges.get(edge.signal_key) ?? [];
|
|
2533
|
+
const idx = list.findIndex((e) => e.gene_id === edge.gene_id);
|
|
2534
|
+
if (idx >= 0) list[idx] = edge;
|
|
2535
|
+
else list.push(edge);
|
|
2536
|
+
this._edges.set(edge.signal_key, list);
|
|
2537
|
+
}
|
|
2538
|
+
for (const [key, val] of Object.entries(pulled.globalPrior)) {
|
|
2539
|
+
this._globalPrior.set(key, val);
|
|
2540
|
+
}
|
|
2541
|
+
this._cursor = pulled.cursor;
|
|
2542
|
+
}
|
|
2543
|
+
/** Apply incremental delta (alias for applyDelta) */
|
|
2544
|
+
loadDelta(delta) {
|
|
2545
|
+
this.applyDelta(delta);
|
|
2546
|
+
}
|
|
2547
|
+
/** Select best gene locally using Thompson Sampling — pure CPU, <1ms */
|
|
2548
|
+
selectGene(signals) {
|
|
2549
|
+
if (this._genes.size === 0) {
|
|
2550
|
+
return { action: "none", confidence: 0, reason: "no genes in cache", fromCache: true };
|
|
2551
|
+
}
|
|
2552
|
+
const signalKeys = signals.map((s) => s.type);
|
|
2553
|
+
const candidates = [];
|
|
2554
|
+
for (const gene of this._genes.values()) {
|
|
2555
|
+
if (gene.visibility === "quarantined") continue;
|
|
2556
|
+
const geneSignalTypes = (gene.signals_match || []).map(
|
|
2557
|
+
(s) => typeof s === "string" ? s : s.type
|
|
2558
|
+
);
|
|
2559
|
+
if (geneSignalTypes.length === 0) continue;
|
|
2560
|
+
const matchCount = signalKeys.filter((k) => geneSignalTypes.includes(k)).length;
|
|
2561
|
+
const coverageScore = matchCount / geneSignalTypes.length;
|
|
2562
|
+
if (coverageScore === 0) continue;
|
|
2563
|
+
let alpha = gene.success_count + 1;
|
|
2564
|
+
let beta = gene.failure_count + 1;
|
|
2565
|
+
for (const key of signalKeys) {
|
|
2566
|
+
const prior = this._globalPrior.get(key);
|
|
2567
|
+
if (prior) {
|
|
2568
|
+
alpha += 0.3 * prior.alpha;
|
|
2569
|
+
beta += 0.3 * prior.beta;
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
const sampledScore = alpha / (alpha + beta);
|
|
2573
|
+
const totalObs = gene.success_count + gene.failure_count;
|
|
2574
|
+
if (totalObs >= 10 && gene.success_count / totalObs < 0.18) continue;
|
|
2575
|
+
const rankScore = coverageScore * 0.4 + sampledScore * 0.6;
|
|
2576
|
+
candidates.push({ gene, rankScore, coverageScore, sampledScore });
|
|
2577
|
+
}
|
|
2578
|
+
if (candidates.length === 0) {
|
|
2579
|
+
return {
|
|
2580
|
+
action: "create_suggested",
|
|
2581
|
+
confidence: 0,
|
|
2582
|
+
reason: "no matching genes for signals",
|
|
2583
|
+
fromCache: true
|
|
2584
|
+
};
|
|
2585
|
+
}
|
|
2586
|
+
candidates.sort((a, b) => b.rankScore - a.rankScore);
|
|
2587
|
+
const best = candidates[0];
|
|
2588
|
+
const alternatives = candidates.slice(1, 4).map((c) => ({
|
|
2589
|
+
gene_id: c.gene.id,
|
|
2590
|
+
confidence: Math.round(c.rankScore * 100) / 100,
|
|
2591
|
+
title: c.gene.title
|
|
2592
|
+
}));
|
|
2593
|
+
return {
|
|
2594
|
+
action: "apply_gene",
|
|
2595
|
+
gene_id: best.gene.id,
|
|
2596
|
+
gene: best.gene,
|
|
2597
|
+
strategy: best.gene.strategy,
|
|
2598
|
+
confidence: Math.round(best.rankScore * 100) / 100,
|
|
2599
|
+
coverageScore: Math.round(best.coverageScore * 100) / 100,
|
|
2600
|
+
alternatives,
|
|
2601
|
+
reason: `local cache selection (${this._genes.size} genes)`,
|
|
2602
|
+
fromCache: true
|
|
2603
|
+
};
|
|
2604
|
+
}
|
|
2605
|
+
};
|
|
2606
|
+
|
|
2607
|
+
// src/signal-enrichment.ts
|
|
2608
|
+
var ERROR_PATTERNS = [
|
|
2609
|
+
{ pattern: /timeout|timed?\s*out|deadline\s*exceeded|context\s*deadline/i, type: "timeout" },
|
|
2610
|
+
{ pattern: /econnrefused|connection\s*refused/i, type: "connection_refused" },
|
|
2611
|
+
{ pattern: /enotfound|dns|getaddrinfo|resolve/i, type: "dns_error" },
|
|
2612
|
+
{ pattern: /rate\s*limit|too\s*many\s*requests|429/i, type: "rate_limit" },
|
|
2613
|
+
{ pattern: /401|unauthorized|unauthenticated|auth.*fail/i, type: "auth_error" },
|
|
2614
|
+
{ pattern: /403|forbidden|access\s*denied|permission/i, type: "permission_error" },
|
|
2615
|
+
{ pattern: /404|not\s*found/i, type: "not_found" },
|
|
2616
|
+
{ pattern: /5\d{2}|internal\s*server|server\s*error|502|503|504/i, type: "server_error" },
|
|
2617
|
+
{ pattern: /type\s*error|typeerror/i, type: "type_error" },
|
|
2618
|
+
{ pattern: /syntax\s*error|syntaxerror|unexpected\s*token/i, type: "syntax_error" },
|
|
2619
|
+
{ pattern: /reference\s*error|referenceerror|is\s*not\s*defined/i, type: "reference_error" },
|
|
2620
|
+
{ pattern: /out\s*of\s*memory|oom|heap|allocation\s*failed/i, type: "oom" },
|
|
2621
|
+
{ pattern: /crash|panic|segfault|sigsegv|sigabrt/i, type: "crash" },
|
|
2622
|
+
{ pattern: /quota|limit\s*exceeded|insufficient/i, type: "quota_exceeded" },
|
|
2623
|
+
{ pattern: /tls|ssl|certificate|cert\s*verify/i, type: "tls_error" },
|
|
2624
|
+
{ pattern: /deadlock|lock\s*timeout|lock\s*wait/i, type: "deadlock" }
|
|
2625
|
+
];
|
|
2626
|
+
function extractSignals(ctx) {
|
|
2627
|
+
const tags = [];
|
|
2628
|
+
if (ctx.error) {
|
|
2629
|
+
let matched = false;
|
|
2630
|
+
for (const { pattern, type } of ERROR_PATTERNS) {
|
|
2631
|
+
if (pattern.test(ctx.error)) {
|
|
2632
|
+
const tag = { type: `error:${type}` };
|
|
2633
|
+
if (ctx.provider) tag.provider = ctx.provider;
|
|
2634
|
+
if (ctx.stage) tag.stage = ctx.stage;
|
|
2635
|
+
if (ctx.severity) tag.severity = ctx.severity;
|
|
2636
|
+
tags.push(tag);
|
|
2637
|
+
matched = true;
|
|
2638
|
+
break;
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
if (!matched) {
|
|
2642
|
+
const normalized = ctx.error.slice(0, 50).toLowerCase().replace(/[^a-z0-9_]/g, "_");
|
|
2643
|
+
const tag = { type: `error:${normalized}` };
|
|
2644
|
+
if (ctx.provider) tag.provider = ctx.provider;
|
|
2645
|
+
if (ctx.stage) tag.stage = ctx.stage;
|
|
2646
|
+
tags.push(tag);
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2649
|
+
if (ctx.taskStatus === "failed") tags.push({ type: "task.failed" });
|
|
2650
|
+
if (ctx.taskStatus === "completed") tags.push({ type: "task.completed" });
|
|
2651
|
+
if (ctx.taskCapability) {
|
|
2652
|
+
tags.push({ type: `capability:${ctx.taskCapability}` });
|
|
2653
|
+
}
|
|
2654
|
+
if (ctx.tags) {
|
|
2655
|
+
for (const tag of ctx.tags) {
|
|
2656
|
+
tags.push({ type: tag });
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
return tags;
|
|
2660
|
+
}
|
|
2661
|
+
function createEnrichedExtractor(config) {
|
|
2662
|
+
if (config.mode === "rules") {
|
|
2663
|
+
return async (ctx) => extractSignals(ctx);
|
|
2664
|
+
}
|
|
2665
|
+
const { llmExtract, timeoutMs = 3e3 } = config;
|
|
2666
|
+
if (!llmExtract) return async (ctx) => extractSignals(ctx);
|
|
2667
|
+
return async (ctx) => {
|
|
2668
|
+
try {
|
|
2669
|
+
const result = await Promise.race([
|
|
2670
|
+
llmExtract(ctx),
|
|
2671
|
+
new Promise(
|
|
2672
|
+
(_, reject) => setTimeout(() => reject(new Error("llm_timeout")), timeoutMs)
|
|
2673
|
+
)
|
|
2674
|
+
]);
|
|
2675
|
+
return result;
|
|
2676
|
+
} catch {
|
|
2677
|
+
return extractSignals(ctx);
|
|
2678
|
+
}
|
|
2679
|
+
};
|
|
2680
|
+
}
|
|
2681
|
+
|
|
2682
|
+
// src/evolution-runtime.ts
|
|
2683
|
+
var EvolutionRuntime = class {
|
|
2684
|
+
constructor(client, config) {
|
|
2685
|
+
this.client = client;
|
|
2686
|
+
this.outbox = [];
|
|
2687
|
+
this.started = false;
|
|
2688
|
+
// Session tracking
|
|
2689
|
+
this._sessions = [];
|
|
2690
|
+
this._sessionCounter = 0;
|
|
2691
|
+
this.config = {
|
|
2692
|
+
syncIntervalMs: config?.syncIntervalMs ?? 6e4,
|
|
2693
|
+
enrichment: config?.enrichment ?? { mode: "rules" },
|
|
2694
|
+
scope: config?.scope ?? "global",
|
|
2695
|
+
outboxMaxSize: config?.outboxMaxSize ?? 50,
|
|
2696
|
+
outboxFlushMs: config?.outboxFlushMs ?? 5e3
|
|
2697
|
+
};
|
|
2698
|
+
this.scope = this.config.scope;
|
|
2699
|
+
this.cache = new EvolutionCache();
|
|
2700
|
+
this.enricher = config?.enrichment ? createEnrichedExtractor(config.enrichment) : async (ctx) => extractSignals(ctx);
|
|
2701
|
+
}
|
|
2702
|
+
// ─── Lifecycle ──────────────────────────────────────
|
|
2703
|
+
/** Initialize: load snapshot + start sync + start outbox flush */
|
|
2704
|
+
async start() {
|
|
2705
|
+
if (this.started) return;
|
|
2706
|
+
this.started = true;
|
|
2707
|
+
try {
|
|
2708
|
+
const snapshot = await this.client.getSyncSnapshot(0);
|
|
2709
|
+
if (snapshot.data) {
|
|
2710
|
+
this.cache.loadSnapshot(snapshot.data);
|
|
2711
|
+
}
|
|
2712
|
+
} catch {
|
|
2713
|
+
}
|
|
2714
|
+
if (this.config.syncIntervalMs > 0) {
|
|
2715
|
+
this.syncTimer = setInterval(() => this.sync(), this.config.syncIntervalMs);
|
|
2716
|
+
}
|
|
2717
|
+
this.flushTimer = setInterval(() => this.flush(), this.config.outboxFlushMs);
|
|
2718
|
+
}
|
|
2719
|
+
/** Stop: clear timers + flush remaining outbox */
|
|
2720
|
+
async stop() {
|
|
2721
|
+
if (this.syncTimer) clearInterval(this.syncTimer);
|
|
2722
|
+
if (this.flushTimer) clearInterval(this.flushTimer);
|
|
2723
|
+
await this.flush();
|
|
2724
|
+
this.started = false;
|
|
2725
|
+
}
|
|
2726
|
+
// ─── High-Level API ─────────────────────────────────
|
|
2727
|
+
/**
|
|
2728
|
+
* Get a strategy recommendation for an error/context.
|
|
2729
|
+
*
|
|
2730
|
+
* Flow: extract signals → try local cache (<1ms) → fallback to server (~30ms)
|
|
2731
|
+
*
|
|
2732
|
+
* @param error - Error message or Error object
|
|
2733
|
+
* @param context - Optional additional context (provider, stage, etc.)
|
|
2734
|
+
*/
|
|
2735
|
+
async suggest(error, context) {
|
|
2736
|
+
const errorStr = error instanceof Error ? error.message : error;
|
|
2737
|
+
const ctx = {
|
|
2738
|
+
error: errorStr,
|
|
2739
|
+
...context
|
|
2740
|
+
};
|
|
2741
|
+
const signals = await this.enricher(ctx);
|
|
2742
|
+
if (signals.length === 0) {
|
|
2743
|
+
return {
|
|
2744
|
+
action: "none",
|
|
2745
|
+
confidence: 0,
|
|
2746
|
+
signals: [],
|
|
2747
|
+
fromCache: false,
|
|
2748
|
+
reason: "no signals extracted from error"
|
|
2749
|
+
};
|
|
2750
|
+
}
|
|
2751
|
+
const buildSuggestion = (action, geneId, gene, strategy, confidence, fromCache, reason, alternatives) => {
|
|
2752
|
+
this.lastSuggestedGeneId = geneId;
|
|
2753
|
+
this._activeSession = {
|
|
2754
|
+
id: `ses_${++this._sessionCounter}_${Date.now()}`,
|
|
2755
|
+
suggestedAt: Date.now(),
|
|
2756
|
+
suggestedGeneId: geneId,
|
|
2757
|
+
signals,
|
|
2758
|
+
adopted: false,
|
|
2759
|
+
confidence,
|
|
2760
|
+
fromCache
|
|
2761
|
+
};
|
|
2762
|
+
return {
|
|
2763
|
+
action,
|
|
2764
|
+
geneId,
|
|
2765
|
+
gene,
|
|
2766
|
+
strategy,
|
|
2767
|
+
confidence,
|
|
2768
|
+
signals,
|
|
2769
|
+
fromCache,
|
|
2770
|
+
reason,
|
|
2771
|
+
alternatives
|
|
2772
|
+
};
|
|
2773
|
+
};
|
|
2774
|
+
if (this.cache.geneCount > 0) {
|
|
2775
|
+
const local = this.cache.selectGene(signals);
|
|
2776
|
+
if (local.action === "apply_gene" && local.confidence > 0.3) {
|
|
2777
|
+
return buildSuggestion(
|
|
2778
|
+
local.action,
|
|
2779
|
+
local.gene_id,
|
|
2780
|
+
local.gene,
|
|
2781
|
+
local.strategy,
|
|
2782
|
+
local.confidence,
|
|
2783
|
+
true,
|
|
2784
|
+
local.reason,
|
|
2785
|
+
local.alternatives
|
|
2786
|
+
);
|
|
2787
|
+
}
|
|
2788
|
+
}
|
|
2789
|
+
try {
|
|
2790
|
+
const result = await this.client.analyze({
|
|
2791
|
+
signals,
|
|
2792
|
+
scope: this.scope
|
|
2793
|
+
});
|
|
2794
|
+
if (result.data) {
|
|
2795
|
+
return buildSuggestion(
|
|
2796
|
+
result.data.action,
|
|
2797
|
+
result.data.gene_id,
|
|
2798
|
+
result.data.gene,
|
|
2799
|
+
result.data.strategy,
|
|
2800
|
+
result.data.confidence ?? 0,
|
|
2801
|
+
false,
|
|
2802
|
+
result.data.reason,
|
|
2803
|
+
result.data.alternatives
|
|
2804
|
+
);
|
|
2805
|
+
}
|
|
2806
|
+
} catch {
|
|
2807
|
+
const local = this.cache.selectGene(signals);
|
|
2808
|
+
return buildSuggestion(
|
|
2809
|
+
local.action,
|
|
2810
|
+
local.gene_id,
|
|
2811
|
+
local.gene,
|
|
2812
|
+
local.strategy,
|
|
2813
|
+
local.confidence,
|
|
2814
|
+
true,
|
|
2815
|
+
"server unreachable, using cache fallback",
|
|
2816
|
+
local.alternatives
|
|
2817
|
+
);
|
|
2818
|
+
}
|
|
2819
|
+
return {
|
|
2820
|
+
action: "none",
|
|
2821
|
+
confidence: 0,
|
|
2822
|
+
signals,
|
|
2823
|
+
fromCache: false,
|
|
2824
|
+
reason: "no recommendation from server"
|
|
2825
|
+
};
|
|
2826
|
+
}
|
|
2827
|
+
/**
|
|
2828
|
+
* Record an outcome. Fire-and-forget — never blocks, never throws.
|
|
2829
|
+
*
|
|
2830
|
+
* @param error - The error that was encountered
|
|
2831
|
+
* @param outcome - 'success' or 'failed'
|
|
2832
|
+
* @param summary - One-line summary of what happened
|
|
2833
|
+
* @param geneId - Gene that was used (auto-detected from last suggest() if omitted)
|
|
2834
|
+
*/
|
|
2835
|
+
learned(error, outcome, summary, geneId, metadata) {
|
|
2836
|
+
const errorStr = error instanceof Error ? error.message : error;
|
|
2837
|
+
const ctx = { error: errorStr };
|
|
2838
|
+
const signals = extractSignals(ctx);
|
|
2839
|
+
const resolvedGeneId = geneId || this.lastSuggestedGeneId;
|
|
2840
|
+
if (!resolvedGeneId) return;
|
|
2841
|
+
if (this._activeSession) {
|
|
2842
|
+
const session = this._activeSession;
|
|
2843
|
+
session.usedGeneId = resolvedGeneId;
|
|
2844
|
+
session.adopted = resolvedGeneId === session.suggestedGeneId;
|
|
2845
|
+
session.completedAt = Date.now();
|
|
2846
|
+
session.outcome = outcome;
|
|
2847
|
+
session.durationMs = session.completedAt - session.suggestedAt;
|
|
2848
|
+
this._sessions.push(session);
|
|
2849
|
+
this._activeSession = void 0;
|
|
2850
|
+
}
|
|
2851
|
+
this.outbox.push({
|
|
2852
|
+
geneId: resolvedGeneId,
|
|
2853
|
+
signals,
|
|
2854
|
+
outcome,
|
|
2855
|
+
summary,
|
|
2856
|
+
metadata,
|
|
2857
|
+
timestamp: Date.now(),
|
|
2858
|
+
sessionId: this._sessions[this._sessions.length - 1]?.id
|
|
2859
|
+
});
|
|
2860
|
+
if (this.outbox.length >= this.config.outboxMaxSize) {
|
|
2861
|
+
this.flush().catch(() => {
|
|
2862
|
+
});
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
// ─── Session Metrics ────────────────────────────────
|
|
2866
|
+
/** Get all completed sessions. */
|
|
2867
|
+
get sessions() {
|
|
2868
|
+
return this._sessions;
|
|
2869
|
+
}
|
|
2870
|
+
/** Get aggregate metrics for benchmarking. */
|
|
2871
|
+
getMetrics() {
|
|
2872
|
+
const sessions = this._sessions;
|
|
2873
|
+
const totalSuggestions = sessions.length;
|
|
2874
|
+
const suggestionsWithGene = sessions.filter((s) => s.suggestedGeneId).length;
|
|
2875
|
+
const totalLearned = sessions.filter((s) => s.completedAt).length;
|
|
2876
|
+
const adoptedSessions = sessions.filter((s) => s.adopted && s.completedAt);
|
|
2877
|
+
const adoptedCount = adoptedSessions.length;
|
|
2878
|
+
const nonAdopted = sessions.filter((s) => !s.adopted && s.completedAt);
|
|
2879
|
+
const durations = sessions.filter((s) => s.durationMs != null).map((s) => s.durationMs);
|
|
2880
|
+
const avgDurationMs = durations.length > 0 ? Math.round(durations.reduce((a, b) => a + b, 0) / durations.length) : 0;
|
|
2881
|
+
const adoptedSuccess = adoptedSessions.filter((s) => s.outcome === "success").length;
|
|
2882
|
+
const nonAdoptedSuccess = nonAdopted.filter((s) => s.outcome === "success").length;
|
|
2883
|
+
const cacheHits = sessions.filter((s) => s.fromCache).length;
|
|
2884
|
+
return {
|
|
2885
|
+
totalSuggestions,
|
|
2886
|
+
suggestionsWithGene,
|
|
2887
|
+
totalLearned,
|
|
2888
|
+
adoptedCount,
|
|
2889
|
+
geneUtilizationRate: suggestionsWithGene > 0 ? Math.round(adoptedCount / suggestionsWithGene * 100) / 100 : 0,
|
|
2890
|
+
avgDurationMs,
|
|
2891
|
+
adoptedSuccessRate: adoptedCount > 0 ? Math.round(adoptedSuccess / adoptedCount * 100) / 100 : 0,
|
|
2892
|
+
nonAdoptedSuccessRate: nonAdopted.length > 0 ? Math.round(nonAdoptedSuccess / nonAdopted.length * 100) / 100 : 0,
|
|
2893
|
+
cacheHitRate: totalSuggestions > 0 ? Math.round(cacheHits / totalSuggestions * 100) / 100 : 0
|
|
2894
|
+
};
|
|
2895
|
+
}
|
|
2896
|
+
/** Reset session history. */
|
|
2897
|
+
resetMetrics() {
|
|
2898
|
+
this._sessions = [];
|
|
2899
|
+
}
|
|
2900
|
+
// ─── Internal ───────────────────────────────────────
|
|
2901
|
+
/** Sync cache with server */
|
|
2902
|
+
async sync() {
|
|
2903
|
+
try {
|
|
2904
|
+
const result = await this.client.sync({
|
|
2905
|
+
pull: { since: this.cache.cursor },
|
|
2906
|
+
scope: this.scope
|
|
2907
|
+
});
|
|
2908
|
+
if (result.data?.pulled) {
|
|
2909
|
+
this.cache.applyDelta({ pulled: result.data.pulled });
|
|
2910
|
+
}
|
|
2911
|
+
} catch {
|
|
2912
|
+
}
|
|
2913
|
+
}
|
|
2914
|
+
/** Flush outbox to server */
|
|
2915
|
+
async flush() {
|
|
2916
|
+
if (this.outbox.length === 0) return;
|
|
2917
|
+
const batch = this.outbox.splice(0, this.config.outboxMaxSize);
|
|
2918
|
+
const promises = batch.map(
|
|
2919
|
+
(entry) => this.client.record({
|
|
2920
|
+
gene_id: entry.geneId,
|
|
2921
|
+
signals: entry.signals.map((s) => s.type),
|
|
2922
|
+
outcome: entry.outcome,
|
|
2923
|
+
summary: entry.summary,
|
|
2924
|
+
score: entry.score,
|
|
2925
|
+
metadata: entry.metadata,
|
|
2926
|
+
scope: this.scope
|
|
2927
|
+
}).catch(() => {
|
|
2928
|
+
this.outbox.push(entry);
|
|
2929
|
+
})
|
|
2930
|
+
);
|
|
2931
|
+
await Promise.allSettled(promises);
|
|
2932
|
+
}
|
|
2933
|
+
};
|
|
2934
|
+
|
|
2270
2935
|
// src/index.ts
|
|
2271
2936
|
var AccountClient = class {
|
|
2272
2937
|
constructor(_r) {
|
|
@@ -2392,8 +3057,8 @@ var MessagesClient = class {
|
|
|
2392
3057
|
return this._r("GET", `/api/im/messages/${conversationId}`, void 0, query);
|
|
2393
3058
|
}
|
|
2394
3059
|
/** Edit a message */
|
|
2395
|
-
async edit(conversationId, messageId, content) {
|
|
2396
|
-
return this._r("PATCH", `/api/im/messages/${conversationId}/${messageId}`, { content });
|
|
3060
|
+
async edit(conversationId, messageId, content, options) {
|
|
3061
|
+
return this._r("PATCH", `/api/im/messages/${conversationId}/${messageId}`, { content, ...options?.metadata ? { metadata: options.metadata } : {} });
|
|
2397
3062
|
}
|
|
2398
3063
|
/** Delete a message */
|
|
2399
3064
|
async delete(conversationId, messageId) {
|
|
@@ -2480,6 +3145,560 @@ var WorkspaceClient = class {
|
|
|
2480
3145
|
return this._r("GET", "/api/im/workspace/mentions/autocomplete", void 0, q);
|
|
2481
3146
|
}
|
|
2482
3147
|
};
|
|
3148
|
+
var TasksClient = class {
|
|
3149
|
+
constructor(_r) {
|
|
3150
|
+
this._r = _r;
|
|
3151
|
+
}
|
|
3152
|
+
/** Create a new task */
|
|
3153
|
+
async create(options) {
|
|
3154
|
+
return this._r("POST", "/api/im/tasks", options);
|
|
3155
|
+
}
|
|
3156
|
+
/** List tasks with optional filters */
|
|
3157
|
+
async list(options) {
|
|
3158
|
+
const query = {};
|
|
3159
|
+
if (options?.status) query.status = options.status;
|
|
3160
|
+
if (options?.capability) query.capability = options.capability;
|
|
3161
|
+
if (options?.assigneeId) query.assigneeId = options.assigneeId;
|
|
3162
|
+
if (options?.creatorId) query.creatorId = options.creatorId;
|
|
3163
|
+
if (options?.scheduleType) query.scheduleType = options.scheduleType;
|
|
3164
|
+
if (options?.limit != null) query.limit = String(options.limit);
|
|
3165
|
+
if (options?.cursor) query.cursor = options.cursor;
|
|
3166
|
+
return this._r("GET", "/api/im/tasks", void 0, query);
|
|
3167
|
+
}
|
|
3168
|
+
/** Get task details with logs */
|
|
3169
|
+
async get(taskId) {
|
|
3170
|
+
return this._r("GET", `/api/im/tasks/${taskId}`);
|
|
3171
|
+
}
|
|
3172
|
+
/** Update a task */
|
|
3173
|
+
async update(taskId, options) {
|
|
3174
|
+
return this._r("PATCH", `/api/im/tasks/${taskId}`, options);
|
|
3175
|
+
}
|
|
3176
|
+
/** Claim a pending task */
|
|
3177
|
+
async claim(taskId) {
|
|
3178
|
+
return this._r("POST", `/api/im/tasks/${taskId}/claim`);
|
|
3179
|
+
}
|
|
3180
|
+
/** Report progress on a task */
|
|
3181
|
+
async progress(taskId, options) {
|
|
3182
|
+
return this._r("POST", `/api/im/tasks/${taskId}/progress`, options);
|
|
3183
|
+
}
|
|
3184
|
+
/** Complete a task with result */
|
|
3185
|
+
async complete(taskId, options) {
|
|
3186
|
+
return this._r("POST", `/api/im/tasks/${taskId}/complete`, options);
|
|
3187
|
+
}
|
|
3188
|
+
/** Fail a task with error */
|
|
3189
|
+
async fail(taskId, error, metadata) {
|
|
3190
|
+
return this._r("POST", `/api/im/tasks/${taskId}/fail`, { error, metadata });
|
|
3191
|
+
}
|
|
3192
|
+
};
|
|
3193
|
+
var MemoryClient = class {
|
|
3194
|
+
constructor(_r) {
|
|
3195
|
+
this._r = _r;
|
|
3196
|
+
}
|
|
3197
|
+
/** Create a memory file */
|
|
3198
|
+
async createFile(options) {
|
|
3199
|
+
return this._r("POST", "/api/im/memory/files", options);
|
|
3200
|
+
}
|
|
3201
|
+
/** List memory files */
|
|
3202
|
+
async listFiles(options) {
|
|
3203
|
+
const query = {};
|
|
3204
|
+
if (options?.scope) query.scope = options.scope;
|
|
3205
|
+
if (options?.path) query.path = options.path;
|
|
3206
|
+
return this._r("GET", "/api/im/memory/files", void 0, query);
|
|
3207
|
+
}
|
|
3208
|
+
/** Get a memory file by ID */
|
|
3209
|
+
async getFile(fileId) {
|
|
3210
|
+
return this._r("GET", `/api/im/memory/files/${fileId}`);
|
|
3211
|
+
}
|
|
3212
|
+
/** Update a memory file (append, replace, or replace_section) */
|
|
3213
|
+
async updateFile(fileId, options) {
|
|
3214
|
+
return this._r("PATCH", `/api/im/memory/files/${fileId}`, options);
|
|
3215
|
+
}
|
|
3216
|
+
/** Delete a memory file */
|
|
3217
|
+
async deleteFile(fileId) {
|
|
3218
|
+
return this._r("DELETE", `/api/im/memory/files/${fileId}`);
|
|
3219
|
+
}
|
|
3220
|
+
/** Compact conversation messages into a summary */
|
|
3221
|
+
async compact(options) {
|
|
3222
|
+
return this._r("POST", "/api/im/memory/compact", options);
|
|
3223
|
+
}
|
|
3224
|
+
/** Get compaction summaries for a conversation */
|
|
3225
|
+
async getCompaction(conversationId) {
|
|
3226
|
+
return this._r("GET", `/api/im/memory/compact/${conversationId}`);
|
|
3227
|
+
}
|
|
3228
|
+
/** Load memory for session context */
|
|
3229
|
+
async load(scope) {
|
|
3230
|
+
const query = {};
|
|
3231
|
+
if (scope) query.scope = scope;
|
|
3232
|
+
return this._r("GET", "/api/im/memory/load", void 0, query);
|
|
3233
|
+
}
|
|
3234
|
+
};
|
|
3235
|
+
var IdentityClient = class {
|
|
3236
|
+
constructor(_r) {
|
|
3237
|
+
this._r = _r;
|
|
3238
|
+
}
|
|
3239
|
+
/** Get server public key */
|
|
3240
|
+
async getServerKey() {
|
|
3241
|
+
return this._r("GET", "/api/im/keys/server");
|
|
3242
|
+
}
|
|
3243
|
+
/** Register or rotate an identity key */
|
|
3244
|
+
async registerKey(options) {
|
|
3245
|
+
return this._r("PUT", "/api/im/keys/identity", options);
|
|
3246
|
+
}
|
|
3247
|
+
/** Get a user's identity key */
|
|
3248
|
+
async getKey(userId) {
|
|
3249
|
+
return this._r("GET", `/api/im/keys/identity/${userId}`);
|
|
3250
|
+
}
|
|
3251
|
+
/** Revoke own identity key */
|
|
3252
|
+
async revokeKey() {
|
|
3253
|
+
return this._r("POST", "/api/im/keys/identity/revoke");
|
|
3254
|
+
}
|
|
3255
|
+
/** Get key audit log for a user */
|
|
3256
|
+
async getAuditLog(userId) {
|
|
3257
|
+
return this._r("GET", `/api/im/keys/audit/${userId}`);
|
|
3258
|
+
}
|
|
3259
|
+
/** Verify key audit log integrity */
|
|
3260
|
+
async verifyAuditLog(userId) {
|
|
3261
|
+
return this._r("GET", `/api/im/keys/audit/${userId}/verify`);
|
|
3262
|
+
}
|
|
3263
|
+
};
|
|
3264
|
+
var SecurityClient = class {
|
|
3265
|
+
constructor(_r) {
|
|
3266
|
+
this._r = _r;
|
|
3267
|
+
}
|
|
3268
|
+
/** Get conversation security settings */
|
|
3269
|
+
async getConversationSecurity(conversationId) {
|
|
3270
|
+
return this._r("GET", `/api/im/conversations/${conversationId}/security`);
|
|
3271
|
+
}
|
|
3272
|
+
/** Update conversation security settings */
|
|
3273
|
+
async setConversationSecurity(conversationId, options) {
|
|
3274
|
+
return this._r("PATCH", `/api/im/conversations/${conversationId}/security`, options);
|
|
3275
|
+
}
|
|
3276
|
+
/** Upload a public key for a conversation */
|
|
3277
|
+
async uploadKey(conversationId, publicKey, algorithm) {
|
|
3278
|
+
const body = { publicKey };
|
|
3279
|
+
if (algorithm) body.algorithm = algorithm;
|
|
3280
|
+
return this._r("POST", `/api/im/conversations/${conversationId}/keys`, body);
|
|
3281
|
+
}
|
|
3282
|
+
/** Get keys for a conversation */
|
|
3283
|
+
async getKeys(conversationId) {
|
|
3284
|
+
return this._r("GET", `/api/im/conversations/${conversationId}/keys`);
|
|
3285
|
+
}
|
|
3286
|
+
/** Revoke a key for a specific user in a conversation */
|
|
3287
|
+
async revokeKey(conversationId, keyUserId) {
|
|
3288
|
+
return this._r("DELETE", `/api/im/conversations/${conversationId}/keys/${keyUserId}`);
|
|
3289
|
+
}
|
|
3290
|
+
};
|
|
3291
|
+
var EvolutionClient = class {
|
|
3292
|
+
constructor(_r) {
|
|
3293
|
+
this._r = _r;
|
|
3294
|
+
}
|
|
3295
|
+
// ── Public endpoints (no auth required) ──
|
|
3296
|
+
/** Get evolution stats */
|
|
3297
|
+
async getStats() {
|
|
3298
|
+
return this._r("GET", "/api/im/evolution/public/stats");
|
|
3299
|
+
}
|
|
3300
|
+
/** Get hot/trending genes */
|
|
3301
|
+
async getHotGenes(limit) {
|
|
3302
|
+
const query = {};
|
|
3303
|
+
if (limit != null) query.limit = String(limit);
|
|
3304
|
+
return this._r("GET", "/api/im/evolution/public/hot", void 0, query);
|
|
3305
|
+
}
|
|
3306
|
+
/** Browse published genes */
|
|
3307
|
+
async browseGenes(options) {
|
|
3308
|
+
const query = {};
|
|
3309
|
+
if (options?.category) query.category = options.category;
|
|
3310
|
+
if (options?.search) query.search = options.search;
|
|
3311
|
+
if (options?.sort) query.sort = options.sort;
|
|
3312
|
+
if (options?.page != null) query.page = String(options.page);
|
|
3313
|
+
if (options?.limit != null) query.limit = String(options.limit);
|
|
3314
|
+
return this._r("GET", "/api/im/evolution/public/genes", void 0, query);
|
|
3315
|
+
}
|
|
3316
|
+
/** Get a public gene by ID */
|
|
3317
|
+
async getPublicGene(geneId) {
|
|
3318
|
+
return this._r("GET", `/api/im/evolution/public/genes/${geneId}`);
|
|
3319
|
+
}
|
|
3320
|
+
/** Get capsules for a public gene */
|
|
3321
|
+
async getGeneCapsules(geneId, limit) {
|
|
3322
|
+
const query = {};
|
|
3323
|
+
if (limit != null) query.limit = String(limit);
|
|
3324
|
+
return this._r("GET", `/api/im/evolution/public/genes/${geneId}/capsules`, void 0, query);
|
|
3325
|
+
}
|
|
3326
|
+
/** Get gene lineage (parent + children) */
|
|
3327
|
+
async getGeneLineage(geneId) {
|
|
3328
|
+
return this._r("GET", `/api/im/evolution/public/genes/${geneId}/lineage`);
|
|
3329
|
+
}
|
|
3330
|
+
/** Get public evolution feed */
|
|
3331
|
+
async getFeed(limit) {
|
|
3332
|
+
const query = {};
|
|
3333
|
+
if (limit != null) query.limit = String(limit);
|
|
3334
|
+
return this._r("GET", "/api/im/evolution/public/feed", void 0, query);
|
|
3335
|
+
}
|
|
3336
|
+
// ── Authenticated endpoints ──
|
|
3337
|
+
/** Analyze signals and get gene recommendation */
|
|
3338
|
+
async analyze(options) {
|
|
3339
|
+
const { scope, ...body } = options;
|
|
3340
|
+
const q = {};
|
|
3341
|
+
if (scope) q.scope = scope;
|
|
3342
|
+
return this._r("POST", "/api/im/evolution/analyze", body, q);
|
|
3343
|
+
}
|
|
3344
|
+
/** Record an outcome (success/failure) for a gene */
|
|
3345
|
+
async record(options) {
|
|
3346
|
+
const { scope, ...body } = options;
|
|
3347
|
+
const q = {};
|
|
3348
|
+
if (scope) q.scope = scope;
|
|
3349
|
+
return this._r("POST", "/api/im/evolution/record", body, q);
|
|
3350
|
+
}
|
|
3351
|
+
/**
|
|
3352
|
+
* One-step evolution: analyze context → get gene recommendation → auto-record outcome.
|
|
3353
|
+
* Combines analyze() + record() into a single call for the common case.
|
|
3354
|
+
*
|
|
3355
|
+
* Usage:
|
|
3356
|
+
* const result = await client.evolution.evolve({
|
|
3357
|
+
* error: 'Connection timeout after 10s',
|
|
3358
|
+
* outcome: 'success',
|
|
3359
|
+
* score: 0.85,
|
|
3360
|
+
* summary: 'Fixed with exponential backoff',
|
|
3361
|
+
* });
|
|
3362
|
+
*/
|
|
3363
|
+
async evolve(options) {
|
|
3364
|
+
const { outcome, score, summary, strategy_used, scope, ...analyzeOpts } = options;
|
|
3365
|
+
const analysis = await this.analyze({ ...analyzeOpts, ...scope ? { scope } : {} });
|
|
3366
|
+
if (!analysis.ok || !analysis.data) {
|
|
3367
|
+
return { ok: false, error: analysis.error };
|
|
3368
|
+
}
|
|
3369
|
+
const data = analysis.data;
|
|
3370
|
+
const geneId = data.gene_id;
|
|
3371
|
+
if (geneId && (data.action === "apply_gene" || data.action === "explore")) {
|
|
3372
|
+
const recordResult = await this.record({
|
|
3373
|
+
gene_id: geneId,
|
|
3374
|
+
signals: data.signals || analyzeOpts.signals || [],
|
|
3375
|
+
outcome,
|
|
3376
|
+
score: score ?? (outcome === "success" ? 0.8 : 0.2),
|
|
3377
|
+
summary: summary || `${outcome === "success" ? "Resolved" : "Failed to resolve"} using ${geneId}`,
|
|
3378
|
+
strategy_used,
|
|
3379
|
+
...scope ? { scope } : {}
|
|
3380
|
+
});
|
|
3381
|
+
return {
|
|
3382
|
+
ok: true,
|
|
3383
|
+
data: {
|
|
3384
|
+
analysis: data,
|
|
3385
|
+
recorded: true,
|
|
3386
|
+
edge_updated: recordResult.data?.edge_updated
|
|
3387
|
+
}
|
|
3388
|
+
};
|
|
3389
|
+
}
|
|
3390
|
+
return {
|
|
3391
|
+
ok: true,
|
|
3392
|
+
data: { analysis: data, recorded: false }
|
|
3393
|
+
};
|
|
3394
|
+
}
|
|
3395
|
+
/** Trigger gene distillation */
|
|
3396
|
+
async distill(dryRun) {
|
|
3397
|
+
const query = {};
|
|
3398
|
+
if (dryRun) query.dry_run = "true";
|
|
3399
|
+
return this._r("POST", "/api/im/evolution/distill", void 0, query);
|
|
3400
|
+
}
|
|
3401
|
+
/** List own genes */
|
|
3402
|
+
async listGenes(signals, scope) {
|
|
3403
|
+
const query = {};
|
|
3404
|
+
if (signals) query.signals = signals;
|
|
3405
|
+
if (scope) query.scope = scope;
|
|
3406
|
+
return this._r("GET", "/api/im/evolution/genes", void 0, query);
|
|
3407
|
+
}
|
|
3408
|
+
/** Create a new gene */
|
|
3409
|
+
async createGene(options) {
|
|
3410
|
+
const { scope, ...body } = options;
|
|
3411
|
+
const q = {};
|
|
3412
|
+
if (scope) q.scope = scope;
|
|
3413
|
+
return this._r("POST", "/api/im/evolution/genes", body, q);
|
|
3414
|
+
}
|
|
3415
|
+
/** Delete a gene */
|
|
3416
|
+
async deleteGene(geneId) {
|
|
3417
|
+
return this._r("DELETE", `/api/im/evolution/genes/${encodeURIComponent(geneId)}`);
|
|
3418
|
+
}
|
|
3419
|
+
/** Publish a gene. Pass skipCanary=true to bypass canary validation (MVP/admin). */
|
|
3420
|
+
async publishGene(geneId, options) {
|
|
3421
|
+
return this._r("POST", `/api/im/evolution/genes/${encodeURIComponent(geneId)}/publish`, options?.skipCanary ? { skipCanary: true } : void 0);
|
|
3422
|
+
}
|
|
3423
|
+
/** Import a published gene */
|
|
3424
|
+
async importGene(geneId) {
|
|
3425
|
+
return this._r("POST", "/api/im/evolution/genes/import", { gene_id: geneId });
|
|
3426
|
+
}
|
|
3427
|
+
/** Fork a gene with modifications */
|
|
3428
|
+
async forkGene(options) {
|
|
3429
|
+
return this._r("POST", "/api/im/evolution/genes/fork", options);
|
|
3430
|
+
}
|
|
3431
|
+
/** Get signal-gene edges */
|
|
3432
|
+
async getEdges(options) {
|
|
3433
|
+
const query = {};
|
|
3434
|
+
if (options?.signalKey) query.signal_key = options.signalKey;
|
|
3435
|
+
if (options?.geneId) query.gene_id = options.geneId;
|
|
3436
|
+
if (options?.limit != null) query.limit = String(options.limit);
|
|
3437
|
+
if (options?.scope) query.scope = options.scope;
|
|
3438
|
+
return this._r("GET", "/api/im/evolution/edges", void 0, query);
|
|
3439
|
+
}
|
|
3440
|
+
/** Get agent personality profile */
|
|
3441
|
+
async getPersonality(agentId) {
|
|
3442
|
+
return this._r("GET", `/api/im/evolution/personality/${agentId}`);
|
|
3443
|
+
}
|
|
3444
|
+
/** Get own capsule history */
|
|
3445
|
+
async getCapsules(options) {
|
|
3446
|
+
const query = {};
|
|
3447
|
+
if (options?.page != null) query.page = String(options.page);
|
|
3448
|
+
if (options?.limit != null) query.limit = String(options.limit);
|
|
3449
|
+
if (options?.scope) query.scope = options.scope;
|
|
3450
|
+
return this._r("GET", "/api/im/evolution/capsules", void 0, query);
|
|
3451
|
+
}
|
|
3452
|
+
/** Get evolution report */
|
|
3453
|
+
async getReport(agentId, scope) {
|
|
3454
|
+
const query = {};
|
|
3455
|
+
if (agentId) query.agent_id = agentId;
|
|
3456
|
+
if (scope) query.scope = scope;
|
|
3457
|
+
return this._r("GET", "/api/im/evolution/report", void 0, query);
|
|
3458
|
+
}
|
|
3459
|
+
/** List available evolution scopes */
|
|
3460
|
+
async listScopes() {
|
|
3461
|
+
return this._r("GET", "/api/im/evolution/scopes");
|
|
3462
|
+
}
|
|
3463
|
+
// ─── v0.3.1: Stories, Metrics, Skills ──────────────
|
|
3464
|
+
/** Get recent evolution stories (for L1 narrative embedding) */
|
|
3465
|
+
async getStories(options) {
|
|
3466
|
+
const query = {};
|
|
3467
|
+
if (options?.limit != null) query.limit = String(options.limit);
|
|
3468
|
+
if (options?.since != null) query.since = String(options.since);
|
|
3469
|
+
return this._r("GET", "/api/im/evolution/stories", void 0, query);
|
|
3470
|
+
}
|
|
3471
|
+
/** Get north-star metrics comparison (standard vs hypergraph) */
|
|
3472
|
+
async getMetrics() {
|
|
3473
|
+
return this._r("GET", "/api/im/evolution/metrics");
|
|
3474
|
+
}
|
|
3475
|
+
/** Trigger metrics collection snapshot */
|
|
3476
|
+
async collectMetrics(windowHours) {
|
|
3477
|
+
return this._r("POST", "/api/im/evolution/metrics/collect", { window_hours: windowHours ?? 1 });
|
|
3478
|
+
}
|
|
3479
|
+
/** Search skills catalog */
|
|
3480
|
+
async searchSkills(options) {
|
|
3481
|
+
const q = {};
|
|
3482
|
+
if (options?.query) q.query = options.query;
|
|
3483
|
+
if (options?.category) q.category = options.category;
|
|
3484
|
+
if (options?.limit != null) q.limit = String(options.limit);
|
|
3485
|
+
return this._r("GET", "/api/im/skills/search", void 0, q);
|
|
3486
|
+
}
|
|
3487
|
+
/** Get skill catalog stats */
|
|
3488
|
+
async getSkillStats() {
|
|
3489
|
+
return this._r("GET", "/api/im/skills/stats");
|
|
3490
|
+
}
|
|
3491
|
+
/** Install a skill — creates Gene + returns content + install guide */
|
|
3492
|
+
async installSkill(slugOrId) {
|
|
3493
|
+
return this._r("POST", `/api/im/skills/${encodeURIComponent(slugOrId)}/install`);
|
|
3494
|
+
}
|
|
3495
|
+
/** Uninstall a skill */
|
|
3496
|
+
async uninstallSkill(slugOrId) {
|
|
3497
|
+
return this._r("DELETE", `/api/im/skills/${encodeURIComponent(slugOrId)}/install`);
|
|
3498
|
+
}
|
|
3499
|
+
/** List installed skills for this agent */
|
|
3500
|
+
async installedSkills() {
|
|
3501
|
+
return this._r("GET", "/api/im/skills/installed");
|
|
3502
|
+
}
|
|
3503
|
+
/** Get full skill content (SKILL.md + package info) */
|
|
3504
|
+
async getSkillContent(slugOrId) {
|
|
3505
|
+
return this._r("GET", `/api/im/skills/${encodeURIComponent(slugOrId)}/content`);
|
|
3506
|
+
}
|
|
3507
|
+
/** Create/submit a community skill */
|
|
3508
|
+
async createSkill(input) {
|
|
3509
|
+
return this._r("POST", "/api/im/skills", input);
|
|
3510
|
+
}
|
|
3511
|
+
/** Star a skill (increment community rating) */
|
|
3512
|
+
async starSkill(skillId) {
|
|
3513
|
+
return this._r("POST", `/api/im/skills/${encodeURIComponent(skillId)}/star`);
|
|
3514
|
+
}
|
|
3515
|
+
/**
|
|
3516
|
+
* Install a skill and write SKILL.md to local filesystem.
|
|
3517
|
+
* Combines cloud install + local file sync for Claude Code / OpenClaw / OpenCode.
|
|
3518
|
+
* @param slugOrId - Skill slug or ID
|
|
3519
|
+
* @param options - Local install options
|
|
3520
|
+
*/
|
|
3521
|
+
async installSkillLocal(slugOrId, options) {
|
|
3522
|
+
const result = await this.installSkill(slugOrId);
|
|
3523
|
+
if (!result.ok || !result.data) return result;
|
|
3524
|
+
let content = result.data.skill?.content || "";
|
|
3525
|
+
if (!content) {
|
|
3526
|
+
const contentResult = await this.getSkillContent(slugOrId);
|
|
3527
|
+
content = contentResult.data?.content || "";
|
|
3528
|
+
}
|
|
3529
|
+
if (!content) {
|
|
3530
|
+
return { ...result, data: { ...result.data, localPaths: [] } };
|
|
3531
|
+
}
|
|
3532
|
+
const rawSlug = result.data.skill?.slug || slugOrId;
|
|
3533
|
+
const slug = rawSlug.replace(/[\/\\]/g, "").replace(/\.\./g, "");
|
|
3534
|
+
if (!slug) {
|
|
3535
|
+
return { ...result, data: { ...result.data, localPaths: [] } };
|
|
3536
|
+
}
|
|
3537
|
+
const localPaths = [];
|
|
3538
|
+
try {
|
|
3539
|
+
const fs = await import("fs");
|
|
3540
|
+
const path = await import("path");
|
|
3541
|
+
const os = await import("os");
|
|
3542
|
+
const home = os.homedir();
|
|
3543
|
+
const pluginBase = process.env.PRISMER_PLUGIN_DIR || path.join(home, ".claude", "plugins", "prismer");
|
|
3544
|
+
const platformPaths = options?.project ? {
|
|
3545
|
+
"claude-code": path.join(options.projectRoot || ".", ".claude", "skills", slug),
|
|
3546
|
+
"openclaw": path.join(options.projectRoot || ".", "skills", slug),
|
|
3547
|
+
"opencode": path.join(options.projectRoot || ".", ".opencode", "skills", slug),
|
|
3548
|
+
"plugin": path.join(options.projectRoot || ".", ".claude", "plugins", "prismer", "skills", slug)
|
|
3549
|
+
} : {
|
|
3550
|
+
"claude-code": path.join(home, ".claude", "skills", slug),
|
|
3551
|
+
"openclaw": path.join(home, ".openclaw", "skills", slug),
|
|
3552
|
+
"opencode": path.join(home, ".config", "opencode", "skills", slug),
|
|
3553
|
+
"plugin": path.join(pluginBase, "skills", slug)
|
|
3554
|
+
};
|
|
3555
|
+
const targets = options?.platforms || Object.keys(platformPaths);
|
|
3556
|
+
for (const platform of targets) {
|
|
3557
|
+
const dir = platformPaths[platform];
|
|
3558
|
+
if (!dir) continue;
|
|
3559
|
+
try {
|
|
3560
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
3561
|
+
const filePath = path.join(dir, "SKILL.md");
|
|
3562
|
+
fs.writeFileSync(filePath, content, "utf-8");
|
|
3563
|
+
localPaths.push(filePath);
|
|
3564
|
+
} catch {
|
|
3565
|
+
}
|
|
3566
|
+
}
|
|
3567
|
+
} catch {
|
|
3568
|
+
}
|
|
3569
|
+
return { ...result, data: { ...result.data, localPaths } };
|
|
3570
|
+
}
|
|
3571
|
+
/**
|
|
3572
|
+
* Uninstall a skill and remove local SKILL.md files.
|
|
3573
|
+
*/
|
|
3574
|
+
async uninstallSkillLocal(slugOrId) {
|
|
3575
|
+
const result = await this.uninstallSkill(slugOrId);
|
|
3576
|
+
const removedPaths = [];
|
|
3577
|
+
const slug = safeSlug(slugOrId);
|
|
3578
|
+
if (!slug) return { ...result, data: { uninstalled: result.data?.uninstalled ?? false, removedPaths } };
|
|
3579
|
+
try {
|
|
3580
|
+
const fs = await import("fs");
|
|
3581
|
+
const path = await import("path");
|
|
3582
|
+
const os = await import("os");
|
|
3583
|
+
const home = os.homedir();
|
|
3584
|
+
const pluginBase = process.env.PRISMER_PLUGIN_DIR || path.join(home, ".claude", "plugins", "prismer");
|
|
3585
|
+
const dirs = [
|
|
3586
|
+
path.join(home, ".claude", "skills", slug),
|
|
3587
|
+
path.join(home, ".openclaw", "skills", slug),
|
|
3588
|
+
path.join(home, ".config", "opencode", "skills", slug),
|
|
3589
|
+
path.join(pluginBase, "skills", slug)
|
|
3590
|
+
];
|
|
3591
|
+
for (const dir of dirs) {
|
|
3592
|
+
try {
|
|
3593
|
+
if (fs.existsSync(dir)) {
|
|
3594
|
+
fs.rmSync(dir, { recursive: true });
|
|
3595
|
+
removedPaths.push(dir);
|
|
3596
|
+
}
|
|
3597
|
+
} catch {
|
|
3598
|
+
}
|
|
3599
|
+
}
|
|
3600
|
+
} catch {
|
|
3601
|
+
}
|
|
3602
|
+
return { ...result, data: { uninstalled: result.data?.uninstalled ?? false, removedPaths } };
|
|
3603
|
+
}
|
|
3604
|
+
/**
|
|
3605
|
+
* Sync all installed skills to local filesystem.
|
|
3606
|
+
*/
|
|
3607
|
+
async syncSkillsLocal(options) {
|
|
3608
|
+
const installed = await this.installedSkills();
|
|
3609
|
+
if (!installed.ok || !installed.data) return { synced: 0, failed: 0, paths: [] };
|
|
3610
|
+
let synced = 0;
|
|
3611
|
+
let failed = 0;
|
|
3612
|
+
const paths = [];
|
|
3613
|
+
for (const record of installed.data) {
|
|
3614
|
+
const rawSlug = record.skill?.slug;
|
|
3615
|
+
if (!rawSlug) {
|
|
3616
|
+
failed++;
|
|
3617
|
+
continue;
|
|
3618
|
+
}
|
|
3619
|
+
const slug = rawSlug.replace(/[\/\\]/g, "").replace(/\.\./g, "");
|
|
3620
|
+
if (!slug) {
|
|
3621
|
+
failed++;
|
|
3622
|
+
continue;
|
|
3623
|
+
}
|
|
3624
|
+
try {
|
|
3625
|
+
const contentResult = await this.getSkillContent(slug);
|
|
3626
|
+
const content = contentResult.data?.content;
|
|
3627
|
+
if (!content) {
|
|
3628
|
+
failed++;
|
|
3629
|
+
continue;
|
|
3630
|
+
}
|
|
3631
|
+
const fs = await import("fs");
|
|
3632
|
+
const path = await import("path");
|
|
3633
|
+
const os = await import("os");
|
|
3634
|
+
const home = os.homedir();
|
|
3635
|
+
const pluginBase = process.env.PRISMER_PLUGIN_DIR || path.join(home, ".claude", "plugins", "prismer");
|
|
3636
|
+
const platformPaths = {
|
|
3637
|
+
"claude-code": path.join(home, ".claude", "skills", slug),
|
|
3638
|
+
"openclaw": path.join(home, ".openclaw", "skills", slug),
|
|
3639
|
+
"opencode": path.join(home, ".config", "opencode", "skills", slug),
|
|
3640
|
+
"plugin": path.join(pluginBase, "skills", slug)
|
|
3641
|
+
};
|
|
3642
|
+
const targets = options?.platforms || Object.keys(platformPaths);
|
|
3643
|
+
for (const platform of targets) {
|
|
3644
|
+
const dir = platformPaths[platform];
|
|
3645
|
+
if (!dir) continue;
|
|
3646
|
+
try {
|
|
3647
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
3648
|
+
const filePath = path.join(dir, "SKILL.md");
|
|
3649
|
+
fs.writeFileSync(filePath, content, "utf-8");
|
|
3650
|
+
paths.push(filePath);
|
|
3651
|
+
} catch {
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
3654
|
+
synced++;
|
|
3655
|
+
} catch {
|
|
3656
|
+
failed++;
|
|
3657
|
+
}
|
|
3658
|
+
}
|
|
3659
|
+
return { synced, failed, paths };
|
|
3660
|
+
}
|
|
3661
|
+
/** Export a Gene as a Skill */
|
|
3662
|
+
async exportAsSkill(geneId, options) {
|
|
3663
|
+
return this._r("POST", `/api/im/evolution/genes/${geneId}/export-skill`, options);
|
|
3664
|
+
}
|
|
3665
|
+
// ─── P0: Report, Achievements, Sync ──────────────
|
|
3666
|
+
/** Submit a raw-context evolution report (auto-creates signals + gene match) */
|
|
3667
|
+
async submitReport(options) {
|
|
3668
|
+
return this._r("POST", "/api/im/evolution/report", {
|
|
3669
|
+
raw_context: options.rawContext,
|
|
3670
|
+
outcome: options.outcome,
|
|
3671
|
+
task_context: options.taskContext,
|
|
3672
|
+
task_error: options.taskError,
|
|
3673
|
+
task_id: options.taskId,
|
|
3674
|
+
metadata: options.metadata
|
|
3675
|
+
});
|
|
3676
|
+
}
|
|
3677
|
+
/** Get status of a submitted report by traceId */
|
|
3678
|
+
async getReportStatus(traceId) {
|
|
3679
|
+
return this._r("GET", `/api/im/evolution/report/${traceId}`);
|
|
3680
|
+
}
|
|
3681
|
+
/** Get evolution achievements for the current agent */
|
|
3682
|
+
async getAchievements() {
|
|
3683
|
+
return this._r("GET", "/api/im/evolution/achievements");
|
|
3684
|
+
}
|
|
3685
|
+
/** Get a sync snapshot (global gene/edge state since a sequence number) */
|
|
3686
|
+
async getSyncSnapshot(since) {
|
|
3687
|
+
const query = { scope: "global" };
|
|
3688
|
+
if (since != null) query.since = String(since);
|
|
3689
|
+
return this._r("GET", "/api/im/evolution/sync/snapshot", void 0, query);
|
|
3690
|
+
}
|
|
3691
|
+
/** Bidirectional sync: push local outcomes and pull remote updates */
|
|
3692
|
+
async sync(options) {
|
|
3693
|
+
const body = {};
|
|
3694
|
+
if (options?.pushOutcomes) body.push = { outcomes: options.pushOutcomes };
|
|
3695
|
+
if (options?.pullSince != null) body.pull = { since: options.pullSince };
|
|
3696
|
+
return this._r("POST", "/api/im/evolution/sync", body);
|
|
3697
|
+
}
|
|
3698
|
+
};
|
|
3699
|
+
function safeSlug(input) {
|
|
3700
|
+
return input.replace(/[\/\\]/g, "").replace(/\.\./g, "").replace(/\0/g, "");
|
|
3701
|
+
}
|
|
2483
3702
|
function guessMimeType(fileName) {
|
|
2484
3703
|
const ext = fileName.split(".").pop()?.toLowerCase() || "";
|
|
2485
3704
|
const map = {
|
|
@@ -2718,6 +3937,11 @@ var IMClient = class {
|
|
|
2718
3937
|
this.bindings = new BindingsClient(request);
|
|
2719
3938
|
this.credits = new CreditsClient(request);
|
|
2720
3939
|
this.workspace = new WorkspaceClient(request);
|
|
3940
|
+
this.tasks = new TasksClient(request);
|
|
3941
|
+
this.memory = new MemoryClient(request);
|
|
3942
|
+
this.identity = new IdentityClient(request);
|
|
3943
|
+
this.security = new SecurityClient(request);
|
|
3944
|
+
this.evolution = new EvolutionClient(request);
|
|
2721
3945
|
this.files = new FilesClient(request, wsBase, fetchFn, getAuthHeaders);
|
|
2722
3946
|
this.realtime = new IMRealtimeClient(wsBase);
|
|
2723
3947
|
this.offline = offlineManager ?? null;
|
|
@@ -2891,6 +4115,7 @@ function createClient(config) {
|
|
|
2891
4115
|
return new PrismerClient(config);
|
|
2892
4116
|
}
|
|
2893
4117
|
export {
|
|
4118
|
+
AIPIdentity,
|
|
2894
4119
|
AccountClient,
|
|
2895
4120
|
AttachmentQueue,
|
|
2896
4121
|
BindingsClient,
|
|
@@ -2900,11 +4125,16 @@ export {
|
|
|
2900
4125
|
DirectClient,
|
|
2901
4126
|
E2EEncryption,
|
|
2902
4127
|
ENVIRONMENTS,
|
|
4128
|
+
EvolutionCache,
|
|
4129
|
+
EvolutionClient,
|
|
4130
|
+
EvolutionRuntime,
|
|
2903
4131
|
FilesClient,
|
|
2904
4132
|
GroupsClient,
|
|
2905
4133
|
IMClient,
|
|
2906
4134
|
IMRealtimeClient,
|
|
4135
|
+
IdentityClient,
|
|
2907
4136
|
IndexedDBStorage,
|
|
4137
|
+
MemoryClient,
|
|
2908
4138
|
MemoryStorage,
|
|
2909
4139
|
MessagesClient,
|
|
2910
4140
|
OfflineManager,
|
|
@@ -2912,8 +4142,21 @@ export {
|
|
|
2912
4142
|
RealtimeSSEClient,
|
|
2913
4143
|
RealtimeWSClient,
|
|
2914
4144
|
SQLiteStorage,
|
|
4145
|
+
SecurityClient,
|
|
2915
4146
|
TabCoordinator,
|
|
4147
|
+
TasksClient,
|
|
2916
4148
|
WorkspaceClient,
|
|
2917
4149
|
createClient,
|
|
2918
|
-
|
|
4150
|
+
createEnrichedExtractor,
|
|
4151
|
+
decryptContext,
|
|
4152
|
+
decryptFile,
|
|
4153
|
+
decryptMessages,
|
|
4154
|
+
decryptOnReceive,
|
|
4155
|
+
index_default as default,
|
|
4156
|
+
encryptContext,
|
|
4157
|
+
encryptFile,
|
|
4158
|
+
encryptForSend,
|
|
4159
|
+
extractSignals,
|
|
4160
|
+
guessMimeType,
|
|
4161
|
+
safeSlug
|
|
2919
4162
|
};
|