@agentvault/openai-bridge 0.1.13 → 0.1.14

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 CHANGED
@@ -42,13 +42,13 @@ If Codex is upgraded, install the supported version again before restarting. If
42
42
  AV_AGENT_NAME=aster AV_INVITE_TOKEN='<fresh-invite>' agentvault-openai-bridge replace
43
43
  ```
44
44
 
45
- Replacement acquires the agent lock before archiving prior AgentVault credentials under the private `replacement-recovery/<uuid>/` directory and forcing enrollment. Codex login and history are retained. Existing credentials are never silently reused for this explicit command. If enrollment fails, the archive remains for deliberate operator recovery; normal restarts do not restore it automatically. After approval, stop the foreground process and install the service again. Do not copy another agent's credentials into this state directory.
45
+ Replacement acquires the agent lock before archiving prior AgentVault credentials under the private `replacement-recovery/<uuid>/` directory and forcing enrollment. Codex login and history are retained. This explicit command never reuses existing credentials silently. If enrollment fails, the archive remains for deliberate operator recovery; normal restarts do not restore it automatically. After approval, stop the foreground process and install the service again. Do not copy another agent's credentials into this state directory.
46
46
 
47
47
  The shared SecureChannel currently persists new enrollment keys only after activation. A crash before approval can leave the invite consumed without recoverable local keys. Retaining `pending-invite` does not solve that gap: obtain a fresh recovery invite if the interrupted enrollment cannot resume.
48
48
 
49
49
  ## Conversation and privacy boundaries
50
50
 
51
- Only direct messages positively attested by SecureChannel as owner 1:1 messages can dispatch a private turn. Rooms answer according to AgentVault's shared mention policy, evaluated on the typed caption, and remain silent while hushed. Recent context comes only from the addressed room. Replies are bound to the original conversation ID or room ID. Device ID, conversation kind, and conversation ID jointly select a separate persistent Codex session UUID; the bridge never resumes “last”.
51
+ Only direct messages positively attested by SecureChannel as owner 1:1 messages can dispatch a private turn. Rooms answer according to AgentVault's shared mention policy, evaluated on the typed caption, and remain silent while hushed. Rooms use recent messages from the addressed room; a 1:1 that starts a new Codex session is seeded with that 1:1's recent messages only. A chat opened from a browser the agent has not mapped yet may start without earlier messages until the agent learns which person it belongs to. Replies are bound to the original conversation ID or room ID. Device ID, conversation kind, and conversation ID jointly select a separate persistent Codex session UUID; the bridge never resumes “last”.
52
52
 
53
53
  Each agent stores its state beneath `~/.agentvault/openai/<name-slug>/`, including isolated `home`, `codex`, `work`, `tmp`, and `sessions` directories (0700). Codex auth and local conversation transcripts persist there. Encryption protects AgentVault transport; local Codex history is plaintext and relies on the host account's filesystem protections. Do not share this directory.
54
54
 
@@ -56,6 +56,31 @@ Codex children receive only a dedicated HOME, CODEX_HOME, TMPDIR, and a minimal
56
56
 
57
57
  The bridge accepts only a terminal successful assistant reply after exit 0. Because the exec stream omits message phase, it additionally verifies the current turn's `final_answer` in the bounded local Codex rollout; a prior resumed turn or commentary cannot authorize delivery. Partial, failed, malformed, oversized or timed-out turns are discarded. Disconnect and shutdown invalidate pending results. One bounded queue serializes inference and awaited sending; child process groups are killed and reaped before another turn starts. Logs contain bridge-owned status codes, without prompts, replies, tokens or provider diagnostics.
58
58
 
59
+ ## When a reply fails
60
+
61
+ When a turn fails, the bridge records it first and then tells the chat. It does not drop a failed turn without a trace.
62
+
63
+ - **In a 1:1**, the owner sees one plain sentence that names the cause when the bridge can tell what it is. For example: "Aster couldn't answer this message: its OpenAI sign-in expired. Sign in again on the agent's computer." This bridge can also tell when OpenAI can't be reached and when the Codex CLI needs attention. The shared list also covers billing problems, rate limits and retired models, but this bridge does not detect those yet. When the cause is unclear, the sentence simply says the agent couldn't answer this message.
64
+ - **In a room**, the sentence does not name the cause, because other people and agents can read it. The room just sees that the agent couldn't answer this message.
65
+ - **In both cases**, the exact cause appears privately to the owner in the app under **Health, Recent problems**.
66
+
67
+ These failure notices are left out of the context the bridge sends to Codex on later turns.
68
+
69
+ The bridge log (`agentvault-openai-bridge logs`) writes one line per event:
70
+
71
+ | Log line | What it means |
72
+ |----------|---------------|
73
+ | `turn_failed code=<code> exit=<n or none>` | Codex did not produce a verified reply. The code is the cause the bridge assigned, and `exit` is the Codex exit status, or `none` when there is no exit status to report. A notice follows. |
74
+ | `reply_failed code=<code>` | Codex answered, but the reply could not be delivered to the chat. |
75
+ | `notice_failed code=<code>` | The turn failed, and the failure notice could not be delivered either. The failure is still recorded under Health. |
76
+ | `turn_cancelled reason=<reason>` | The turn was cut short before it could finish, so no reply or notice is posted. Reasons: `stop` (the bridge is shutting down), `revoked` (the agent's device was revoked), `channel_state` (the encrypted channel dropped or reconnected), `delivery_timeout` (sending took too long), `hushed` (the room was hushed), `device_changed` (the agent's device ID changed), and `unknown`. |
77
+ | `turn_recovered code=session_resume_failed` | The saved Codex session for this chat was gone, so the bridge started a fresh one and its reply passed the final-answer check. Delivery is then logged as usual. See below. |
78
+ | `dm_dropped reason=unmapped_conversation` | A 1:1 message arrived that the agent cannot yet match to a conversation. That message got no answer and no notice yet. |
79
+
80
+ **Self-restart of a saved session.** Each chat keeps its own saved Codex session in the `sessions` directory. If that saved session can no longer be found, the bridge runs one fresh session, seeded with the recent messages of that same chat. It replaces the saved session only after the fresh reply passes the same final-answer check as any other reply. The first session it replaced is kept in a `.prev` file next to it, and that file is never overwritten, so a later restart cannot erase the original. If the fresh session also fails, nothing is replaced and the turn is reported as `turn_failed`.
81
+
82
+ The log holds only these codes and exit numbers. It never holds message text or provider error text.
83
+
59
84
  ## Local checks
60
85
 
61
86
  ```sh
