@agentvault/claude-bridge 0.2.0 → 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 +163 -36
- 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,8 +96402,12 @@ 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
|
|
96408
|
+
// AgentVault name via AV_AGENT_NAME (the native connect command passes it).
|
|
96409
|
+
// The neutral default avoids impersonating any specific named agent when unset.
|
|
96410
|
+
agentName: env.AV_AGENT_NAME ?? "Claude",
|
|
96341
96411
|
roomFilter: env.AV_ROOM_ID || void 0,
|
|
96342
96412
|
model: env.AV_CLAUDE_MODEL || void 0,
|
|
96343
96413
|
systemPrompt: env.AV_SYSTEM_PROMPT || void 0
|
|
@@ -118291,7 +118361,7 @@ __export(util_exports2, {
|
|
|
118291
118361
|
required: () => required2,
|
|
118292
118362
|
safeExtend: () => safeExtend2,
|
|
118293
118363
|
shallowClone: () => shallowClone2,
|
|
118294
|
-
slugify: () =>
|
|
118364
|
+
slugify: () => slugify3,
|
|
118295
118365
|
stringifyPrimitive: () => stringifyPrimitive2,
|
|
118296
118366
|
uint8ArrayToBase64: () => uint8ArrayToBase643,
|
|
118297
118367
|
uint8ArrayToBase64url: () => uint8ArrayToBase64url2,
|
|
@@ -118432,7 +118502,7 @@ function randomString2(length = 10) {
|
|
|
118432
118502
|
function esc2(str) {
|
|
118433
118503
|
return JSON.stringify(str);
|
|
118434
118504
|
}
|
|
118435
|
-
function
|
|
118505
|
+
function slugify3(input) {
|
|
118436
118506
|
return input.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
118437
118507
|
}
|
|
118438
118508
|
var captureStackTrace2 = "captureStackTrace" in Error ? Error.captureStackTrace : (..._args) => {
|
|
@@ -128166,7 +128236,7 @@ function _toUpperCase2() {
|
|
|
128166
128236
|
}
|
|
128167
128237
|
// @__NO_SIDE_EFFECTS__
|
|
128168
128238
|
function _slugify2() {
|
|
128169
|
-
return /* @__PURE__ */ _overwrite2((input) =>
|
|
128239
|
+
return /* @__PURE__ */ _overwrite2((input) => slugify3(input));
|
|
128170
128240
|
}
|
|
128171
128241
|
// @__NO_SIDE_EFFECTS__
|
|
128172
128242
|
function _array2(Class3, element, params) {
|
|
@@ -131410,11 +131480,11 @@ config2(en_default3());
|
|
|
131410
131480
|
function makeRoomSayTool(onSay) {
|
|
131411
131481
|
return bs(
|
|
131412
131482
|
"say",
|
|
131413
|
-
"Send a message to the current AgentVault
|
|
131414
|
-
{ 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.") },
|
|
131415
131485
|
async (args) => {
|
|
131416
131486
|
await onSay(args.message);
|
|
131417
|
-
return { content: [{ type: "text", text: "Message sent
|
|
131487
|
+
return { content: [{ type: "text", text: "Message sent." }] };
|
|
131418
131488
|
}
|
|
131419
131489
|
);
|
|
131420
131490
|
}
|
|
@@ -131426,37 +131496,52 @@ var PersistentClaudeSession = class {
|
|
|
131426
131496
|
opts;
|
|
131427
131497
|
waiting = null;
|
|
131428
131498
|
pending = [];
|
|
131429
|
-
|
|
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) {
|
|
131430
131503
|
const msg = {
|
|
131431
131504
|
type: "user",
|
|
131432
131505
|
message: { role: "user", content: text },
|
|
131433
131506
|
parent_tool_use_id: null,
|
|
131434
131507
|
session_id: ""
|
|
131435
131508
|
};
|
|
131509
|
+
const item = { msg, reply };
|
|
131436
131510
|
if (this.waiting) {
|
|
131437
131511
|
const w2 = this.waiting;
|
|
131438
131512
|
this.waiting = null;
|
|
131439
|
-
w2(
|
|
131513
|
+
w2(item);
|
|
131440
131514
|
} else {
|
|
131441
|
-
this.pending.push(
|
|
131515
|
+
this.pending.push(item);
|
|
131442
131516
|
}
|
|
131443
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
|
+
}
|
|
131444
131525
|
async *input() {
|
|
131445
131526
|
while (true) {
|
|
131446
131527
|
if (this.pending.length > 0) {
|
|
131447
|
-
|
|
131528
|
+
const item2 = this.pending.shift();
|
|
131529
|
+
this.activeReply = item2.reply;
|
|
131530
|
+
yield item2.msg;
|
|
131448
131531
|
continue;
|
|
131449
131532
|
}
|
|
131450
|
-
|
|
131533
|
+
const item = await new Promise((resolve2) => {
|
|
131451
131534
|
this.waiting = resolve2;
|
|
131452
131535
|
});
|
|
131536
|
+
this.activeReply = item.reply;
|
|
131537
|
+
yield item.msg;
|
|
131453
131538
|
}
|
|
131454
131539
|
}
|
|
131455
131540
|
async start() {
|
|
131456
131541
|
const roomServer = _s({
|
|
131457
131542
|
name: "room",
|
|
131458
131543
|
version: "0.2.0",
|
|
131459
|
-
tools: [makeRoomSayTool(this.
|
|
131544
|
+
tools: [makeRoomSayTool((text) => this.deliver(text))]
|
|
131460
131545
|
});
|
|
131461
131546
|
const sdkOptions = {
|
|
131462
131547
|
model: this.opts.model,
|
|
@@ -131502,7 +131587,47 @@ var PersistentClaudeSession = class {
|
|
|
131502
131587
|
};
|
|
131503
131588
|
|
|
131504
131589
|
// src/bridge.ts
|
|
131505
|
-
|
|
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 = {}) {
|
|
131506
131631
|
const log = opts.log ?? (() => {
|
|
131507
131632
|
});
|
|
131508
131633
|
const onTerminal = opts.onTerminal ?? (() => process.exit(1));
|
|
@@ -131522,7 +131647,7 @@ function attachLifecycle(channel, opts = {}) {
|
|
|
131522
131647
|
if (/reconnect loop detected/i.test(msg)) terminal("reconnect_loop");
|
|
131523
131648
|
});
|
|
131524
131649
|
}
|
|
131525
|
-
function wireBridge(channel, session, opts = {}) {
|
|
131650
|
+
function wireBridge(channel, session, target, opts = {}) {
|
|
131526
131651
|
const log = opts.log ?? (() => {
|
|
131527
131652
|
});
|
|
131528
131653
|
channel.on("error", (err) => {
|
|
@@ -131532,8 +131657,14 @@ function wireBridge(channel, session, opts = {}) {
|
|
|
131532
131657
|
channel.on("room_message", (e7) => {
|
|
131533
131658
|
if (opts.roomFilter && e7.roomId !== opts.roomFilter) return;
|
|
131534
131659
|
log(`inbound from ${e7.senderName} in ${e7.roomId.slice(0, 8)}`);
|
|
131535
|
-
|
|
131536
|
-
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));
|
|
131537
131668
|
});
|
|
131538
131669
|
}
|
|
131539
131670
|
|
|
@@ -131546,7 +131677,9 @@ async function main() {
|
|
|
131546
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"
|
|
131547
131678
|
);
|
|
131548
131679
|
}
|
|
131549
|
-
|
|
131680
|
+
console.error(`[bridge] data dir: ${cfg.dataDir} (${cfg.dataDirSource})`);
|
|
131681
|
+
const target = new ActiveTarget();
|
|
131682
|
+
if (cfg.roomFilter) target.setRoom(cfg.roomFilter);
|
|
131550
131683
|
const channel = new SecureChannel({
|
|
131551
131684
|
inviteToken: cfg.inviteToken,
|
|
131552
131685
|
dataDir: cfg.dataDir,
|
|
@@ -131556,28 +131689,22 @@ async function main() {
|
|
|
131556
131689
|
});
|
|
131557
131690
|
const session = new PersistentClaudeSession({
|
|
131558
131691
|
model: cfg.model,
|
|
131559
|
-
systemPrompt: cfg.systemPrompt ??
|
|
131560
|
-
// Claude speaks by calling
|
|
131561
|
-
|
|
131562
|
-
|
|
131563
|
-
|
|
131564
|
-
|
|
131565
|
-
console.error(`[bridge] said in ${activeRoomId.slice(0, 8)}`);
|
|
131566
|
-
} catch (err) {
|
|
131567
|
-
console.error(`[bridge] send failed to ${activeRoomId.slice(0, 8)}:`, err);
|
|
131568
|
-
}
|
|
131569
|
-
},
|
|
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.
|
|
131570
131698
|
// Assistant reasoning that wasn't sent — log a short trace only.
|
|
131571
131699
|
onObserve: (text) => console.error(`[bridge] observed (${text.length} chars, not sent)`)
|
|
131572
131700
|
});
|
|
131573
131701
|
wireBridge(
|
|
131574
131702
|
channel,
|
|
131575
|
-
{ push: (t7) => session.push(t7
|
|
131576
|
-
|
|
131577
|
-
} },
|
|
131703
|
+
{ push: (t7, reply) => session.push(t7, reply) },
|
|
131704
|
+
target,
|
|
131578
131705
|
{ roomFilter: cfg.roomFilter, log: (m6) => console.error("[bridge] " + m6) }
|
|
131579
131706
|
);
|
|
131580
|
-
|
|
131707
|
+
attachLifecycle2(channel, {
|
|
131581
131708
|
log: (m6) => console.error("[bridge] " + m6)
|
|
131582
131709
|
});
|
|
131583
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": {
|