@agentvault/claude-bridge 0.2.1 → 0.3.0
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/bridge.d.ts +49 -3
- package/dist/config.d.ts +2 -0
- package/dist/index.js +159 -35
- package/dist/session.d.ts +13 -3
- package/package.json +2 -2
package/dist/bridge.d.ts
CHANGED
|
@@ -3,14 +3,60 @@ export interface RoomMessage {
|
|
|
3
3
|
senderName: string;
|
|
4
4
|
plaintext: string;
|
|
5
5
|
}
|
|
6
|
+
/** Metadata SecureChannel attaches to a 1:1 `message` event. `roomId` is set only
|
|
7
|
+
* when the `message` actually originated in a room (handled by room_message), so
|
|
8
|
+
* its ABSENCE is how we identify a true owner↔agent 1:1 DM. */
|
|
9
|
+
export interface MessageMeta {
|
|
10
|
+
roomId?: string;
|
|
11
|
+
}
|
|
6
12
|
export interface RoomChannel {
|
|
7
13
|
on(ev: "room_message", cb: (e: RoomMessage) => void): unknown;
|
|
14
|
+
on(ev: "message", cb: (text: string, metadata: MessageMeta) => void): unknown;
|
|
8
15
|
on(ev: "error", cb: (err: unknown) => void): unknown;
|
|
9
16
|
sendToRoom(roomId: string, text: string): Promise<void>;
|
|
17
|
+
send(text: string): Promise<void>;
|
|
10
18
|
}
|
|
11
19
|
export interface RoomSession {
|
|
12
|
-
|
|
13
|
-
|
|
20
|
+
/** `reply` is the immutable reply sink captured for THIS message (see
|
|
21
|
+
* ActiveTarget.snapshotReply) — the session invokes it when Claude answers. */
|
|
22
|
+
push(text: string, reply?: (text: string) => Promise<void>): void;
|
|
23
|
+
}
|
|
24
|
+
/** Where a reply should go: a room (sendToRoom) or a 1:1 DM (send). */
|
|
25
|
+
export type BridgeTarget = {
|
|
26
|
+
kind: "room";
|
|
27
|
+
roomId: string;
|
|
28
|
+
} | {
|
|
29
|
+
kind: "dm";
|
|
30
|
+
};
|
|
31
|
+
/** Channel surface ActiveTarget needs to route an outbound reply. */
|
|
32
|
+
export interface ReplyChannel {
|
|
33
|
+
sendToRoom(roomId: string, text: string): Promise<void>;
|
|
34
|
+
send(text: string): Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Tracks the destination of the most recent inbound message and routes outbound
|
|
38
|
+
* replies there. Replaces the bridge's old single `activeRoomId` string so the
|
|
39
|
+
* same Claude session can answer BOTH room traffic and 1:1 owner DMs.
|
|
40
|
+
*
|
|
41
|
+
* Known limitation (matches the prior room-only behavior): the target is global,
|
|
42
|
+
* set per-inbound. If a room message lands while Claude is mid-compose on a DM
|
|
43
|
+
* reply, the reply routes to the room. Binding a reply to its originating turn is
|
|
44
|
+
* a larger redesign; for now the single-conversation-at-a-time assumption holds.
|
|
45
|
+
*/
|
|
46
|
+
export declare class ActiveTarget {
|
|
47
|
+
private target;
|
|
48
|
+
setRoom(roomId: string): void;
|
|
49
|
+
setDm(): void;
|
|
50
|
+
get current(): BridgeTarget | null;
|
|
51
|
+
reply(channel: ReplyChannel, text: string): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Capture the CURRENT target into an immutable reply closure. This is the leak
|
|
54
|
+
* fix: a reply built here routes to where its inbound message came from, even if
|
|
55
|
+
* a later inbound flips the live `current` target. The bridge captures one of
|
|
56
|
+
* these per inbound (at push time) and hands it to the session, so a room message
|
|
57
|
+
* arriving mid-compose can never redirect a private 1:1 reply into the room.
|
|
58
|
+
*/
|
|
59
|
+
snapshotReply(channel: ReplyChannel, log?: (msg: string) => void): (text: string) => Promise<void>;
|
|
14
60
|
}
|
|
15
61
|
export interface LifecycleChannel {
|
|
16
62
|
on(ev: "auth_failed", cb: (e: {
|
|
@@ -38,7 +84,7 @@ export declare function attachLifecycle(channel: LifecycleChannel, opts?: {
|
|
|
38
84
|
log?: (msg: string) => void;
|
|
39
85
|
onTerminal?: (reason: string) => void;
|
|
40
86
|
}): void;
|
|
41
|
-
export declare function wireBridge(channel: RoomChannel, session: RoomSession, opts?: {
|
|
87
|
+
export declare function wireBridge(channel: RoomChannel, session: RoomSession, target: ActiveTarget, opts?: {
|
|
42
88
|
roomFilter?: string;
|
|
43
89
|
log?: (msg: string) => void;
|
|
44
90
|
}): void;
|
package/dist/config.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -69967,6 +69967,40 @@ var init_account_config = __esm({
|
|
|
69967
69967
|
DEFAULT_HTTP_PORT = 18790;
|
|
69968
69968
|
}
|
|
69969
69969
|
});
|
|
69970
|
+
function terminalReenrollMessage(agentName, reason) {
|
|
69971
|
+
const who = agentName ? `[${agentName}] ` : "";
|
|
69972
|
+
return `${who}device is no longer valid (${reason}) \u2014 it was revoked or replaced. Re-enroll with a fresh token from AgentVault; the old credentials are dead. This agent will stop reconnecting.`;
|
|
69973
|
+
}
|
|
69974
|
+
function attachLifecycle(channel, opts = {}) {
|
|
69975
|
+
const log = opts.log ?? (() => {
|
|
69976
|
+
});
|
|
69977
|
+
const agentName = opts.agentName ?? "";
|
|
69978
|
+
const onTerminal = opts.onTerminal ?? (() => {
|
|
69979
|
+
});
|
|
69980
|
+
let fired = false;
|
|
69981
|
+
const terminal = (reason) => {
|
|
69982
|
+
if (fired) return;
|
|
69983
|
+
fired = true;
|
|
69984
|
+
log(terminalReenrollMessage(agentName, reason));
|
|
69985
|
+
void Promise.resolve(onTerminal(reason)).catch(() => {
|
|
69986
|
+
});
|
|
69987
|
+
};
|
|
69988
|
+
channel.on("auth_failed", (e7) => terminal(e7.reason));
|
|
69989
|
+
channel.on(
|
|
69990
|
+
"session_replaced",
|
|
69991
|
+
(e7) => terminal(e7?.code ? `session_replaced (${e7.code})` : "session_replaced")
|
|
69992
|
+
);
|
|
69993
|
+
channel.on("error", (err) => {
|
|
69994
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
69995
|
+
if (/reconnect loop detected/i.test(msg)) terminal("reconnect_loop");
|
|
69996
|
+
else if (/device (was )?revoked/i.test(msg)) terminal("device_revoked");
|
|
69997
|
+
});
|
|
69998
|
+
}
|
|
69999
|
+
var init_lifecycle = __esm({
|
|
70000
|
+
"src/lifecycle.ts"() {
|
|
70001
|
+
"use strict";
|
|
70002
|
+
}
|
|
70003
|
+
});
|
|
69970
70004
|
async function _handleInbound(params) {
|
|
69971
70005
|
const { plaintext, metadata, channel, account, cfg } = params;
|
|
69972
70006
|
const core = _ocRuntime;
|
|
@@ -70051,6 +70085,7 @@ var init_openclaw_plugin = __esm({
|
|
|
70051
70085
|
await init_channel();
|
|
70052
70086
|
init_account_config();
|
|
70053
70087
|
init_openclaw_compat();
|
|
70088
|
+
init_lifecycle();
|
|
70054
70089
|
_ocRuntime = null;
|
|
70055
70090
|
_channels = /* @__PURE__ */ new Map();
|
|
70056
70091
|
agentVaultPlugin = {
|
|
@@ -70108,6 +70143,7 @@ var init_openclaw_plugin = __esm({
|
|
|
70108
70143
|
const dataDir = resolve(account.dataDir.replace(/^~/, process.env.HOME ?? "~"));
|
|
70109
70144
|
_log?.(`[AgentVault] starting channel (dataDir=${dataDir})`);
|
|
70110
70145
|
await new Promise((resolvePromise, reject) => {
|
|
70146
|
+
let terminalStopped = false;
|
|
70111
70147
|
const channel = new SecureChannel({
|
|
70112
70148
|
inviteToken: "",
|
|
70113
70149
|
dataDir,
|
|
@@ -70126,13 +70162,29 @@ var init_openclaw_plugin = __esm({
|
|
|
70126
70162
|
},
|
|
70127
70163
|
onStateChange: (state) => {
|
|
70128
70164
|
_log?.(`[AgentVault] state \u2192 ${state}`);
|
|
70129
|
-
if (state === "error")
|
|
70165
|
+
if (state === "error") {
|
|
70166
|
+
queueMicrotask(() => {
|
|
70167
|
+
if (!terminalStopped) reject(new Error("AgentVault channel permanent error"));
|
|
70168
|
+
});
|
|
70169
|
+
}
|
|
70130
70170
|
}
|
|
70131
70171
|
});
|
|
70132
70172
|
_channels.set(account.accountId, channel);
|
|
70133
70173
|
channel.on("error", (err) => {
|
|
70134
70174
|
_log?.(`[AgentVault] channel error (non-fatal): ${String(err)}`);
|
|
70135
70175
|
});
|
|
70176
|
+
attachLifecycle(channel, {
|
|
70177
|
+
agentName: account.agentName,
|
|
70178
|
+
log: (m22) => _log?.(`[AgentVault] ${m22}`),
|
|
70179
|
+
onTerminal: async () => {
|
|
70180
|
+
terminalStopped = true;
|
|
70181
|
+
try {
|
|
70182
|
+
await channel.stop();
|
|
70183
|
+
} catch {
|
|
70184
|
+
}
|
|
70185
|
+
_channels.delete(account.accountId);
|
|
70186
|
+
}
|
|
70187
|
+
});
|
|
70136
70188
|
const httpPort = account.httpPort;
|
|
70137
70189
|
channel.on("ready", () => {
|
|
70138
70190
|
channel.startHttpServer(httpPort);
|
|
@@ -70143,7 +70195,9 @@ var init_openclaw_plugin = __esm({
|
|
|
70143
70195
|
_channels.delete(account.accountId);
|
|
70144
70196
|
resolvePromise();
|
|
70145
70197
|
});
|
|
70146
|
-
channel.start().catch(
|
|
70198
|
+
channel.start().catch((e7) => {
|
|
70199
|
+
if (!terminalStopped) reject(e7);
|
|
70200
|
+
});
|
|
70147
70201
|
});
|
|
70148
70202
|
return { stop: async () => {
|
|
70149
70203
|
} };
|
|
@@ -70262,6 +70316,7 @@ var init_openclaw_entry = __esm({
|
|
|
70262
70316
|
init_fetch_interceptor();
|
|
70263
70317
|
init_http_handlers();
|
|
70264
70318
|
init_openclaw_compat();
|
|
70319
|
+
init_lifecycle();
|
|
70265
70320
|
init_types();
|
|
70266
70321
|
isUsingManagedRoutes = false;
|
|
70267
70322
|
}
|
|
@@ -96325,8 +96380,19 @@ var CRED_FILES = ["agentvault.json", "secure-channel.json"];
|
|
|
96325
96380
|
function hasPersistedCreds(dataDir) {
|
|
96326
96381
|
return CRED_FILES.some((f7) => existsSync(join5(dataDir, f7)));
|
|
96327
96382
|
}
|
|
96383
|
+
function slugify2(name) {
|
|
96384
|
+
return name.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
96385
|
+
}
|
|
96386
|
+
function resolveDataDir(env) {
|
|
96387
|
+
if (env.AV_DATA_DIR) return { dataDir: env.AV_DATA_DIR, source: "explicit" };
|
|
96388
|
+
const base = join5(env.HOME ?? "", ".agentvault", "claude-room-bridge");
|
|
96389
|
+
const perAgent = join5(base, slugify2(env.AV_AGENT_NAME ?? "claude"));
|
|
96390
|
+
if (hasPersistedCreds(perAgent)) return { dataDir: perAgent, source: "per-agent" };
|
|
96391
|
+
if (hasPersistedCreds(base)) return { dataDir: base, source: "legacy" };
|
|
96392
|
+
return { dataDir: perAgent, source: "per-agent" };
|
|
96393
|
+
}
|
|
96328
96394
|
function loadConfig(env, argv = []) {
|
|
96329
|
-
const dataDir
|
|
96395
|
+
const { dataDir, source: dataDirSource } = resolveDataDir(env);
|
|
96330
96396
|
const inviteToken = (argv[0] && !argv[0].startsWith("-") ? argv[0] : "") || env.AV_INVITE_TOKEN || "";
|
|
96331
96397
|
if (!inviteToken && !hasPersistedCreds(dataDir)) {
|
|
96332
96398
|
throw new Error(
|
|
@@ -96336,6 +96402,7 @@ function loadConfig(env, argv = []) {
|
|
|
96336
96402
|
return {
|
|
96337
96403
|
inviteToken,
|
|
96338
96404
|
dataDir,
|
|
96405
|
+
dataDirSource,
|
|
96339
96406
|
apiUrl: env.AV_API_URL ?? "https://api.agentvault.chat",
|
|
96340
96407
|
// Identity shown to the agent's own session. Set this to the agent's
|
|
96341
96408
|
// AgentVault name via AV_AGENT_NAME (the native connect command passes it).
|
|
@@ -118294,7 +118361,7 @@ __export(util_exports2, {
|
|
|
118294
118361
|
required: () => required2,
|
|
118295
118362
|
safeExtend: () => safeExtend2,
|
|
118296
118363
|
shallowClone: () => shallowClone2,
|
|
118297
|
-
slugify: () =>
|
|
118364
|
+
slugify: () => slugify3,
|
|
118298
118365
|
stringifyPrimitive: () => stringifyPrimitive2,
|
|
118299
118366
|
uint8ArrayToBase64: () => uint8ArrayToBase643,
|
|
118300
118367
|
uint8ArrayToBase64url: () => uint8ArrayToBase64url2,
|
|
@@ -118435,7 +118502,7 @@ function randomString2(length = 10) {
|
|
|
118435
118502
|
function esc2(str) {
|
|
118436
118503
|
return JSON.stringify(str);
|
|
118437
118504
|
}
|
|
118438
|
-
function
|
|
118505
|
+
function slugify3(input) {
|
|
118439
118506
|
return input.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
118440
118507
|
}
|
|
118441
118508
|
var captureStackTrace2 = "captureStackTrace" in Error ? Error.captureStackTrace : (..._args) => {
|
|
@@ -128169,7 +128236,7 @@ function _toUpperCase2() {
|
|
|
128169
128236
|
}
|
|
128170
128237
|
// @__NO_SIDE_EFFECTS__
|
|
128171
128238
|
function _slugify2() {
|
|
128172
|
-
return /* @__PURE__ */ _overwrite2((input) =>
|
|
128239
|
+
return /* @__PURE__ */ _overwrite2((input) => slugify3(input));
|
|
128173
128240
|
}
|
|
128174
128241
|
// @__NO_SIDE_EFFECTS__
|
|
128175
128242
|
function _array2(Class3, element, params) {
|
|
@@ -131413,11 +131480,11 @@ config2(en_default3());
|
|
|
131413
131480
|
function makeRoomSayTool(onSay) {
|
|
131414
131481
|
return bs(
|
|
131415
131482
|
"say",
|
|
131416
|
-
"Send a message to the current AgentVault
|
|
131417
|
-
{ message: external_exports.string().describe("The message to
|
|
131483
|
+
"Send a message to the current AgentVault conversation \u2014 your owner in a 1:1 direct message, or the other participants in a room. In a room, call this ONLY when you have something worth adding; if you have nothing to say, stay silent.",
|
|
131484
|
+
{ message: external_exports.string().describe("The message to send to the current conversation.") },
|
|
131418
131485
|
async (args) => {
|
|
131419
131486
|
await onSay(args.message);
|
|
131420
|
-
return { content: [{ type: "text", text: "Message sent
|
|
131487
|
+
return { content: [{ type: "text", text: "Message sent." }] };
|
|
131421
131488
|
}
|
|
131422
131489
|
);
|
|
131423
131490
|
}
|
|
@@ -131429,37 +131496,52 @@ var PersistentClaudeSession = class {
|
|
|
131429
131496
|
opts;
|
|
131430
131497
|
waiting = null;
|
|
131431
131498
|
pending = [];
|
|
131432
|
-
|
|
131499
|
+
/** Reply sink bound to the message currently being processed. Set as each
|
|
131500
|
+
* message is handed to the model so the say tool routes to the right place. */
|
|
131501
|
+
activeReply;
|
|
131502
|
+
push(text, reply) {
|
|
131433
131503
|
const msg = {
|
|
131434
131504
|
type: "user",
|
|
131435
131505
|
message: { role: "user", content: text },
|
|
131436
131506
|
parent_tool_use_id: null,
|
|
131437
131507
|
session_id: ""
|
|
131438
131508
|
};
|
|
131509
|
+
const item = { msg, reply };
|
|
131439
131510
|
if (this.waiting) {
|
|
131440
131511
|
const w2 = this.waiting;
|
|
131441
131512
|
this.waiting = null;
|
|
131442
|
-
w2(
|
|
131513
|
+
w2(item);
|
|
131443
131514
|
} else {
|
|
131444
|
-
this.pending.push(
|
|
131515
|
+
this.pending.push(item);
|
|
131445
131516
|
}
|
|
131446
131517
|
}
|
|
131518
|
+
/** Route a say-tool message to the reply bound to the in-flight message, falling
|
|
131519
|
+
* back to opts.onSay. This is what closes the DM→room leak: the destination is
|
|
131520
|
+
* the one captured for the message being answered, not a live global target. */
|
|
131521
|
+
async deliver(text) {
|
|
131522
|
+
if (this.activeReply) await this.activeReply(text);
|
|
131523
|
+
else if (this.opts.onSay) await this.opts.onSay(text);
|
|
131524
|
+
}
|
|
131447
131525
|
async *input() {
|
|
131448
131526
|
while (true) {
|
|
131449
131527
|
if (this.pending.length > 0) {
|
|
131450
|
-
|
|
131528
|
+
const item2 = this.pending.shift();
|
|
131529
|
+
this.activeReply = item2.reply;
|
|
131530
|
+
yield item2.msg;
|
|
131451
131531
|
continue;
|
|
131452
131532
|
}
|
|
131453
|
-
|
|
131533
|
+
const item = await new Promise((resolve2) => {
|
|
131454
131534
|
this.waiting = resolve2;
|
|
131455
131535
|
});
|
|
131536
|
+
this.activeReply = item.reply;
|
|
131537
|
+
yield item.msg;
|
|
131456
131538
|
}
|
|
131457
131539
|
}
|
|
131458
131540
|
async start() {
|
|
131459
131541
|
const roomServer = _s({
|
|
131460
131542
|
name: "room",
|
|
131461
131543
|
version: "0.2.0",
|
|
131462
|
-
tools: [makeRoomSayTool(this.
|
|
131544
|
+
tools: [makeRoomSayTool((text) => this.deliver(text))]
|
|
131463
131545
|
});
|
|
131464
131546
|
const sdkOptions = {
|
|
131465
131547
|
model: this.opts.model,
|
|
@@ -131505,7 +131587,47 @@ var PersistentClaudeSession = class {
|
|
|
131505
131587
|
};
|
|
131506
131588
|
|
|
131507
131589
|
// src/bridge.ts
|
|
131508
|
-
|
|
131590
|
+
var ActiveTarget = class {
|
|
131591
|
+
target = null;
|
|
131592
|
+
setRoom(roomId) {
|
|
131593
|
+
this.target = { kind: "room", roomId };
|
|
131594
|
+
}
|
|
131595
|
+
setDm() {
|
|
131596
|
+
this.target = { kind: "dm" };
|
|
131597
|
+
}
|
|
131598
|
+
get current() {
|
|
131599
|
+
return this.target;
|
|
131600
|
+
}
|
|
131601
|
+
async reply(channel, text) {
|
|
131602
|
+
const t7 = this.target;
|
|
131603
|
+
if (!t7) return;
|
|
131604
|
+
if (t7.kind === "room") await channel.sendToRoom(t7.roomId, text);
|
|
131605
|
+
else await channel.send(text);
|
|
131606
|
+
}
|
|
131607
|
+
/**
|
|
131608
|
+
* Capture the CURRENT target into an immutable reply closure. This is the leak
|
|
131609
|
+
* fix: a reply built here routes to where its inbound message came from, even if
|
|
131610
|
+
* a later inbound flips the live `current` target. The bridge captures one of
|
|
131611
|
+
* these per inbound (at push time) and hands it to the session, so a room message
|
|
131612
|
+
* arriving mid-compose can never redirect a private 1:1 reply into the room.
|
|
131613
|
+
*/
|
|
131614
|
+
snapshotReply(channel, log = () => {
|
|
131615
|
+
}) {
|
|
131616
|
+
const t7 = this.target;
|
|
131617
|
+
const where = !t7 ? "nowhere" : t7.kind === "room" ? `room ${t7.roomId.slice(0, 8)}` : "1:1 owner";
|
|
131618
|
+
return async (text) => {
|
|
131619
|
+
if (!t7) return;
|
|
131620
|
+
try {
|
|
131621
|
+
if (t7.kind === "room") await channel.sendToRoom(t7.roomId, text);
|
|
131622
|
+
else await channel.send(text);
|
|
131623
|
+
log(`said to ${where}`);
|
|
131624
|
+
} catch (err) {
|
|
131625
|
+
log(`send failed to ${where}: ${err instanceof Error ? err.message : String(err)}`);
|
|
131626
|
+
}
|
|
131627
|
+
};
|
|
131628
|
+
}
|
|
131629
|
+
};
|
|
131630
|
+
function attachLifecycle2(channel, opts = {}) {
|
|
131509
131631
|
const log = opts.log ?? (() => {
|
|
131510
131632
|
});
|
|
131511
131633
|
const onTerminal = opts.onTerminal ?? (() => process.exit(1));
|
|
@@ -131525,7 +131647,7 @@ function attachLifecycle(channel, opts = {}) {
|
|
|
131525
131647
|
if (/reconnect loop detected/i.test(msg)) terminal("reconnect_loop");
|
|
131526
131648
|
});
|
|
131527
131649
|
}
|
|
131528
|
-
function wireBridge(channel, session, opts = {}) {
|
|
131650
|
+
function wireBridge(channel, session, target, opts = {}) {
|
|
131529
131651
|
const log = opts.log ?? (() => {
|
|
131530
131652
|
});
|
|
131531
131653
|
channel.on("error", (err) => {
|
|
@@ -131535,8 +131657,14 @@ function wireBridge(channel, session, opts = {}) {
|
|
|
131535
131657
|
channel.on("room_message", (e7) => {
|
|
131536
131658
|
if (opts.roomFilter && e7.roomId !== opts.roomFilter) return;
|
|
131537
131659
|
log(`inbound from ${e7.senderName} in ${e7.roomId.slice(0, 8)}`);
|
|
131538
|
-
|
|
131539
|
-
session.push(`[${e7.senderName}]: ${e7.plaintext}
|
|
131660
|
+
target.setRoom(e7.roomId);
|
|
131661
|
+
session.push(`[${e7.senderName}]: ${e7.plaintext}`, target.snapshotReply(channel, log));
|
|
131662
|
+
});
|
|
131663
|
+
channel.on("message", (text, metadata) => {
|
|
131664
|
+
if (metadata?.roomId) return;
|
|
131665
|
+
log("inbound 1:1 DM from owner");
|
|
131666
|
+
target.setDm();
|
|
131667
|
+
session.push(text, target.snapshotReply(channel, log));
|
|
131540
131668
|
});
|
|
131541
131669
|
}
|
|
131542
131670
|
|
|
@@ -131549,7 +131677,9 @@ async function main() {
|
|
|
131549
131677
|
"[bridge] warning: passing the invite token on the command line is visible to other local users via 'ps'. Prefer: AV_INVITE_TOKEN=\u2026 npx @agentvault/claude-bridge"
|
|
131550
131678
|
);
|
|
131551
131679
|
}
|
|
131552
|
-
|
|
131680
|
+
console.error(`[bridge] data dir: ${cfg.dataDir} (${cfg.dataDirSource})`);
|
|
131681
|
+
const target = new ActiveTarget();
|
|
131682
|
+
if (cfg.roomFilter) target.setRoom(cfg.roomFilter);
|
|
131553
131683
|
const channel = new SecureChannel({
|
|
131554
131684
|
inviteToken: cfg.inviteToken,
|
|
131555
131685
|
dataDir: cfg.dataDir,
|
|
@@ -131559,28 +131689,22 @@ async function main() {
|
|
|
131559
131689
|
});
|
|
131560
131690
|
const session = new PersistentClaudeSession({
|
|
131561
131691
|
model: cfg.model,
|
|
131562
|
-
systemPrompt: cfg.systemPrompt ?? `You are ${cfg.agentName}, an AI agent
|
|
131563
|
-
// Claude speaks by calling
|
|
131564
|
-
|
|
131565
|
-
|
|
131566
|
-
|
|
131567
|
-
|
|
131568
|
-
console.error(`[bridge] said in ${activeRoomId.slice(0, 8)}`);
|
|
131569
|
-
} catch (err) {
|
|
131570
|
-
console.error(`[bridge] send failed to ${activeRoomId.slice(0, 8)}:`, err);
|
|
131571
|
-
}
|
|
131572
|
-
},
|
|
131692
|
+
systemPrompt: cfg.systemPrompt ?? `You are ${cfg.agentName}, an AI agent on AgentVault. You talk with your owner in 1:1 direct messages and collaborate with other agents in shared rooms. That is your identity \u2014 introduce yourself by that name and do not claim to be any other agent. To speak, call the say tool. In a 1:1 with your owner, reply to what they say. In a room you see every message \u2014 call say only when you have something worth adding, and otherwise stay silent. Keep messages concise.`,
|
|
131693
|
+
// Claude speaks by calling the say tool → the session routes it to the reply
|
|
131694
|
+
// bound to the message being answered (wireBridge captures that per inbound via
|
|
131695
|
+
// ActiveTarget.snapshotReply, which also logs the "said to …" line). This
|
|
131696
|
+
// per-message binding is what prevents a private 1:1 reply from leaking into a
|
|
131697
|
+
// room when room traffic arrives mid-compose.
|
|
131573
131698
|
// Assistant reasoning that wasn't sent — log a short trace only.
|
|
131574
131699
|
onObserve: (text) => console.error(`[bridge] observed (${text.length} chars, not sent)`)
|
|
131575
131700
|
});
|
|
131576
131701
|
wireBridge(
|
|
131577
131702
|
channel,
|
|
131578
|
-
{ push: (t7) => session.push(t7
|
|
131579
|
-
|
|
131580
|
-
} },
|
|
131703
|
+
{ push: (t7, reply) => session.push(t7, reply) },
|
|
131704
|
+
target,
|
|
131581
131705
|
{ roomFilter: cfg.roomFilter, log: (m6) => console.error("[bridge] " + m6) }
|
|
131582
131706
|
);
|
|
131583
|
-
|
|
131707
|
+
attachLifecycle2(channel, {
|
|
131584
131708
|
log: (m6) => console.error("[bridge] " + m6)
|
|
131585
131709
|
});
|
|
131586
131710
|
channel.on("state", (s10) => console.error(`[bridge] channel state: ${JSON.stringify(s10)}`));
|
package/dist/session.d.ts
CHANGED
|
@@ -45,9 +45,11 @@ export type QueryFn = (args: {
|
|
|
45
45
|
type: string;
|
|
46
46
|
[k: string]: unknown;
|
|
47
47
|
}>;
|
|
48
|
+
type ReplySink = (text: string) => void | Promise<void>;
|
|
48
49
|
export interface SessionOpts {
|
|
49
|
-
/**
|
|
50
|
-
|
|
50
|
+
/** Fallback reply sink when a message carries no per-message reply (e.g. a
|
|
51
|
+
* proactive say). Per-message replies passed to push() take precedence. */
|
|
52
|
+
onSay?: ReplySink;
|
|
51
53
|
/** Called with assistant text that is NOT sent to the room (for logging). */
|
|
52
54
|
onObserve?: (text: string) => void;
|
|
53
55
|
model?: string;
|
|
@@ -58,9 +60,17 @@ export declare class PersistentClaudeSession {
|
|
|
58
60
|
private opts;
|
|
59
61
|
private waiting;
|
|
60
62
|
private pending;
|
|
63
|
+
/** Reply sink bound to the message currently being processed. Set as each
|
|
64
|
+
* message is handed to the model so the say tool routes to the right place. */
|
|
65
|
+
private activeReply?;
|
|
61
66
|
constructor(opts: SessionOpts);
|
|
62
|
-
push(text: string): void;
|
|
67
|
+
push(text: string, reply?: ReplySink): void;
|
|
68
|
+
/** Route a say-tool message to the reply bound to the in-flight message, falling
|
|
69
|
+
* back to opts.onSay. This is what closes the DM→room leak: the destination is
|
|
70
|
+
* the one captured for the message being answered, not a live global target. */
|
|
71
|
+
deliver(text: string): Promise<void>;
|
|
63
72
|
private input;
|
|
64
73
|
start(): Promise<void>;
|
|
65
74
|
}
|
|
75
|
+
export {};
|
|
66
76
|
//# sourceMappingURL=session.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentvault/claude-bridge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "AgentVault Claude
|
|
5
|
+
"description": "AgentVault Claude Bridge — daemon for bridging a Claude agent into secure E2E-encrypted AgentVault 1:1 direct messages and rooms.",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
8
|
"bin": {
|