viber-channel 0.8.38 → 0.8.39
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/lib/agent_tools.ts +19 -2
- package/lib/bridge_tool_host.ts +2 -1
- package/lib/claude_tool_defs.ts +2 -1
- package/lib/peers.ts +35 -4
- package/package.json +1 -1
package/lib/agent_tools.ts
CHANGED
|
@@ -194,11 +194,20 @@ export async function messageAgent(
|
|
|
194
194
|
}
|
|
195
195
|
if (!outcome.ok) {
|
|
196
196
|
let friendly: string;
|
|
197
|
-
if (outcome.detail === "target_offline") {
|
|
197
|
+
if (outcome.detail === "target_offline" && outcome.liveInstanceIds) {
|
|
198
|
+
// #692: the id is stale and its agent is online under several ids (a
|
|
199
|
+
// reconnect race) — the server refused to guess.
|
|
200
|
+
friendly =
|
|
201
|
+
`${trimmedPeerId} is a stale id: that agent reconnected and is online under ` +
|
|
202
|
+
`${outcome.liveInstanceIds.join(", ")}. Call message_agent again with one of them.`;
|
|
203
|
+
} else if (outcome.detail === "target_offline") {
|
|
198
204
|
// #627: a just-launched agent comes online with its runner's first beat (≤ 30 s).
|
|
205
|
+
// #692: an agent that reconnected under a new id is followed by the server;
|
|
206
|
+
// reaching here means no live instance of it was found under any id.
|
|
199
207
|
friendly =
|
|
200
208
|
"That agent is offline right now — it must be running to receive a DM. " +
|
|
201
|
-
"If it was just launched, it comes online with its runner's next beat (within 30 s): retry then."
|
|
209
|
+
"If it was just launched, it comes online with its runner's next beat (within 30 s): retry then. " +
|
|
210
|
+
"If you expected it online, check list_agents: it may have been relaunched under another name.";
|
|
202
211
|
} else if (outcome.detail === "dm_unavailable_retry") {
|
|
203
212
|
friendly = "Could not open the DM due to a transient conflict — try message_agent again.";
|
|
204
213
|
} else {
|
|
@@ -233,6 +242,14 @@ export async function messageAgent(
|
|
|
233
242
|
if (!result.ok) {
|
|
234
243
|
return errorText(result.detail);
|
|
235
244
|
}
|
|
245
|
+
if (outcome.redirect) {
|
|
246
|
+
// #692: say which agent was reached, and that the id used is stale.
|
|
247
|
+
return text(
|
|
248
|
+
`Message sent to agent ${outcome.redirect.to} (DM ${outcome.dm.conversation_id}). ` +
|
|
249
|
+
`Note: ${outcome.redirect.from} is a stale id — the same agent reconnected as ` +
|
|
250
|
+
`${outcome.redirect.to}; use that id from now on.`,
|
|
251
|
+
);
|
|
252
|
+
}
|
|
236
253
|
return text(`Message sent to agent (DM ${outcome.dm.conversation_id}).`);
|
|
237
254
|
}
|
|
238
255
|
|
package/lib/bridge_tool_host.ts
CHANGED
|
@@ -115,7 +115,8 @@ export const TOOL_DEFS = [
|
|
|
115
115
|
"(from list_agents) and the text. Opens or reuses a private 1:1 DM and posts your message; " +
|
|
116
116
|
"the agent's reply arrives back on this channel. Targets are normally same-project; an " +
|
|
117
117
|
"ORCHESTRATOR instance (#307) can also message agents of the owner's other projects. " +
|
|
118
|
-
"Fails if the target agent is offline."
|
|
118
|
+
"Fails if the target agent is offline. An id gone stale because the agent reconnected " +
|
|
119
|
+
"(same name, new id) still reaches it: the result then names the new id to use (#692).",
|
|
119
120
|
inputSchema: {
|
|
120
121
|
type: "object",
|
|
121
122
|
properties: {
|
package/lib/claude_tool_defs.ts
CHANGED
|
@@ -104,7 +104,8 @@ export const CLAUDE_TOOL_DEFS = [
|
|
|
104
104
|
"the agent's reply arrives back on this channel tagged source=agent-dm. " +
|
|
105
105
|
"Targets are normally same-project; an ORCHESTRATOR instance (#307) can also message agents " +
|
|
106
106
|
"of the owner's other projects (ids from its cross-project list_agents). " +
|
|
107
|
-
"Fails if the target agent is offline."
|
|
107
|
+
"Fails if the target agent is offline. An id gone stale because the agent reconnected " +
|
|
108
|
+
"(same name, new id) still reaches it: the result then names the new id to use (#692).",
|
|
108
109
|
inputSchema: {
|
|
109
110
|
type: "object" as const,
|
|
110
111
|
properties: {
|
package/lib/peers.ts
CHANGED
|
@@ -153,9 +153,22 @@ export interface OpenDmResult {
|
|
|
153
153
|
invited: boolean;
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
/** #692: the server opened the DM with the live instance of the identity the
|
|
157
|
+
* caller named by a stale id. */
|
|
158
|
+
export interface OpenDmRedirect {
|
|
159
|
+
from: string;
|
|
160
|
+
to: string;
|
|
161
|
+
}
|
|
162
|
+
|
|
156
163
|
export type OpenDmOutcome =
|
|
157
|
-
| { ok: true; dm: OpenDmResult }
|
|
158
|
-
| { ok: false; detail: string; status: number };
|
|
164
|
+
| { ok: true; dm: OpenDmResult; redirect?: OpenDmRedirect }
|
|
165
|
+
| { ok: false; detail: string; status: number; liveInstanceIds?: string[] };
|
|
166
|
+
|
|
167
|
+
/** Server-supplied ids end up in the model's prompt: only an instance id shape
|
|
168
|
+
* (32 lowercase hex) is ever echoed back, never free text. */
|
|
169
|
+
function instanceIdOrNull(v: unknown): string | null {
|
|
170
|
+
return typeof v === "string" && /^[0-9a-f]{32}$/.test(v) ? v : null;
|
|
171
|
+
}
|
|
159
172
|
|
|
160
173
|
/**
|
|
161
174
|
* Open-or-create a 1:1 DM with `peerInstanceId` (POST /api/instances/dms).
|
|
@@ -180,13 +193,20 @@ export async function openDm(
|
|
|
180
193
|
|
|
181
194
|
if (!resp.ok) {
|
|
182
195
|
let detail = `HTTP ${resp.status}`;
|
|
196
|
+
let liveInstanceIds: string[] | undefined;
|
|
183
197
|
try {
|
|
184
|
-
const json = (await resp.json()) as { detail?: string };
|
|
198
|
+
const json = (await resp.json()) as { detail?: string; live_instance_ids?: unknown };
|
|
185
199
|
if (typeof json.detail === "string") detail = json.detail;
|
|
200
|
+
// #692: several live instances of a stale id's identity — the server
|
|
201
|
+
// refused to guess and named them.
|
|
202
|
+
if (Array.isArray(json.live_instance_ids)) {
|
|
203
|
+
const ids = json.live_instance_ids.map(instanceIdOrNull).filter((id): id is string => id !== null);
|
|
204
|
+
if (ids.length > 0) liveInstanceIds = ids;
|
|
205
|
+
}
|
|
186
206
|
} catch {
|
|
187
207
|
/* non-JSON body */
|
|
188
208
|
}
|
|
189
|
-
return { ok: false, detail, status: resp.status };
|
|
209
|
+
return { ok: false, detail, status: resp.status, ...(liveInstanceIds ? { liveInstanceIds } : {}) };
|
|
190
210
|
}
|
|
191
211
|
|
|
192
212
|
const body = (await resp.json()) as {
|
|
@@ -194,6 +214,8 @@ export async function openDm(
|
|
|
194
214
|
conversation_token?: string;
|
|
195
215
|
expires_at?: number;
|
|
196
216
|
invited?: boolean;
|
|
217
|
+
peer_instance_id?: unknown;
|
|
218
|
+
redirected_from?: unknown;
|
|
197
219
|
};
|
|
198
220
|
if (!body.conversation?.id || typeof body.conversation_token !== "string") {
|
|
199
221
|
throw new Error("openDm: malformed response (missing conversation id or token)");
|
|
@@ -206,5 +228,14 @@ export async function openDm(
|
|
|
206
228
|
expires_at: typeof body.expires_at === "number" ? body.expires_at : 0,
|
|
207
229
|
invited: body.invited === true,
|
|
208
230
|
},
|
|
231
|
+
...redirectOf(body.redirected_from, body.peer_instance_id),
|
|
209
232
|
};
|
|
210
233
|
}
|
|
234
|
+
|
|
235
|
+
/** #692: both ids well-formed and distinct, or no redirect at all. An older
|
|
236
|
+
* server sends neither field. */
|
|
237
|
+
function redirectOf(from: unknown, to: unknown): { redirect?: OpenDmRedirect } {
|
|
238
|
+
const f = instanceIdOrNull(from);
|
|
239
|
+
const t = instanceIdOrNull(to);
|
|
240
|
+
return f && t && f !== t ? { redirect: { from: f, to: t } } : {};
|
|
241
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "viber-channel",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.39",
|
|
4
4
|
"description": "Voice + text MCP channel between a Claude Code session and the Viber UI (https://viber.dgypx.dev). Push transcripts to Claude; send_message tool delivers text back to the UI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|