codex-channel-bgos 0.2.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.
Files changed (151) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +114 -0
  3. package/dist/adapter.d.ts +96 -0
  4. package/dist/adapter.d.ts.map +1 -0
  5. package/dist/adapter.js +677 -0
  6. package/dist/adapter.js.map +1 -0
  7. package/dist/agent-hints.d.ts +20 -0
  8. package/dist/agent-hints.d.ts.map +1 -0
  9. package/dist/agent-hints.js +92 -0
  10. package/dist/agent-hints.js.map +1 -0
  11. package/dist/agent-identity.d.ts +36 -0
  12. package/dist/agent-identity.d.ts.map +1 -0
  13. package/dist/agent-identity.js +42 -0
  14. package/dist/agent-identity.js.map +1 -0
  15. package/dist/attachment-bridge.d.ts +64 -0
  16. package/dist/attachment-bridge.d.ts.map +1 -0
  17. package/dist/attachment-bridge.js +216 -0
  18. package/dist/attachment-bridge.js.map +1 -0
  19. package/dist/auth-mode.d.ts +34 -0
  20. package/dist/auth-mode.d.ts.map +1 -0
  21. package/dist/auth-mode.js +48 -0
  22. package/dist/auth-mode.js.map +1 -0
  23. package/dist/bgos-api.d.ts +191 -0
  24. package/dist/bgos-api.d.ts.map +1 -0
  25. package/dist/bgos-api.js +225 -0
  26. package/dist/bgos-api.js.map +1 -0
  27. package/dist/bgos-ws.d.ts +94 -0
  28. package/dist/bgos-ws.d.ts.map +1 -0
  29. package/dist/bgos-ws.js +310 -0
  30. package/dist/bgos-ws.js.map +1 -0
  31. package/dist/capabilities.d.ts +28 -0
  32. package/dist/capabilities.d.ts.map +1 -0
  33. package/dist/capabilities.js +44 -0
  34. package/dist/capabilities.js.map +1 -0
  35. package/dist/catalog-sync.d.ts +26 -0
  36. package/dist/catalog-sync.d.ts.map +1 -0
  37. package/dist/catalog-sync.js +24 -0
  38. package/dist/catalog-sync.js.map +1 -0
  39. package/dist/cli.d.ts +3 -0
  40. package/dist/cli.d.ts.map +1 -0
  41. package/dist/cli.js +152 -0
  42. package/dist/cli.js.map +1 -0
  43. package/dist/codex-host.d.ts +44 -0
  44. package/dist/codex-host.d.ts.map +1 -0
  45. package/dist/codex-host.js +142 -0
  46. package/dist/codex-host.js.map +1 -0
  47. package/dist/commands-sync.d.ts +24 -0
  48. package/dist/commands-sync.d.ts.map +1 -0
  49. package/dist/commands-sync.js +43 -0
  50. package/dist/commands-sync.js.map +1 -0
  51. package/dist/config.d.ts +4 -0
  52. package/dist/config.d.ts.map +1 -0
  53. package/dist/config.js +36 -0
  54. package/dist/config.js.map +1 -0
  55. package/dist/default-commands.d.ts +27 -0
  56. package/dist/default-commands.d.ts.map +1 -0
  57. package/dist/default-commands.js +64 -0
  58. package/dist/default-commands.js.map +1 -0
  59. package/dist/event-mapper.d.ts +43 -0
  60. package/dist/event-mapper.d.ts.map +1 -0
  61. package/dist/event-mapper.js +127 -0
  62. package/dist/event-mapper.js.map +1 -0
  63. package/dist/heartbeat.d.ts +75 -0
  64. package/dist/heartbeat.d.ts.map +1 -0
  65. package/dist/heartbeat.js +186 -0
  66. package/dist/heartbeat.js.map +1 -0
  67. package/dist/inbound-handler.d.ts +167 -0
  68. package/dist/inbound-handler.d.ts.map +1 -0
  69. package/dist/inbound-handler.js +282 -0
  70. package/dist/inbound-handler.js.map +1 -0
  71. package/dist/inbound-input.d.ts +17 -0
  72. package/dist/inbound-input.d.ts.map +1 -0
  73. package/dist/inbound-input.js +25 -0
  74. package/dist/inbound-input.js.map +1 -0
  75. package/dist/index.d.ts +25 -0
  76. package/dist/index.d.ts.map +1 -0
  77. package/dist/index.js +18 -0
  78. package/dist/index.js.map +1 -0
  79. package/dist/install-service.d.ts +8 -0
  80. package/dist/install-service.d.ts.map +1 -0
  81. package/dist/install-service.js +66 -0
  82. package/dist/install-service.js.map +1 -0
  83. package/dist/last-id-store.d.ts +33 -0
  84. package/dist/last-id-store.d.ts.map +1 -0
  85. package/dist/last-id-store.js +101 -0
  86. package/dist/last-id-store.js.map +1 -0
  87. package/dist/load-config.d.ts +30 -0
  88. package/dist/load-config.d.ts.map +1 -0
  89. package/dist/load-config.js +86 -0
  90. package/dist/load-config.js.map +1 -0
  91. package/dist/media-classify.d.ts +37 -0
  92. package/dist/media-classify.d.ts.map +1 -0
  93. package/dist/media-classify.js +108 -0
  94. package/dist/media-classify.js.map +1 -0
  95. package/dist/media-guard.d.ts +13 -0
  96. package/dist/media-guard.d.ts.map +1 -0
  97. package/dist/media-guard.js +159 -0
  98. package/dist/media-guard.js.map +1 -0
  99. package/dist/outbound-retry.d.ts +24 -0
  100. package/dist/outbound-retry.d.ts.map +1 -0
  101. package/dist/outbound-retry.js +75 -0
  102. package/dist/outbound-retry.js.map +1 -0
  103. package/dist/outbound.d.ts +223 -0
  104. package/dist/outbound.d.ts.map +1 -0
  105. package/dist/outbound.js +366 -0
  106. package/dist/outbound.js.map +1 -0
  107. package/dist/outbox.d.ts +16 -0
  108. package/dist/outbox.d.ts.map +1 -0
  109. package/dist/outbox.js +88 -0
  110. package/dist/outbox.js.map +1 -0
  111. package/dist/pair-cli.d.ts +44 -0
  112. package/dist/pair-cli.d.ts.map +1 -0
  113. package/dist/pair-cli.js +182 -0
  114. package/dist/pair-cli.js.map +1 -0
  115. package/dist/pending-unknown-store.d.ts +26 -0
  116. package/dist/pending-unknown-store.d.ts.map +1 -0
  117. package/dist/pending-unknown-store.js +135 -0
  118. package/dist/pending-unknown-store.js.map +1 -0
  119. package/dist/processed-ids.d.ts +36 -0
  120. package/dist/processed-ids.d.ts.map +1 -0
  121. package/dist/processed-ids.js +55 -0
  122. package/dist/processed-ids.js.map +1 -0
  123. package/dist/reply-markers.d.ts +42 -0
  124. package/dist/reply-markers.d.ts.map +1 -0
  125. package/dist/reply-markers.js +124 -0
  126. package/dist/reply-markers.js.map +1 -0
  127. package/dist/setup/supervisor.d.ts +16 -0
  128. package/dist/setup/supervisor.d.ts.map +1 -0
  129. package/dist/setup/supervisor.js +61 -0
  130. package/dist/setup/supervisor.js.map +1 -0
  131. package/dist/thread-map.d.ts +12 -0
  132. package/dist/thread-map.d.ts.map +1 -0
  133. package/dist/thread-map.js +72 -0
  134. package/dist/thread-map.js.map +1 -0
  135. package/dist/tool-progress.d.ts +103 -0
  136. package/dist/tool-progress.d.ts.map +1 -0
  137. package/dist/tool-progress.js +216 -0
  138. package/dist/tool-progress.js.map +1 -0
  139. package/dist/types.d.ts +244 -0
  140. package/dist/types.d.ts.map +1 -0
  141. package/dist/types.js +16 -0
  142. package/dist/types.js.map +1 -0
  143. package/dist/version.d.ts +2 -0
  144. package/dist/version.d.ts.map +1 -0
  145. package/dist/version.js +28 -0
  146. package/dist/version.js.map +1 -0
  147. package/dist/voice-rpc.d.ts +37 -0
  148. package/dist/voice-rpc.d.ts.map +1 -0
  149. package/dist/voice-rpc.js +44 -0
  150. package/dist/voice-rpc.js.map +1 -0
  151. package/package.json +47 -0
