@agentvault/secure-channel 0.4.0 → 0.4.2
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/README.md +229 -0
- package/dist/channel.d.ts +21 -1
- package/dist/channel.d.ts.map +1 -1
- package/dist/cli.js +108 -13
- package/dist/cli.js.map +3 -3
- package/dist/index.js +108 -13
- package/dist/index.js.map +3 -3
- package/dist/types.d.ts +9 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -45127,17 +45127,21 @@ var SecureChannel = class extends EventEmitter {
|
|
|
45127
45127
|
/**
|
|
45128
45128
|
* Append a message to persistent history for cross-device replay.
|
|
45129
45129
|
*/
|
|
45130
|
-
_appendHistory(sender, text) {
|
|
45130
|
+
_appendHistory(sender, text, topicId) {
|
|
45131
45131
|
if (!this._persisted) return;
|
|
45132
45132
|
if (!this._persisted.messageHistory) {
|
|
45133
45133
|
this._persisted.messageHistory = [];
|
|
45134
45134
|
}
|
|
45135
45135
|
const maxSize = this.config.maxHistorySize ?? 500;
|
|
45136
|
-
|
|
45136
|
+
const entry = {
|
|
45137
45137
|
sender,
|
|
45138
45138
|
text,
|
|
45139
45139
|
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
45140
|
-
}
|
|
45140
|
+
};
|
|
45141
|
+
if (topicId) {
|
|
45142
|
+
entry.topicId = topicId;
|
|
45143
|
+
}
|
|
45144
|
+
this._persisted.messageHistory.push(entry);
|
|
45141
45145
|
if (this._persisted.messageHistory.length > maxSize) {
|
|
45142
45146
|
this._persisted.messageHistory = this._persisted.messageHistory.slice(-maxSize);
|
|
45143
45147
|
}
|
|
@@ -45146,11 +45150,12 @@ var SecureChannel = class extends EventEmitter {
|
|
|
45146
45150
|
* Encrypt and send a message to ALL owner devices (fanout).
|
|
45147
45151
|
* Each session gets the same plaintext encrypted independently.
|
|
45148
45152
|
*/
|
|
45149
|
-
async send(plaintext) {
|
|
45153
|
+
async send(plaintext, options) {
|
|
45150
45154
|
if (this._state !== "ready" || this._sessions.size === 0 || !this._ws) {
|
|
45151
45155
|
throw new Error("Channel is not ready");
|
|
45152
45156
|
}
|
|
45153
|
-
this.
|
|
45157
|
+
const topicId = options?.topicId ?? this._persisted?.defaultTopicId;
|
|
45158
|
+
this._appendHistory("agent", plaintext, topicId);
|
|
45154
45159
|
const messageGroupId = randomUUID();
|
|
45155
45160
|
for (const [convId, session] of this._sessions) {
|
|
45156
45161
|
if (!session.activated) {
|
|
@@ -45165,7 +45170,8 @@ var SecureChannel = class extends EventEmitter {
|
|
|
45165
45170
|
conversation_id: convId,
|
|
45166
45171
|
header_blob: transport.header_blob,
|
|
45167
45172
|
ciphertext: transport.ciphertext,
|
|
45168
|
-
message_group_id: messageGroupId
|
|
45173
|
+
message_group_id: messageGroupId,
|
|
45174
|
+
topic_id: topicId
|
|
45169
45175
|
}
|
|
45170
45176
|
})
|
|
45171
45177
|
);
|
|
@@ -45189,6 +45195,72 @@ var SecureChannel = class extends EventEmitter {
|
|
|
45189
45195
|
}
|
|
45190
45196
|
this._setState("disconnected");
|
|
45191
45197
|
}
|
|
45198
|
+
// --- Topic management ---
|
|
45199
|
+
/**
|
|
45200
|
+
* Create a new topic within the conversation group.
|
|
45201
|
+
* Requires the channel to be initialized with a groupId (from activation).
|
|
45202
|
+
*/
|
|
45203
|
+
async createTopic(name) {
|
|
45204
|
+
if (!this._persisted?.groupId) {
|
|
45205
|
+
throw new Error("Channel not initialized or groupId unknown");
|
|
45206
|
+
}
|
|
45207
|
+
if (!this._deviceJwt) {
|
|
45208
|
+
throw new Error("Channel not authenticated");
|
|
45209
|
+
}
|
|
45210
|
+
const res = await fetch(`${this.config.apiUrl}/api/v1/topics`, {
|
|
45211
|
+
method: "POST",
|
|
45212
|
+
headers: {
|
|
45213
|
+
"Content-Type": "application/json",
|
|
45214
|
+
Authorization: `Bearer ${this._deviceJwt}`
|
|
45215
|
+
},
|
|
45216
|
+
body: JSON.stringify({
|
|
45217
|
+
group_id: this._persisted.groupId,
|
|
45218
|
+
name,
|
|
45219
|
+
creator_device_id: this._persisted.deviceId
|
|
45220
|
+
})
|
|
45221
|
+
});
|
|
45222
|
+
if (!res.ok) {
|
|
45223
|
+
const detail = await res.text();
|
|
45224
|
+
throw new Error(`Create topic failed (${res.status}): ${detail}`);
|
|
45225
|
+
}
|
|
45226
|
+
const resp = await res.json();
|
|
45227
|
+
const topic = { id: resp.id, name: resp.name, isDefault: resp.is_default };
|
|
45228
|
+
if (!this._persisted.topics) {
|
|
45229
|
+
this._persisted.topics = [];
|
|
45230
|
+
}
|
|
45231
|
+
this._persisted.topics.push(topic);
|
|
45232
|
+
await this._persistState();
|
|
45233
|
+
return topic;
|
|
45234
|
+
}
|
|
45235
|
+
/**
|
|
45236
|
+
* List all topics in the conversation group.
|
|
45237
|
+
* Requires the channel to be initialized with a groupId (from activation).
|
|
45238
|
+
*/
|
|
45239
|
+
async listTopics() {
|
|
45240
|
+
if (!this._persisted?.groupId) {
|
|
45241
|
+
throw new Error("Channel not initialized or groupId unknown");
|
|
45242
|
+
}
|
|
45243
|
+
if (!this._deviceJwt) {
|
|
45244
|
+
throw new Error("Channel not authenticated");
|
|
45245
|
+
}
|
|
45246
|
+
const res = await fetch(
|
|
45247
|
+
`${this.config.apiUrl}/api/v1/topics?group_id=${encodeURIComponent(this._persisted.groupId)}`,
|
|
45248
|
+
{
|
|
45249
|
+
headers: {
|
|
45250
|
+
Authorization: `Bearer ${this._deviceJwt}`
|
|
45251
|
+
}
|
|
45252
|
+
}
|
|
45253
|
+
);
|
|
45254
|
+
if (!res.ok) {
|
|
45255
|
+
const detail = await res.text();
|
|
45256
|
+
throw new Error(`List topics failed (${res.status}): ${detail}`);
|
|
45257
|
+
}
|
|
45258
|
+
const resp = await res.json();
|
|
45259
|
+
const topics = resp.map((t2) => ({ id: t2.id, name: t2.name, isDefault: t2.is_default }));
|
|
45260
|
+
this._persisted.topics = topics;
|
|
45261
|
+
await this._persistState();
|
|
45262
|
+
return topics;
|
|
45263
|
+
}
|
|
45192
45264
|
// --- Internal lifecycle ---
|
|
45193
45265
|
async _enroll() {
|
|
45194
45266
|
this._setState("enrolling");
|
|
@@ -45315,6 +45387,15 @@ var SecureChannel = class extends EventEmitter {
|
|
|
45315
45387
|
sessions,
|
|
45316
45388
|
messageHistory: this._persisted.messageHistory ?? []
|
|
45317
45389
|
};
|
|
45390
|
+
if (conversations.length > 0) {
|
|
45391
|
+
const firstConv = conversations[0];
|
|
45392
|
+
if (firstConv.group_id) {
|
|
45393
|
+
this._persisted.groupId = firstConv.group_id;
|
|
45394
|
+
}
|
|
45395
|
+
if (firstConv.default_topic_id) {
|
|
45396
|
+
this._persisted.defaultTopicId = firstConv.default_topic_id;
|
|
45397
|
+
}
|
|
45398
|
+
}
|
|
45318
45399
|
await saveState(this.config.dataDir, this._persisted);
|
|
45319
45400
|
if (this.config.webhookUrl) {
|
|
45320
45401
|
try {
|
|
@@ -45352,6 +45433,14 @@ var SecureChannel = class extends EventEmitter {
|
|
|
45352
45433
|
}
|
|
45353
45434
|
_connect() {
|
|
45354
45435
|
if (this._stopped) return;
|
|
45436
|
+
if (this._ws) {
|
|
45437
|
+
this._ws.removeAllListeners();
|
|
45438
|
+
try {
|
|
45439
|
+
this._ws.close();
|
|
45440
|
+
} catch {
|
|
45441
|
+
}
|
|
45442
|
+
this._ws = null;
|
|
45443
|
+
}
|
|
45355
45444
|
this._setState("connecting");
|
|
45356
45445
|
const wsUrl = this.config.apiUrl.replace(/^http/, "ws");
|
|
45357
45446
|
const url = `${wsUrl}/api/v1/ws?token=${encodeURIComponent(this._deviceJwt)}&device_id=${this._deviceId}`;
|
|
@@ -45436,15 +45525,17 @@ var SecureChannel = class extends EventEmitter {
|
|
|
45436
45525
|
return;
|
|
45437
45526
|
}
|
|
45438
45527
|
if (messageType === "message") {
|
|
45439
|
-
|
|
45528
|
+
const topicId = msgData.topic_id;
|
|
45529
|
+
this._appendHistory("owner", messageText, topicId);
|
|
45440
45530
|
const metadata = {
|
|
45441
45531
|
messageId: msgData.message_id,
|
|
45442
45532
|
conversationId: convId,
|
|
45443
|
-
timestamp: msgData.created_at
|
|
45533
|
+
timestamp: msgData.created_at,
|
|
45534
|
+
topicId
|
|
45444
45535
|
};
|
|
45445
45536
|
this.emit("message", messageText, metadata);
|
|
45446
45537
|
this.config.onMessage?.(messageText, metadata);
|
|
45447
|
-
await this._relaySyncToSiblings(convId, session.ownerDeviceId, messageText);
|
|
45538
|
+
await this._relaySyncToSiblings(convId, session.ownerDeviceId, messageText, topicId);
|
|
45448
45539
|
}
|
|
45449
45540
|
if (this._persisted) {
|
|
45450
45541
|
this._persisted.lastMessageTimestamp = msgData.created_at;
|
|
@@ -45455,13 +45546,14 @@ var SecureChannel = class extends EventEmitter {
|
|
|
45455
45546
|
* Relay an owner's message to all sibling sessions as encrypted sync messages.
|
|
45456
45547
|
* This allows all owner devices to see messages from any single device.
|
|
45457
45548
|
*/
|
|
45458
|
-
async _relaySyncToSiblings(sourceConvId, senderOwnerDeviceId, messageText) {
|
|
45549
|
+
async _relaySyncToSiblings(sourceConvId, senderOwnerDeviceId, messageText, topicId) {
|
|
45459
45550
|
if (!this._ws || this._sessions.size <= 1) return;
|
|
45460
45551
|
const syncPayload = JSON.stringify({
|
|
45461
45552
|
type: "sync",
|
|
45462
45553
|
sender: senderOwnerDeviceId,
|
|
45463
45554
|
text: messageText,
|
|
45464
|
-
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
45555
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
45556
|
+
topicId
|
|
45465
45557
|
});
|
|
45466
45558
|
for (const [siblingConvId, siblingSession] of this._sessions) {
|
|
45467
45559
|
if (siblingConvId === sourceConvId) continue;
|
|
@@ -45611,11 +45703,13 @@ var SecureChannel = class extends EventEmitter {
|
|
|
45611
45703
|
messageText = plaintext;
|
|
45612
45704
|
}
|
|
45613
45705
|
if (messageType === "message") {
|
|
45614
|
-
|
|
45706
|
+
const topicId = msg.topic_id;
|
|
45707
|
+
this._appendHistory("owner", messageText, topicId);
|
|
45615
45708
|
const metadata = {
|
|
45616
45709
|
messageId: msg.id,
|
|
45617
45710
|
conversationId: msg.conversation_id,
|
|
45618
|
-
timestamp: msg.created_at
|
|
45711
|
+
timestamp: msg.created_at,
|
|
45712
|
+
topicId
|
|
45619
45713
|
};
|
|
45620
45714
|
this.emit("message", messageText, metadata);
|
|
45621
45715
|
this.config.onMessage?.(messageText, metadata);
|
|
@@ -45632,6 +45726,7 @@ var SecureChannel = class extends EventEmitter {
|
|
|
45632
45726
|
}
|
|
45633
45727
|
_scheduleReconnect() {
|
|
45634
45728
|
if (this._stopped) return;
|
|
45729
|
+
if (this._reconnectTimer) return;
|
|
45635
45730
|
const delay = Math.min(
|
|
45636
45731
|
RECONNECT_BASE_MS * Math.pow(2, this._reconnectAttempt),
|
|
45637
45732
|
RECONNECT_MAX_MS
|