package/dist/bridge.d.ts CHANGED
@@ -16,6 +16,13 @@ export interface BridgeChannel {
16
16
  text: string;
17
17
  sender: string;
18
18
  }>;
19
+ /** #1560: one person's 1:1 history, scoped by the group the frame decrypted under. */
20
+ getConversationHistory?(id: string, max: number, opts?: {
21
+ groupId?: string;
22
+ }): Array<{
23
+ text: string;
24
+ sender: string;
25
+ }>;
19
26
  send(text: string, options: {
20
27
  conversationId: string;
21
28
  }): Promise<unknown>;
@@ -33,7 +40,9 @@ export interface BridgeChannel {
33
40
  } & Partial<ToolSpanSink>) | null;
34
41
  }
35
42
  export interface TurnDriver {
36
- run(key: string, prompt: string, signal?: AbortSignal): Promise<TurnResult>;
43
+ run(key: string, prompt: string, signal?: AbortSignal, opts?: {
44
+ freshContext?: string;
45
+ }): Promise<TurnResult>;
37
46
  }
38
47
  /** Serial inference AND delivery; every queued entry owns its authenticated target. */
39
48
  export declare class OpenAIBridge {
@@ -49,6 +58,8 @@ export declare class OpenAIBridge {
49
58
  private ready;
50
59
  private accepting;
51
60
  private waiting;
61
+ /** The reason each generation ended, keyed by the generation it ended (#1560). */
62
+ private cancelReasons;
52
63
  private stopped;
53
64
  private active?;
54
65
  private hush;
@@ -56,8 +67,20 @@ export declare class OpenAIBridge {
56
67
  constructor(channel: BridgeChannel, driver: TurnDriver, name: string, report?: (event: string) => void, maxQueue?: number, deliveryTimeoutMs?: number);
57
68
  private listen;
58
69
  private hushed;
70
+ /** Ends the current generation. Its reason is recorded BEFORE the bump, under
71
+ * the generation it ends, so a later invalidate (stop after revoked) cannot
72
+ * overwrite why the turns of that generation were cut. */
59
73
  private invalidate;
74
+ private cancelReason;
75
+ /** Recent entries as `sender: text`, without the bridge's own failure notices. */
76
+ private context;
60
77
  private enqueue;
78
+ /** #1560: a turn that failed (not one cancelled on purpose). RECORD FIRST —
79
+ * the span and the log line — then post the notice, so a notice that stalls
80
+ * or fails cannot lose the record. Only codes and the exit status are
81
+ * logged, never the error's text. In a room the notice is the bare sentence:
82
+ * the coded ones describe the owner's own provider account. */
83
+ private turnFailed;
61
84
  /** Health (2026-10-14): tokens for the model call. No cost — the bridge runs
62
85
  * on a ChatGPT login, so there is no per-token price to report honestly. */
63
86
  private reportUsage;
package/dist/cli.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { type HistoryRecoveryCommandInput } from "@agentvault/agentvault";
2
2
  import { type Layout } from "./driver.js";
3
- export declare const VERSION = "0.1.13";
3
+ export declare const VERSION = "0.1.14";
4
4
  export interface RunConfig {
5
5
  name: string;
6
6
  apiUrl: string;
package/dist/driver.d.ts CHANGED
@@ -1,5 +1,10 @@
1
+ import { type TurnFailureCode } from "@agentvault/agentvault";
1
2
  export declare const SUPPORTED_CODEX_VERSION = "0.154.0";
2
3
  export declare const CODE_MODE_WARNING = "Code Mode is unavailable because code-mode host is disabled. Code mode will fail closed; enable `features.code_mode_host` and install `codex-code-mode-host`.";
4
+ /** #1560 — observed on 0.154.0 as an `item.completed` error item when the
5
+ * model name has no local metadata. Codex carries on and answers, so it is a
6
+ * warning, not a failed turn (like CODE_MODE_WARNING). */
7
+ export declare const MODEL_METADATA_WARNING: RegExp;
3
8
  export interface Layout {
4
9
  root: string;
5
10
  home: string;
@@ -17,7 +22,29 @@ export declare function buildInvocation(layout: Layout, sessionId?: string): {
17
22
  cwd: string;
18
23
  };
19
24
  export declare function resolveCodex(path?: string): Promise<string>;
20
- export declare function checkVersion(binary: string, layout: Layout): Promise<void>;
25
+ /**
26
+ * #1560 (audit-3 F7): a `--version` that does not answer in time is a slow or
27
+ * loaded machine, not a wrong install, so it is the bare `model_error`. Only a
28
+ * binary that cannot run at all, or answers with the wrong version, is
29
+ * `ai_tool_unsupported`. No retry here: on a machine that is already slow a
30
+ * retry doubles the wait (follow-up 10).
31
+ */
32
+ export declare function checkVersion(binary: string, layout: Layout, timeoutMs?: number): Promise<void>;
33
+ /**
34
+ * #1560 — map Codex failure text to a shared turn-failure code. Only a code
35
+ * comes back; the text itself is never kept, logged or thrown.
36
+ *
37
+ * Every branch is a literal `return "<code>"` on purpose: the plugin's
38
+ * tool-error-codes.test.ts scans this file for `return "<code>"` and checks
39
+ * each against KNOWN_TOOL_ERROR_CODES. A lookup table, ternary or variable
40
+ * would hide a code from that scan, so do not refactor this into one.
41
+ *
42
+ * Only shapes observed from Codex 0.154.0 are matched; anything else is the
43
+ * bare `model_error`.
44
+ * TODO(#1560 follow-up): the usage-limit strings map to plan_limit_reached,
45
+ * and when billing and rate-limit rules are added, billing must precede rate.
46
+ */
47
+ export declare function classifyCodexFailure(text: string): TurnFailureCode;
21
48
  /** The one model this bridge runs (pinned in buildInvocation). */
22
49
  export declare const CODEX_MODEL = "gpt-5.6-terra";
23
50
  /** Token usage Codex reports on `turn.completed`, for Health (2026-10-14). */
@@ -43,6 +70,14 @@ export interface DriverOptions {
43
70
  killGraceMs?: number;
44
71
  maxLineBytes?: number;
45
72
  maxOutputBytes?: number;
73
+ /** `--version` timeout (default 5000 ms). */
74
+ versionTimeoutMs?: number;
75
+ /** Called once after a refused resume was replaced by a fresh, verified session. */
76
+ onRecovered?: (code: "session_resume_failed") => void;
77
+ }
78
+ export interface RunOptions {
79
+ /** Recent messages of this conversation, sent only when a session starts fresh. */
80
+ freshContext?: string;
46
81
  }
47
82
  /** One caller at a time. The bridge serializes delivery as well as inference. */
48
83
  export declare class CodexDriver {
@@ -50,7 +85,13 @@ export declare class CodexDriver {
50
85
  private busy;
51
86
  private poisoned;
52
87
  constructor(options: DriverOptions);
53
- run(key: string, prompt: string, signal?: AbortSignal): Promise<TurnResult>;
88
+ run(key: string, prompt: string, signal?: AbortSignal, opts?: RunOptions): Promise<TurnResult>;
89
+ /**
90
+ * #1560 — the stored session is gone: run exactly one fresh, seeded session.
91
+ * The stored id is replaced only after the fresh reply verifies; the first
92
+ * replaced id is kept in `<file>.prev` and never overwritten.
93
+ */
94
+ private recover;
54
95
  private execute;
55
96
  }
56
97
  //# sourceMappingURL=driver.d.ts.map