package/dist/outbox.js ADDED
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Durable outbound spool for the BGOS channel.
3
+ *
4
+ * When an outbound send fails 3 times against the safe network-error class
5
+ * (see outbound.ts classifyOutboundError), the payload is appended here so a
6
+ * later reconnect / 60s replay tick can retry it. This is safe ONLY for the
7
+ * provably-undelivered network-error class: the backend message insert is not
8
+ * idempotent, so ambiguous failures (timeouts, 5xx) are never spooled.
9
+ *
10
+ * File: `$CODEX_BGOS_HOME/bgos_outbox.jsonl` (default `~/.codex-bgos/bgos_outbox.jsonl`),
11
+ * one JSON object per line. Cap 200 entries (drop-oldest). Entries older than
12
+ * 24h are dropped on read. All writes are best-effort: a spool hiccup must
13
+ * never crash the daemon.
14
+ */
15
+ import { appendFileSync, mkdirSync, readFileSync, renameSync, writeFileSync, } from "node:fs";
16
+ import { homedir } from "node:os";
17
+ import { dirname, join } from "node:path";
18
+ const MAX_ENTRIES = 200;
19
+ const MAX_AGE_MS = 24 * 60 * 60 * 1000;
20
+ function outboxPath() {
21
+ const root = process.env.CODEX_BGOS_HOME ?? join(homedir(), ".codex-bgos");
22
+ return join(root, "bgos_outbox.jsonl");
23
+ }
24
+ /** Read the spool, dropping entries older than 24h. Never throws. */
25
+ export function loadOutbox() {
26
+ let raw;
27
+ try {
28
+ raw = readFileSync(outboxPath(), "utf8");
29
+ }
30
+ catch {
31
+ return [];
32
+ }
33
+ const now = Date.now();
34
+ const out = [];
35
+ for (const line of raw.split("\n")) {
36
+ const trimmed = line.trim();
37
+ if (!trimmed)
38
+ continue;
39
+ try {
40
+ const entry = JSON.parse(trimmed);
41
+ if (entry &&
42
+ typeof entry.ts === "number" &&
43
+ now - entry.ts <= MAX_AGE_MS &&
44
+ entry.payload) {
45
+ out.push(entry);
46
+ }
47
+ }
48
+ catch {
49
+ // skip malformed line
50
+ }
51
+ }
52
+ return out;
53
+ }
54
+ /** Append one entry, then compact to the newest MAX_ENTRIES. Never throws. */
55
+ export function appendOutbox(entry) {
56
+ try {
57
+ const target = outboxPath();
58
+ mkdirSync(dirname(target), { recursive: true });
59
+ appendFileSync(target, JSON.stringify(entry) + "\n", { mode: 0o600 });
60
+ // Compact if we may have exceeded the cap.
61
+ const entries = loadOutbox();
62
+ if (entries.length > MAX_ENTRIES) {
63
+ rewriteOutbox(entries.slice(entries.length - MAX_ENTRIES));
64
+ }
65
+ }
66
+ catch {
67
+ /* best-effort */
68
+ }
69
+ }
70
+ /** Atomically replace the spool with `entries` (tmp + rename). Never throws. */
71
+ export function rewriteOutbox(entries) {
72
+ try {
73
+ const target = outboxPath();
74
+ mkdirSync(dirname(target), { recursive: true });
75
+ const body = entries.map((e) => JSON.stringify(e)).join("\n");
76
+ const tmp = `${target}.${process.pid}.tmp`;
77
+ writeFileSync(tmp, body ? body + "\n" : "", { mode: 0o600 });
78
+ renameSync(tmp, target);
79
+ }
80
+ catch {
81
+ /* best-effort */
82
+ }
83
+ }
84
+ /** Empty the spool. Never throws. */
85
+ export function clearOutbox() {
86
+ rewriteOutbox([]);
87
+ }
88
+ //# sourceMappingURL=outbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outbox.js","sourceRoot":"","sources":["../src/outbox.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env node
2
+ import { type AgentCatalogEntry, type PairExchangeResponse } from "./types.js";
3
+ /**
4
+ * CLI entry point for `codex-pair-bgos`.
5
+ *
6
+ * Two modes:
7
+ * - Pair-code mode: `codex-pair-bgos <CODE>`, POST /integrations/pair-exchange
8
+ * with the code, a device label, the daemon version, and the local agent
9
+ * catalog, then persist the returned token.
10
+ * - Token mode: `codex-pair-bgos --token -` (reads the token from stdin) or
11
+ * `--token <value>` (scripting; shell-history caveat documented). Validates
12
+ * via GET /integrations/me (learns pairing_id/user_id), then persists.
13
+ *
14
+ * The secrets file is written ATOMICALLY (tmp + rename, 0600) in both modes.
15
+ *
16
+ * Exit codes:
17
+ * 0 success
18
+ * 1 network error
19
+ * 2 invalid/consumed/expired code, or invalid token (401)
20
+ */
21
+ export interface PairResult {
22
+ pairing: PairExchangeResponse;
23
+ tokenFile: string;
24
+ }
25
+ export interface PairCliOptions {
26
+ baseUrl: string;
27
+ code: string;
28
+ deviceLabel?: string;
29
+ agentCatalog?: AgentCatalogEntry[];
30
+ secretsDir?: string;
31
+ }
32
+ export declare function pairBgos(opts: PairCliOptions): Promise<PairResult>;
33
+ export interface PairWithTokenOptions {
34
+ baseUrl: string;
35
+ token: string;
36
+ secretsDir?: string;
37
+ }
38
+ /**
39
+ * Token mode: validate an existing pairing token via GET /integrations/me
40
+ * (learns pairing_id/user_id), then persist the secrets file atomically.
41
+ * Throws PairingRevokedError (mapped from 401) for an invalid token.
42
+ */
43
+ export declare function pairBgosWithToken(opts: PairWithTokenOptions): Promise<PairResult>;
44
+ //# sourceMappingURL=pair-cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pair-cli.d.ts","sourceRoot":"","sources":["../src/pair-cli.ts"],"names":[],"mappings":";AAOA,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EAC1B,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,oBAAoB,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,iBAAiB,EAAE,CAAC;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAiCD,wBAAsB,QAAQ,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC,CAsBxE;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,oBAAoB,GACzB,OAAO,CAAC,UAAU,CAAC,CAuBrB"}
@@ -0,0 +1,182 @@
1
+ #!/usr/bin/env node
2
+ import { hostname } from "node:os";
3
+ import { mkdir, rename, writeFile } from "node:fs/promises";
4
+ import { join } from "node:path";
5
+ import { BgosApi } from "./bgos-api.js";
6
+ import { getPackageVersion } from "./version.js";
7
+ import { PairingRevokedError, } from "./types.js";
8
+ function defaultSecretsDir() {
9
+ // Respect CODEX_BGOS_HOME so pairing writes the token where `start` reads it
10
+ // (every other module resolves the home the same way). Falls back to
11
+ // ~/.codex-bgos when the override is unset.
12
+ const override = process.env.CODEX_BGOS_HOME?.trim();
13
+ if (override) {
14
+ const root = override.startsWith("~")
15
+ ? join(process.env.HOME ?? ".", override.slice(1))
16
+ : override;
17
+ return join(root, "secrets");
18
+ }
19
+ return join(process.env.HOME ?? process.env.USERPROFILE ?? ".", ".codex-bgos", "secrets");
20
+ }
21
+ /** Atomically persist the secrets file (tmp + rename, 0600). */
22
+ async function writeSecretsAtomic(secretsDir, payload) {
23
+ await mkdir(secretsDir, { recursive: true });
24
+ const tokenFile = join(secretsDir, "bgos.json");
25
+ const tmp = `${tokenFile}.${process.pid}.tmp`;
26
+ await writeFile(tmp, JSON.stringify(payload, null, 2), { mode: 0o600 });
27
+ await rename(tmp, tokenFile);
28
+ return tokenFile;
29
+ }
30
+ export async function pairBgos(opts) {
31
+ const label = opts.deviceLabel ?? `${hostname()} (Codex)`;
32
+ const res = await BgosApi.pairExchange(opts.baseUrl, {
33
+ code: opts.code,
34
+ deviceLabel: label,
35
+ agentCatalog: opts.agentCatalog,
36
+ // Tag the pairing so it lands under the Codex card (assistants get
37
+ // created with code='codex'). Without this the backend defaults to
38
+ // 'openclaw' and the pairing surfaces in the wrong card.
39
+ integration: "codex",
40
+ daemonVersion: getPackageVersion(),
41
+ });
42
+ const secretsDir = opts.secretsDir ?? defaultSecretsDir();
43
+ const tokenFile = await writeSecretsAtomic(secretsDir, {
44
+ baseUrl: opts.baseUrl.replace(/\/+$/, ""),
45
+ pairingToken: res.pairing_token,
46
+ pairingId: res.pairing_id,
47
+ userId: res.user_id,
48
+ pairedAt: new Date().toISOString(),
49
+ });
50
+ return { pairing: res, tokenFile };
51
+ }
52
+ /**
53
+ * Token mode: validate an existing pairing token via GET /integrations/me
54
+ * (learns pairing_id/user_id), then persist the secrets file atomically.
55
+ * Throws PairingRevokedError (mapped from 401) for an invalid token.
56
+ */
57
+ export async function pairBgosWithToken(opts) {
58
+ const api = new BgosApi({
59
+ baseUrl: opts.baseUrl.replace(/\/+$/, ""),
60
+ pairingToken: opts.token,
61
+ reconnect: { initialDelayMs: 1000, maxDelayMs: 30000 },
62
+ });
63
+ const me = await api.whoami();
64
+ const secretsDir = opts.secretsDir ?? defaultSecretsDir();
65
+ const tokenFile = await writeSecretsAtomic(secretsDir, {
66
+ baseUrl: opts.baseUrl.replace(/\/+$/, ""),
67
+ pairingToken: opts.token,
68
+ pairingId: me.pairing_id,
69
+ userId: me.user_id,
70
+ pairedAt: new Date().toISOString(),
71
+ });
72
+ return {
73
+ pairing: {
74
+ pairing_token: opts.token,
75
+ pairing_id: me.pairing_id,
76
+ user_id: me.user_id,
77
+ },
78
+ tokenFile,
79
+ };
80
+ }
81
+ const DEFAULT_BASE_URL = "https://api.brandgrowthos.ai";
82
+ function parseAgentCatalog(raw) {
83
+ if (!raw)
84
+ return undefined;
85
+ const entries = raw
86
+ .split(",")
87
+ .map((s) => s.trim())
88
+ .filter(Boolean)
89
+ .map((entry) => {
90
+ const [route, name] = entry.split(":").map((p) => p.trim());
91
+ if (!route)
92
+ return null;
93
+ return { agent_route: route, name: name || route };
94
+ })
95
+ .filter((a) => a !== null);
96
+ return entries.length ? entries : undefined;
97
+ }
98
+ function takeFlag(argv, flag) {
99
+ const idx = argv.indexOf(flag);
100
+ if (idx < 0)
101
+ return undefined;
102
+ const val = argv[idx + 1];
103
+ argv.splice(idx, 2);
104
+ return val;
105
+ }
106
+ /** Read a single line/blob from stdin (used for `--token -`). */
107
+ async function readStdin() {
108
+ const chunks = [];
109
+ for await (const c of process.stdin)
110
+ chunks.push(c);
111
+ return Buffer.concat(chunks).toString("utf8").trim();
112
+ }
113
+ async function main() {
114
+ const argv = process.argv.slice(2);
115
+ const deviceLabel = takeFlag(argv, "--device-label");
116
+ const baseUrl = takeFlag(argv, "--base-url") ??
117
+ process.env.BGOS_BASE_URL ??
118
+ DEFAULT_BASE_URL;
119
+ const tokenFlag = takeFlag(argv, "--token");
120
+ // ---- Token mode ---------------------------------------------------
121
+ if (tokenFlag !== undefined) {
122
+ try {
123
+ let token = tokenFlag;
124
+ if (token === "-") {
125
+ token = await readStdin();
126
+ }
127
+ if (!token) {
128
+ process.stderr.write("No token provided on stdin.\n");
129
+ process.exit(2);
130
+ }
131
+ const { pairing, tokenFile } = await pairBgosWithToken({ baseUrl, token });
132
+ process.stdout.write(`Token accepted. Secret written to ${tokenFile}\n`);
133
+ process.stdout.write(`pairing_id=${pairing.pairing_id} user_id=${pairing.user_id}\n`);
134
+ process.exit(0);
135
+ }
136
+ catch (err) {
137
+ const e = err;
138
+ if (err instanceof PairingRevokedError || e?.response?.status === 401) {
139
+ process.stderr.write("Token rejected (401): invalid or revoked.\n");
140
+ process.exit(2);
141
+ }
142
+ process.stderr.write(`Token validation failed: ${e?.message ?? String(err)}\n`);
143
+ process.exit(1);
144
+ }
145
+ }
146
+ // ---- Pair-code mode -----------------------------------------------
147
+ const code = argv.find((a) => !a.startsWith("--"));
148
+ if (!code) {
149
+ process.stderr.write("usage: codex-pair-bgos <CODE> [--device-label NAME] [--base-url URL]\n" +
150
+ " codex-pair-bgos --token - (reads a pairing token from stdin)\n");
151
+ process.exit(2);
152
+ }
153
+ try {
154
+ const { pairing, tokenFile } = await pairBgos({
155
+ baseUrl,
156
+ code,
157
+ deviceLabel,
158
+ agentCatalog: parseAgentCatalog(process.env.CODEX_BGOS_AGENTS),
159
+ });
160
+ process.stdout.write(`Paired. Secret written to ${tokenFile}\n`);
161
+ process.stdout.write(`pairing_id=${pairing.pairing_id} user_id=${pairing.user_id}\n`);
162
+ process.exit(0);
163
+ }
164
+ catch (err) {
165
+ const e = err;
166
+ const status = e?.response?.status;
167
+ if (status === 400 || status === 404 || status === 410) {
168
+ process.stderr.write(`Pair code rejected (${status}): ${JSON.stringify(e.response?.data ?? "expired or consumed")}\n`);
169
+ process.exit(2);
170
+ }
171
+ process.stderr.write(`Pair failed: ${e?.message ?? String(err)}\n`);
172
+ process.exit(1);
173
+ }
174
+ }
175
+ // Run when invoked as a CLI (not when imported as a library).
176
+ const invokedAsCli = typeof process !== "undefined" &&
177
+ Array.isArray(process.argv) &&
178
+ /pair-cli(?:\.[mc]?[jt]s)?$/.test(process.argv[1] ?? "");
179
+ if (invokedAsCli) {
180
+ void main();
181
+ }
182
+ //# sourceMappingURL=pair-cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pair-cli.js","sourceRoot":"","sources":["../src/pair-cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ export interface PendingUnknownEntry {
2
+ /** BGOS inbound message id left unconsumed. */
3
+ id: number;
4
+ /** Epoch ms when first recorded. */
5
+ at: number;
6
+ }
7
+ /** Read the persisted pending set. Never throws; returns [] on any error. */
8
+ export declare function loadPendingUnknown(): PendingUnknownEntry[];
9
+ /**
10
+ * Record an id as pending-unknown. Idempotent (a repeated poll re-fetch of the
11
+ * same unconsumed id does not duplicate the entry). Bounded to MAX_PENDING by
12
+ * dropping the oldest entries.
13
+ */
14
+ export declare function recordPendingUnknown(id: number): void;
15
+ /** Remove an id from the pending set (it has now been consumed). No-op when
16
+ * the id is absent. Never throws. */
17
+ export declare function clearPendingUnknown(id: number): void;
18
+ /** Lowest still-pending unknown id, or null when the set is empty. Used by
19
+ * saveLastId to clamp the disk cursor. */
20
+ export declare function lowestPendingUnknown(): number | null;
21
+ /** Count + oldest timestamp, for the adapter's visible stuck telemetry. */
22
+ export declare function pendingUnknownStats(): {
23
+ count: number;
24
+ oldestAt: number | null;
25
+ };
26
+ //# sourceMappingURL=pending-unknown-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pending-unknown-store.d.ts","sourceRoot":"","sources":["../src/pending-unknown-store.ts"],"names":[],"mappings":"AAkCA,MAAM,WAAW,mBAAmB;IAClC,+CAA+C;IAC/C,EAAE,EAAE,MAAM,CAAC;IACX,oCAAoC;IACpC,EAAE,EAAE,MAAM,CAAC;CACZ;AAWD,6EAA6E;AAC7E,wBAAgB,kBAAkB,IAAI,mBAAmB,EAAE,CA6B1D;AAeD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAUrD;AAED;sCACsC;AACtC,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAKpD;AAED;2CAC2C;AAC3C,wBAAgB,oBAAoB,IAAI,MAAM,GAAG,IAAI,CAMpD;AAED,2EAA2E;AAC3E,wBAAgB,mBAAmB,IAAI;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAMhF"}
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Durable set of inbound message ids that arrived for an assistant NOT yet in
3
+ * the route map and were therefore left UNCONSUMED by the inbound handler.
4
+ *
5
+ * Why this exists (silent-loss fix). The disk cursor (bgos_last_id) advances
6
+ * monotonically as KNOWN messages are consumed. A sibling known message with a
7
+ * HIGHER id than an unconsumed unknown-assistant message would push the cursor
8
+ * PAST the hole, so inboundSince(cursor) would never return the unknown message
9
+ * again, even after its route later heals. Worst on multi-assistant pairings
10
+ * (one bound sibling advancing the cursor past a not-yet-bound sibling's
11
+ * message) and null agent_route assistants.
12
+ *
13
+ * Persisting the pending ids lets saveLastId CLAMP the cursor so it never
14
+ * advances past the LOWEST still-pending unknown id, keeping the inbound window
15
+ * re-fetchable until identity heals. ProcessedIdsCache dedupes the already
16
+ * consumed ids above the hole, so re-fetching the window is safe.
17
+ *
18
+ * File: `$CODEX_BGOS_HOME/bgos_pending_unknown.json` (default `~/.codex-bgos/...`).
19
+ * Format: JSON array of `{ id: number, at: number }` (epoch ms), insertion
20
+ * order. Atomic writes (tmp + rename, mode 0600), same pattern as
21
+ * last-id-store. All errors swallowed: a persistence hiccup must never crash
22
+ * the adapter.
23
+ *
24
+ * Bounded to MAX_PENDING (drop-oldest). A genuinely-never-bound assistant that
25
+ * floods the lane can therefore hold the cursor back by at most the newest
26
+ * MAX_PENDING ids, never forever: the oldest pending ids age out and the cursor
27
+ * is free to advance past them. This is the "cap the clamp" guard; the adapter
28
+ * surfaces a visible heartbeat lastError while the set is stuck so the bounded
29
+ * loss is never silent.
30
+ */
31
+ import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
32
+ import { homedir } from "node:os";
33
+ import { dirname, join } from "node:path";
34
+ /** Bound on the pending set (drop-oldest). Caps how far the clamp can hold the
35
+ * cursor back so a never-binding assistant cannot wedge it forever. */
36
+ const MAX_PENDING = 50;
37
+ function pendingPath() {
38
+ const root = process.env.CODEX_BGOS_HOME ?? join(homedir(), ".codex-bgos");
39
+ return join(root, "bgos_pending_unknown.json");
40
+ }
41
+ /** Read the persisted pending set. Never throws; returns [] on any error. */
42
+ export function loadPendingUnknown() {
43
+ let raw;
44
+ try {
45
+ raw = readFileSync(pendingPath(), "utf8");
46
+ }
47
+ catch {
48
+ return [];
49
+ }
50
+ try {
51
+ const parsed = JSON.parse(raw);
52
+ if (!Array.isArray(parsed))
53
+ return [];
54
+ const out = [];
55
+ for (const e of parsed) {
56
+ if (e &&
57
+ typeof e === "object" &&
58
+ Number.isInteger(e.id) &&
59
+ e.id > 0 &&
60
+ typeof e.at === "number") {
61
+ out.push({
62
+ id: e.id,
63
+ at: e.at,
64
+ });
65
+ }
66
+ }
67
+ return out;
68
+ }
69
+ catch {
70
+ return [];
71
+ }
72
+ }
73
+ /** Atomically persist the pending set (tmp + rename). Never throws. */
74
+ function writePending(entries) {
75
+ try {
76
+ const target = pendingPath();
77
+ mkdirSync(dirname(target), { recursive: true });
78
+ const tmp = `${target}.${process.pid}.tmp`;
79
+ writeFileSync(tmp, JSON.stringify(entries), { mode: 0o600 });
80
+ renameSync(tmp, target);
81
+ }
82
+ catch {
83
+ /* swallow: persistence hiccup must never crash the adapter */
84
+ }
85
+ }
86
+ /**
87
+ * Record an id as pending-unknown. Idempotent (a repeated poll re-fetch of the
88
+ * same unconsumed id does not duplicate the entry). Bounded to MAX_PENDING by
89
+ * dropping the oldest entries.
90
+ */
91
+ export function recordPendingUnknown(id) {
92
+ if (!Number.isInteger(id) || id <= 0)
93
+ return;
94
+ const entries = loadPendingUnknown();
95
+ if (entries.some((e) => e.id === id))
96
+ return;
97
+ entries.push({ id, at: Date.now() });
98
+ const bounded = entries.length > MAX_PENDING
99
+ ? entries.slice(entries.length - MAX_PENDING)
100
+ : entries;
101
+ writePending(bounded);
102
+ }
103
+ /** Remove an id from the pending set (it has now been consumed). No-op when
104
+ * the id is absent. Never throws. */
105
+ export function clearPendingUnknown(id) {
106
+ const entries = loadPendingUnknown();
107
+ const next = entries.filter((e) => e.id !== id);
108
+ if (next.length === entries.length)
109
+ return;
110
+ writePending(next);
111
+ }
112
+ /** Lowest still-pending unknown id, or null when the set is empty. Used by
113
+ * saveLastId to clamp the disk cursor. */
114
+ export function lowestPendingUnknown() {
115
+ const entries = loadPendingUnknown();
116
+ if (entries.length === 0)
117
+ return null;
118
+ let lowest = entries[0].id;
119
+ for (const e of entries)
120
+ if (e.id < lowest)
121
+ lowest = e.id;
122
+ return lowest;
123
+ }
124
+ /** Count + oldest timestamp, for the adapter's visible stuck telemetry. */
125
+ export function pendingUnknownStats() {
126
+ const entries = loadPendingUnknown();
127
+ if (entries.length === 0)
128
+ return { count: 0, oldestAt: null };
129
+ let oldest = entries[0].at;
130
+ for (const e of entries)
131
+ if (e.at < oldest)
132
+ oldest = e.at;
133
+ return { count: entries.length, oldestAt: oldest };
134
+ }
135
+ //# sourceMappingURL=pending-unknown-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pending-unknown-store.js","sourceRoot":"","sources":["../src/pending-unknown-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Bounded FIFO cache of processed message IDs. Used to dedup inbound_message
3
+ * events that may arrive twice via independent paths:
4
+ *
5
+ * - Live WS push receives msg N
6
+ * - WS reconnect / poll triggerBackfill() refetches inbound since the disk
7
+ * cursor, re-emitting msg N if the cursor lagged behind the live emit
8
+ * - Two integration sockets briefly subscribed to assistant:<id> during a
9
+ * disconnect/reconnect race, both delivering the same payload
10
+ * - Backend transport-level retry
11
+ *
12
+ * Without dedup, the daemon dispatches each duplicate to Codex and posts the
13
+ * reply again. Ported from codex-channel-bgos/src/processed-ids.ts (originally
14
+ * openclaw-channel-bgos; observed in prod 2026-05-10: chat 917 had four
15
+ * assistant replies for two user questions, including identical-text pairs 1.3s
16
+ * apart). The Set is bounded by `capacity` (FIFO eviction by insertion order) so
17
+ * memory does not grow unbounded for long-running daemons.
18
+ */
19
+ export declare class ProcessedIdsCache {
20
+ private readonly seen;
21
+ private readonly capacity;
22
+ constructor(capacity: number);
23
+ /**
24
+ * Returns true if this is the FIRST time we have seen this id (caller
25
+ * should proceed to dispatch). Returns false if it is a duplicate (caller
26
+ * should silently skip).
27
+ *
28
+ * Note: re-marking an already-cached id does NOT promote it. Eviction
29
+ * order is strict insertion order, which is fine for our sliding-window
30
+ * use case: we do not care about LRU semantics, only "did we see this
31
+ * recently."
32
+ */
33
+ markIfFirstTime(messageId: number): boolean;
34
+ size(): number;
35
+ }
36
+ //# sourceMappingURL=processed-ids.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"processed-ids.d.ts","sourceRoot":"","sources":["../src/processed-ids.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAc;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;gBAEtB,QAAQ,EAAE,MAAM;IAK5B;;;;;;;;;OASG;IACH,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAgB3C,IAAI,IAAI,MAAM;CAGf"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Bounded FIFO cache of processed message IDs. Used to dedup inbound_message
3
+ * events that may arrive twice via independent paths:
4
+ *
5
+ * - Live WS push receives msg N
6
+ * - WS reconnect / poll triggerBackfill() refetches inbound since the disk
7
+ * cursor, re-emitting msg N if the cursor lagged behind the live emit
8
+ * - Two integration sockets briefly subscribed to assistant:<id> during a
9
+ * disconnect/reconnect race, both delivering the same payload
10
+ * - Backend transport-level retry
11
+ *
12
+ * Without dedup, the daemon dispatches each duplicate to Codex and posts the
13
+ * reply again. Ported from codex-channel-bgos/src/processed-ids.ts (originally
14
+ * openclaw-channel-bgos; observed in prod 2026-05-10: chat 917 had four
15
+ * assistant replies for two user questions, including identical-text pairs 1.3s
16
+ * apart). The Set is bounded by `capacity` (FIFO eviction by insertion order) so
17
+ * memory does not grow unbounded for long-running daemons.
18
+ */
19
+ export class ProcessedIdsCache {
20
+ seen;
21
+ capacity;
22
+ constructor(capacity) {
23
+ this.capacity = Math.max(1, capacity | 0);
24
+ this.seen = new Set();
25
+ }
26
+ /**
27
+ * Returns true if this is the FIRST time we have seen this id (caller
28
+ * should proceed to dispatch). Returns false if it is a duplicate (caller
29
+ * should silently skip).
30
+ *
31
+ * Note: re-marking an already-cached id does NOT promote it. Eviction
32
+ * order is strict insertion order, which is fine for our sliding-window
33
+ * use case: we do not care about LRU semantics, only "did we see this
34
+ * recently."
35
+ */
36
+ markIfFirstTime(messageId) {
37
+ if (this.seen.has(messageId)) {
38
+ return false;
39
+ }
40
+ this.seen.add(messageId);
41
+ if (this.seen.size > this.capacity) {
42
+ // Set iteration is insertion order in JS: the first value is the
43
+ // oldest entry. Drop it.
44
+ const oldest = this.seen.values().next().value;
45
+ if (oldest !== undefined) {
46
+ this.seen.delete(oldest);
47
+ }
48
+ }
49
+ return true;
50
+ }
51
+ size() {
52
+ return this.seen.size;
53
+ }
54
+ }
55
+ //# sourceMappingURL=processed-ids.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"processed-ids.js","sourceRoot":"","sources":["../src/processed-ids.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,OAAO,iBAAiB;IACX,IAAI,CAAc;IAClB,QAAQ,CAAS;IAElC,YAAY,QAAgB;QAC1B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,GAAG,CAAC,CAAC,CAAC;QAC1C,IAAI,CAAC,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,CAAC;IAED;;;;;;;;;OASG;IACH,eAAe,CAAC,SAAiB;QAC/B,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;YAC7B,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACzB,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnC,iEAAiE;YACjE,yBAAyB;YACzB,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,KAA2B,CAAC;YACrE,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBACzB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAC3B,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI;QACF,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;IACxB,CAAC;CACF"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Parse a Codex text reply for BGOS outbound markers and strip them from the
3
+ * user-visible text. Codex emits a single text stream, so, like OpenClaw, the
4
+ * agent-facing surface uses marker syntax (documented in agent-hints.ts and
5
+ * AGENTS.md):
6
+ *
7
+ * MEDIA:/abs/path one file per line -> outbound files[]
8
+ * STATUS: <text> set the agent status line (empty clears)
9
+ * [[BGOS_BUTTONS]] ... [[/...]] inline option buttons (Label | value, <=6)
10
+ * [[BGOS_ASK]] ... [[/BGOS_ASK]] blocking multi-question modal (<=4 questions)
11
+ *
12
+ * tool_progress is NOT a marker: Codex streams real tool events, so the daemon
13
+ * drives that card from run() events (see event-mapper.ts), not from the reply.
14
+ */
15
+ export interface ButtonOption {
16
+ text: string;
17
+ callbackData: string;
18
+ }
19
+ export interface ParsedButtons {
20
+ options: ButtonOption[];
21
+ }
22
+ export interface AskQuestion {
23
+ text: string;
24
+ options: ButtonOption[];
25
+ allowFreeText: boolean;
26
+ allowSkip: boolean;
27
+ }
28
+ export interface ParsedAsk {
29
+ questions: AskQuestion[];
30
+ }
31
+ export interface ParsedStatus {
32
+ text: string;
33
+ }
34
+ export interface ParsedReply {
35
+ cleanText: string;
36
+ media: string[];
37
+ buttons: ParsedButtons | null;
38
+ ask: ParsedAsk | null;
39
+ status: ParsedStatus | null;
40
+ }
41
+ export declare function parseReply(input: string): ParsedReply;
42
+ //# sourceMappingURL=reply-markers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reply-markers.d.ts","sourceRoot":"","sources":["../src/reply-markers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,YAAY,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,aAAa,EAAE,OAAO,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,WAAW,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,EAAE,aAAa,GAAG,IAAI,CAAC;IAC9B,GAAG,EAAE,SAAS,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;CAC7B;AA4ED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,CAoCrD"}