@agentvault/muse-bridge 0.1.32 → 0.1.33

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 CHANGED
@@ -92,6 +92,22 @@ export interface ReplyChannel {
92
92
  groupId?: string;
93
93
  }): Promise<T>;
94
94
  }
95
+ /**
96
+ * "Recent problems" (2026-10-26): every say delivery is one
97
+ * `ai.agent.tool.invoke` span (tool `say`) on the channel's existing telemetry
98
+ * pipe. Only the outcome, a machine error code, the duration and the
99
+ * conversation it answers — never the text or the error message. Never throws.
100
+ */
101
+ /**
102
+ * #1467: a Muse turn failed. One `ai.agent.tool.invoke` error span (tool
103
+ * `model_turn`): `model_rate_limited` when Meta answered 429, else
104
+ * `model_error`. No conversation and no text. Never throws.
105
+ */
106
+ export declare function reportTurnFailure(channel: {
107
+ telemetry?: ToolSpanSink | null;
108
+ } | undefined, failure: {
109
+ upstreamStatus?: number;
110
+ }): void;
95
111
  export declare class ActiveTarget {
96
112
  private target;
97
113
  setRoom(roomId: string): void;
package/dist/index.js CHANGED
@@ -11803,7 +11803,7 @@ function ownIdentity() {
11803
11803
  return `${PACKAGE}@${v2}`;
11804
11804
  }
11805
11805
  function buildSha() {
11806
- const raw = true ? "9599d8e" : "";
11806
+ const raw = true ? "628e3ba" : "";
11807
11807
  const cleaned = (raw ?? "").trim();
11808
11808
  return !cleaned || cleaned === "unknown" ? null : cleaned;
11809
11809
  }
@@ -12658,6 +12658,10 @@ function buildToolInvokeSpan(report) {
12658
12658
  "tool.duration_ms": durationMs
12659
12659
  };
12660
12660
  if (isError) attributes["tool.error_kind"] = safeErrorKind(report.errorKind);
12661
+ const resets = report.limitResetsAt;
12662
+ if (attributes["tool.error_kind"] === "plan_limit_reached" && typeof resets === "number" && Number.isInteger(resets) && resets > 0 && resets < 1e11) {
12663
+ attributes["av.limit_resets_at"] = resets;
12664
+ }
12661
12665
  if (kind !== "unknown" && typeof conv.id === "string" && conv.id) {
12662
12666
  attributes["av.conversation_id"] = conv.id.slice(0, 64);
12663
12667
  }
@@ -71487,6 +71491,11 @@ var init_dist = __esm({
71487
71491
  "ambiguous_target",
71488
71492
  "no_receipt",
71489
71493
  "invalid_request",
71494
+ // #1467: the owner's own AI plan refused the turn (a bridge on a subscription).
71495
+ "plan_limit_reached",
71496
+ // #1467: a Muse turn failed — Meta answered 429, or anything else.
71497
+ "model_rate_limited",
71498
+ "model_error",
71490
71499
  UNCLASSIFIED_ERROR
71491
71500
  ];
71492
71501
  KNOWN_CODES = new Set(KNOWN_TOOL_ERROR_CODES);
@@ -103567,6 +103576,17 @@ import { chmodSync as chmodSync5, mkdirSync as mkdirSync6 } from "node:fs";
103567
103576
  import { join as join17 } from "node:path";
103568
103577
 
103569
103578
  // src/muse-session.ts
103579
+ var ERROR_TYPE = /\(([a-z][a-z_]{2,40})\)\s*$/;
103580
+ function describeTurnFailure(err) {
103581
+ const message = err instanceof Error ? err.message : "";
103582
+ if (message === "muse turn stopped") return null;
103583
+ const status = err?.upstreamStatus;
103584
+ const type = ERROR_TYPE.exec(message.slice(-200))?.[1];
103585
+ return {
103586
+ ...typeof status === "number" && Number.isInteger(status) && status >= 400 && status <= 599 ? { upstreamStatus: status } : {},
103587
+ ...type ? { errorType: type } : {}
103588
+ };
103589
+ }
103570
103590
  var MuseSession = class {
103571
103591
  constructor(driver, opts = {}) {
103572
103592
  this.driver = driver;
@@ -103638,8 +103658,17 @@ var MuseSession = class {
103638
103658
  ${item.text}` : item.text;
103639
103659
  const result = await this.driver.runTurn(prompt, { ownerTurn: item.ownerTurn });
103640
103660
  await this.maybeFallback(item, result);
103641
- } catch {
103642
- this.log("turn failed reason=driver");
103661
+ } catch (err) {
103662
+ const failure2 = describeTurnFailure(err);
103663
+ if (failure2) {
103664
+ this.log(`turn failed reason=driver${failure2.upstreamStatus ? ` upstream_status=${failure2.upstreamStatus}` : ""}${failure2.errorType ? ` error_type=${failure2.errorType}` : ""}`);
103665
+ try {
103666
+ this.opts.onTurnFailed?.(failure2);
103667
+ } catch {
103668
+ }
103669
+ } else {
103670
+ this.log("turn failed reason=stopped");
103671
+ }
103643
103672
  await this.failureNotice(item);
103644
103673
  } finally {
103645
103674
  this.current = null;
@@ -103767,6 +103796,16 @@ var META_RESPONSES_URL = "https://api.meta.ai/v1/responses";
103767
103796
  var BODY_LIMIT = 16 * 1024 * 1024;
103768
103797
  var ERROR_LIMIT = 64 * 1024;
103769
103798
  var HOP_HEADERS = ["connection", "keep-alive", "proxy-authenticate", "proxy-authorization", "te", "trailer", "transfer-encoding", "upgrade"];
103799
+ var LIMIT_HEADER = /^(?:retry-after|x-ratelimit-[a-z0-9-]{1,40}|ratelimit(?:-[a-z0-9-]{1,40})?)$/;
103800
+ var LIMIT_VALUE = /^[0-9][0-9.smh]{0,15}$/;
103801
+ function limitHeaderNote(headers) {
103802
+ const parts = [];
103803
+ headers.forEach((value, name) => {
103804
+ if (parts.length >= 8 || !LIMIT_HEADER.test(name)) return;
103805
+ parts.push(`${name}=${LIMIT_VALUE.test(value.trim()) ? value.trim() : "present"}`);
103806
+ });
103807
+ return parts.length ? ` ${parts.join(" ")}` : "";
103808
+ }
103770
103809
  function record2(value) {
103771
103810
  return value !== null && typeof value === "object" && !Array.isArray(value);
103772
103811
  }
@@ -103885,6 +103924,7 @@ async function createMuseProviderAdapter(opts) {
103885
103924
  } catch {
103886
103925
  }
103887
103926
  };
103927
+ let lastStatus;
103888
103928
  const server = createServer2((req, res) => {
103889
103929
  const abort = new AbortController();
103890
103930
  const stop = () => abort.abort();
@@ -103963,6 +104003,10 @@ async function createMuseProviderAdapter(opts) {
103963
104003
  }
103964
104004
  }
103965
104005
  }
104006
+ if (!response.ok) {
104007
+ lastStatus = response.status;
104008
+ log(`upstream_error status=${response.status}${limitHeaderNote(response.headers)}`);
104009
+ }
103966
104010
  await forward(response, res, abort.signal, inspected);
103967
104011
  })().catch(() => {
103968
104012
  log("request_failed reason=transport");
@@ -104004,7 +104048,11 @@ async function createMuseProviderAdapter(opts) {
104004
104048
  await close();
104005
104049
  throw new Error("provider adapter unavailable");
104006
104050
  }
104007
- return { baseUrl: `http://127.0.0.1:${address.port}${route.slice(0, -"/responses".length)}`, close };
104051
+ return {
104052
+ baseUrl: `http://127.0.0.1:${address.port}${route.slice(0, -"/responses".length)}`,
104053
+ close,
104054
+ upstreamStatus: () => lastStatus
104055
+ };
104008
104056
  }
104009
104057
 
104010
104058
  // src/muse-driver.ts
@@ -104101,6 +104149,12 @@ var MuseDriver = class {
104101
104149
  if (this.current?.cancelled) throw new Error("muse turn stopped");
104102
104150
  spawn2.args.splice(2, 0, "--base-url", adapter.baseUrl);
104103
104151
  return await this.execJsonl(spawn2);
104152
+ } catch (err) {
104153
+ const status = adapter.upstreamStatus?.();
104154
+ if (typeof status === "number" && err instanceof Error) {
104155
+ err.upstreamStatus = status;
104156
+ }
104157
+ throw err;
104104
104158
  } finally {
104105
104159
  await adapter.close();
104106
104160
  if (this.adapter === adapter) this.adapter = null;
@@ -104724,6 +104778,18 @@ var RoomHushState = class {
104724
104778
  return t2 !== void 0 && now < t2;
104725
104779
  }
104726
104780
  };
104781
+ function reportTurnFailure(channel, failure2) {
104782
+ try {
104783
+ reportToolInvoke(channel?.telemetry, {
104784
+ toolName: "model_turn",
104785
+ outcome: "error",
104786
+ errorKind: failure2.upstreamStatus === 429 ? "model_rate_limited" : "model_error",
104787
+ durationMs: 0,
104788
+ conversation: { kind: "unknown" }
104789
+ });
104790
+ } catch {
104791
+ }
104792
+ }
104727
104793
  function reportSay(channel, t2, startedAt, failure2) {
104728
104794
  try {
104729
104795
  reportToolInvoke(channel.telemetry, {
@@ -105004,7 +105070,7 @@ function mkdir700(path2) {
105004
105070
  chmodSync5(path2, 448);
105005
105071
  }
105006
105072
  function packageVersion() {
105007
- return true ? "0.1.32" : void 0;
105073
+ return true ? "0.1.33" : void 0;
105008
105074
  }
105009
105075
  function helpText(version2) {
105010
105076
  return [
@@ -105129,7 +105195,13 @@ async function main(opts = {}) {
105129
105195
  spawnFn: opts.spawnFn
105130
105196
  });
105131
105197
  const identity = identityPrompt(cfg.agentName, SAY_TOOL_NAME);
105132
- session = new MuseSession(driver, { log: (m2) => logLine(m2), identity });
105198
+ session = new MuseSession(driver, {
105199
+ log: (m2) => logLine(m2),
105200
+ identity,
105201
+ // #1467: every failed turn reaches Recent problems (and Admin) — `channel`
105202
+ // is read when the failure happens, after it is connected.
105203
+ onTurnFailed: (failure2) => reportTurnFailure(channel, failure2)
105204
+ });
105133
105205
  try {
105134
105206
  await driver.start();
105135
105207
  } catch (err) {
@@ -1,7 +1,10 @@
1
1
  export interface MuseProviderAdapter {
2
2
  baseUrl: string;
3
3
  close(): Promise<void>;
4
+ /** #1467: the last error status Meta returned this turn; undefined if none. */
5
+ upstreamStatus?(): number | undefined;
4
6
  }
7
+ export declare function limitHeaderNote(headers: Headers): string;
5
8
  export interface MuseProviderAdapterOptions {
6
9
  timeoutMs: number;
7
10
  log?: (message: string) => void;
@@ -20,6 +20,16 @@ export interface DriverLike {
20
20
  * cannot redirect a private 1:1 reply, because the newer message only becomes
21
21
  * current when its own turn starts.
22
22
  */
23
+ /** #1467: what is safe to keep about a failed turn — never its message text. */
24
+ export interface TurnFailure {
25
+ /** The error status Meta's API returned this turn, when it returned one. */
26
+ upstreamStatus?: number;
27
+ /** The provider's error type, e.g. "invalid_request_error" — only a short
28
+ * lower-case token in parentheses at the end of Muse's reason. */
29
+ errorType?: string;
30
+ }
31
+ /** A failed turn → what to report, or null when the turn was stopped on purpose. */
32
+ export declare function describeTurnFailure(err: unknown): TurnFailure | null;
23
33
  export declare class MuseSession implements RoomSession {
24
34
  private readonly driver;
25
35
  private readonly opts;
@@ -32,6 +42,8 @@ export declare class MuseSession implements RoomSession {
32
42
  onSay?: (text: string) => Promise<void>;
33
43
  log?: (m: string) => void;
34
44
  identity?: string;
45
+ /** #1467: a turn failed (not stopped). Fire-and-forget; a throw is swallowed. */
46
+ onTurnFailed?: (failure: TurnFailure) => void;
35
47
  });
36
48
  push(text: string, reply?: (text: string) => Promise<void>, opts?: {
37
49
  autoReplyOnText?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentvault/muse-bridge",
3
- "version": "0.1.32",
3
+ "version": "0.1.33",
4
4
  "type": "module",
5
5
  "description": "AgentVault Muse Bridge — daemon bridging a Muse agent into E2E-encrypted AgentVault direct messages and rooms.",
6
6
  "main": "dist/index.js",