@ours.network/codex 1.1.1 → 1.2.0-nightly.10

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.
@@ -0,0 +1,386 @@
1
+ // src/host-hooks.mjs
2
+ import { dirname as dirname3 } from "node:path";
3
+
4
+ // ../core/src/application-identities.ts
5
+ import { randomUUID } from "node:crypto";
6
+ import { chmod, mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
7
+ import { homedir } from "node:os";
8
+ import { dirname, resolve } from "node:path";
9
+ var APPLICATION_IDENTITIES_VERSION = 1;
10
+ var APPLICATION_IDENTITIES_ENV = "OURS_MCP_CONFIG";
11
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
12
+ var emptyConfig = () => ({ version: 1, daemons: {}, instances: {} });
13
+ function applicationIdentityConfigPath(env = process.env) {
14
+ const explicit = (env[APPLICATION_IDENTITIES_ENV] ?? "").trim();
15
+ return resolve(explicit || resolve(homedir(), ".ours-mcp", "config.json"));
16
+ }
17
+ function parseRows(raw, path, namespace, normalizeKey) {
18
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
19
+ throw new Error(`Invalid ours-mcp application identity config at ${path}: "${namespace}s" must be an object.`);
20
+ }
21
+ const rows = {};
22
+ for (const [key, value] of Object.entries(raw)) {
23
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
24
+ throw new Error(`Invalid ours-mcp application identity config at ${path}: ${namespace} ${JSON.stringify(key)} must be an object.`);
25
+ }
26
+ const row = value;
27
+ if (!Array.isArray(row.identities) || row.identities.some((name) => typeof name !== "string" || name.length === 0)) {
28
+ throw new Error(`Invalid ours-mcp application identity config at ${path}: ${namespace} ${JSON.stringify(key)} has an invalid identity list.`);
29
+ }
30
+ rows[normalizeKey(key)] = { identities: [...new Set(row.identities)].sort() };
31
+ }
32
+ return rows;
33
+ }
34
+ function parseConfig(text, path) {
35
+ let value;
36
+ try {
37
+ value = JSON.parse(text);
38
+ } catch (error) {
39
+ throw new Error(`Invalid ours-mcp application identity config at ${path}: ${String(error)}`);
40
+ }
41
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`Invalid ours-mcp application identity config at ${path}: expected an object.`);
42
+ const record = value;
43
+ if (record.version !== APPLICATION_IDENTITIES_VERSION) {
44
+ throw new Error(`Unsupported ours-mcp application identity config version at ${path}: expected ${APPLICATION_IDENTITIES_VERSION}, found ${String(record.version)}.`);
45
+ }
46
+ const daemons = parseRows(record.daemons, path, "daemon", resolve);
47
+ const instances = record.instances === void 0 ? {} : parseRows(record.instances, path, "instance", (instanceId) => {
48
+ if (!UUID.test(instanceId)) throw new Error(`Invalid ours-mcp application identity config at ${path}: instance ${JSON.stringify(instanceId)} must be a lowercase UUID.`);
49
+ return instanceId;
50
+ });
51
+ return { version: 1, daemons, instances };
52
+ }
53
+ async function readConfig(path) {
54
+ try {
55
+ return parseConfig(await readFile(path, "utf8"), path);
56
+ } catch (error) {
57
+ if (error.code === "ENOENT") return emptyConfig();
58
+ throw error;
59
+ }
60
+ }
61
+ async function writeConfigAtomic(path, config) {
62
+ const parent = dirname(path);
63
+ await mkdir(parent, { recursive: true, mode: 448 });
64
+ const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
65
+ try {
66
+ await writeFile(temporary, `${JSON.stringify(config, null, 2)}
67
+ `, { mode: 384, flag: "wx" });
68
+ await rename(temporary, path);
69
+ await chmod(path, 384);
70
+ } catch (error) {
71
+ try {
72
+ await unlink(temporary);
73
+ } catch {
74
+ }
75
+ throw error;
76
+ }
77
+ }
78
+ var ApplicationIdentityStore = class {
79
+ stateDir;
80
+ instanceId;
81
+ path;
82
+ constructor(target, options = {}) {
83
+ if (typeof target === "string") {
84
+ this.stateDir = resolve(target);
85
+ this.instanceId = null;
86
+ } else {
87
+ if (!target || !UUID.test(target.instanceId)) throw new Error("Application identity instanceId must be a lowercase UUID.");
88
+ this.stateDir = null;
89
+ this.instanceId = target.instanceId;
90
+ }
91
+ this.path = resolve(options.path ?? applicationIdentityConfigPath(options.env));
92
+ }
93
+ rows(config) {
94
+ return this.instanceId === null ? config.daemons : config.instances;
95
+ }
96
+ key() {
97
+ return this.instanceId ?? this.stateDir;
98
+ }
99
+ async list() {
100
+ const config = await readConfig(this.path);
101
+ return [...this.rows(config)[this.key()]?.identities ?? []];
102
+ }
103
+ async has(name) {
104
+ return (await this.list()).includes(name);
105
+ }
106
+ async add(name) {
107
+ if (!name) throw new Error("Cannot add an empty identity name to ours-mcp.");
108
+ const config = await readConfig(this.path);
109
+ const rows = this.rows(config);
110
+ const key = this.key();
111
+ const identities = rows[key]?.identities ?? [];
112
+ if (identities.includes(name)) return;
113
+ rows[key] = { identities: [...identities, name].sort() };
114
+ await writeConfigAtomic(this.path, config);
115
+ }
116
+ async remove(name) {
117
+ const config = await readConfig(this.path);
118
+ const rows = this.rows(config);
119
+ const key = this.key();
120
+ const current = rows[key];
121
+ if (!current?.identities.includes(name)) return;
122
+ const identities = current.identities.filter((candidate) => candidate !== name);
123
+ if (identities.length === 0) delete rows[key];
124
+ else rows[key] = { identities };
125
+ await writeConfigAtomic(this.path, config);
126
+ }
127
+ };
128
+
129
+ // ../core/src/host-profile.ts
130
+ import { readGatewayClientProfile, validateGatewayClientProfile } from "@ours.network/sdk/client";
131
+ var validateHostProfile = validateGatewayClientProfile;
132
+
133
+ // ../core/src/native-session.ts
134
+ import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
135
+ import { chmod as chmod2, mkdir as mkdir2, readFile as readFile2, rename as rename2, unlink as unlink2, writeFile as writeFile2 } from "node:fs/promises";
136
+ import { dirname as dirname2, join as join2, resolve as resolve2 } from "node:path";
137
+ import { attachOursClient } from "@ours.network/sdk";
138
+
139
+ // ../core/src/session-guard.ts
140
+ import { createHash } from "node:crypto";
141
+ import { openSync, closeSync, lstatSync, mkdirSync, chmodSync, constants } from "node:fs";
142
+ import { join } from "node:path";
143
+ import { createRequire } from "node:module";
144
+ var BUSY_TIMEOUT_MS = 5e3;
145
+ var DatabaseSync = null;
146
+ var resolveAttempted = false;
147
+ function resolveDatabase() {
148
+ if (DatabaseSync) return DatabaseSync;
149
+ if (resolveAttempted) throw new Error("No SQLite implementation available for native session guard");
150
+ resolveAttempted = true;
151
+ try {
152
+ const mod = createRequire(import.meta.url)("node:sqlite");
153
+ DatabaseSync = mod.DatabaseSync;
154
+ return DatabaseSync;
155
+ } catch {
156
+ try {
157
+ const mod = createRequire(import.meta.url)("better-sqlite3");
158
+ DatabaseSync = mod.default ?? mod;
159
+ return DatabaseSync;
160
+ } catch {
161
+ throw new Error("Native session guard requires node:sqlite (Node >=22.13) or better-sqlite3");
162
+ }
163
+ }
164
+ }
165
+ function ensureGuardFile(guardPath) {
166
+ try {
167
+ const fd = openSync(guardPath, constants.O_RDWR | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 384);
168
+ closeSync(fd);
169
+ } catch (error) {
170
+ if (error.code !== "EEXIST") throw error;
171
+ }
172
+ const stat = lstatSync(guardPath);
173
+ if (!stat.isFile() || stat.uid !== process.getuid() || (stat.mode & 63) !== 0 || stat.nlink !== 1) {
174
+ throw new Error("Native session guard file must be an owner-only regular file");
175
+ }
176
+ }
177
+ function acquireSessionGuard(recordPath) {
178
+ const uid = process.getuid();
179
+ const base = join(`/tmp/ours-${uid}`, "session-guards");
180
+ mkdirSync(base, { recursive: true, mode: 448 });
181
+ try {
182
+ chmodSync(base, 448);
183
+ } catch {
184
+ }
185
+ const hash = createHash("sha256").update(recordPath).digest("hex");
186
+ const guardPath = join(base, `${hash}.guard`);
187
+ ensureGuardFile(guardPath);
188
+ const before = lstatSync(guardPath);
189
+ const Database = resolveDatabase();
190
+ const db = new Database(guardPath);
191
+ try {
192
+ const after = lstatSync(guardPath);
193
+ if (before.dev !== after.dev || before.ino !== after.ino || before.mode !== after.mode || before.uid !== after.uid) {
194
+ throw new Error("Native session guard file changed during acquisition");
195
+ }
196
+ db.exec(`PRAGMA busy_timeout=${BUSY_TIMEOUT_MS}; BEGIN EXCLUSIVE`);
197
+ return () => {
198
+ try {
199
+ db.close();
200
+ } catch {
201
+ }
202
+ };
203
+ } catch (error) {
204
+ try {
205
+ db.close();
206
+ } catch {
207
+ }
208
+ throw error;
209
+ }
210
+ }
211
+
212
+ // ../core/src/native-session.ts
213
+ var RECORD_VERSION = 1;
214
+ var UUID2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
215
+ var clients = /* @__PURE__ */ new Map();
216
+ var operations = /* @__PURE__ */ new Map();
217
+ function selector(value) {
218
+ if (typeof value !== "string" || value.length === 0 || value.length > 4096 || value.trim() !== value || value.includes("\0")) {
219
+ throw new Error("Native session metadata is missing or invalid for this host-profile tool call.");
220
+ }
221
+ return value;
222
+ }
223
+ function nativeSessionRecordPath(hostRecordRoot, instanceId, nativeSessionId) {
224
+ if (!hostRecordRoot || !hostRecordRoot.startsWith("/") || resolve2(hostRecordRoot) !== hostRecordRoot) {
225
+ throw new Error("Native session host record root must be a normalized absolute path.");
226
+ }
227
+ if (!UUID2.test(instanceId)) throw new Error("Native session daemon instance must be a lowercase UUID.");
228
+ const hash = createHash2("sha256").update(selector(nativeSessionId), "utf8").digest("hex");
229
+ return join2(hostRecordRoot, "sessions", instanceId, `${hash}.json`);
230
+ }
231
+ function parseRecord(text, path) {
232
+ let value;
233
+ try {
234
+ value = JSON.parse(text);
235
+ } catch (error) {
236
+ throw new Error(`Invalid native session record at ${path}: ${String(error)}`);
237
+ }
238
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
239
+ throw new Error(`Invalid native session record at ${path}: expected an object.`);
240
+ }
241
+ const record = value;
242
+ const keys = Object.keys(record).sort();
243
+ if (keys.join(",") !== "ownerInstanceId,state,version" || record.version !== RECORD_VERSION || typeof record.ownerInstanceId !== "string" || !UUID2.test(record.ownerInstanceId) || !["active", "pending", "ended"].includes(String(record.state))) {
244
+ throw new Error(`Invalid native session record at ${path}.`);
245
+ }
246
+ return record;
247
+ }
248
+ async function readRecord(path) {
249
+ try {
250
+ return parseRecord(await readFile2(path, "utf8"), path);
251
+ } catch (error) {
252
+ if (error.code === "ENOENT") return null;
253
+ throw error;
254
+ }
255
+ }
256
+ async function writeRecord(path, record) {
257
+ const parent = dirname2(path);
258
+ await mkdir2(parent, { recursive: true, mode: 448 });
259
+ await chmod2(parent, 448);
260
+ const temporary = `${path}.${process.pid}.${randomUUID2()}.tmp`;
261
+ try {
262
+ await writeFile2(temporary, `${JSON.stringify(record, null, 2)}
263
+ `, { mode: 384, flag: "wx" });
264
+ await rename2(temporary, path);
265
+ await chmod2(path, 384);
266
+ } catch (error) {
267
+ try {
268
+ await unlink2(temporary);
269
+ } catch {
270
+ }
271
+ throw error;
272
+ }
273
+ }
274
+ async function serialized(key, operation) {
275
+ const predecessor = operations.get(key) ?? Promise.resolve();
276
+ let release;
277
+ const gate = new Promise((resolve3) => {
278
+ release = resolve3;
279
+ });
280
+ const tail = predecessor.catch(() => {
281
+ }).then(() => gate);
282
+ operations.set(key, tail);
283
+ await predecessor.catch(() => {
284
+ });
285
+ let releaseGuard;
286
+ try {
287
+ releaseGuard = acquireSessionGuard(key);
288
+ return await operation();
289
+ } finally {
290
+ try {
291
+ releaseGuard?.();
292
+ } finally {
293
+ release();
294
+ if (operations.get(key) === tail) operations.delete(key);
295
+ }
296
+ }
297
+ }
298
+ function attach(profile, ownerInstanceId) {
299
+ return attachOursClient({
300
+ endpoint: profile.endpoint,
301
+ expectedInstanceId: profile.expectedInstanceId,
302
+ credentialPath: profile.credentialPath,
303
+ sessionMode: "external",
304
+ leaseToken: ownerInstanceId,
305
+ env: {}
306
+ });
307
+ }
308
+ function cachedClient(path, profile, ownerInstanceId) {
309
+ const cached = clients.get(path);
310
+ if (cached?.ownerInstanceId === ownerInstanceId) return cached.promise;
311
+ if (cached) {
312
+ clients.delete(path);
313
+ void cached.promise.then((client) => client.close()).catch(() => {
314
+ });
315
+ }
316
+ const promise = attach(profile, ownerInstanceId);
317
+ const next = { ownerInstanceId, promise };
318
+ clients.set(path, next);
319
+ void promise.catch(() => {
320
+ if (clients.get(path) === next) clients.delete(path);
321
+ });
322
+ return promise;
323
+ }
324
+ async function releaseOwner(path, profile, ownerInstanceId) {
325
+ const cached = clients.get(path);
326
+ const client = cached?.ownerInstanceId === ownerInstanceId ? await cached.promise : await attach(profile, ownerInstanceId);
327
+ try {
328
+ await client.releaseLease();
329
+ } finally {
330
+ if (cached?.ownerInstanceId !== ownerInstanceId) await client.close();
331
+ }
332
+ }
333
+ async function finishPending(path, profile, ownerInstanceId) {
334
+ await releaseOwner(path, profile, ownerInstanceId);
335
+ const current = await readRecord(path);
336
+ if (current?.state === "pending" && current.ownerInstanceId === ownerInstanceId) {
337
+ await writeRecord(path, { version: RECORD_VERSION, ownerInstanceId, state: "ended" });
338
+ }
339
+ const cached = clients.get(path);
340
+ if (cached?.ownerInstanceId === ownerInstanceId) {
341
+ clients.delete(path);
342
+ void cached.promise.then((client) => client.close()).catch(() => {
343
+ });
344
+ }
345
+ }
346
+ async function nativeClientForAtRoot(profileValue, nativeSessionId, hostRecordRoot) {
347
+ const profile = validateHostProfile(profileValue);
348
+ const path = nativeSessionRecordPath(hostRecordRoot, profile.expectedInstanceId, nativeSessionId);
349
+ return serialized(path, async () => {
350
+ let record = await readRecord(path);
351
+ if (record?.state === "pending") {
352
+ await finishPending(path, profile, record.ownerInstanceId);
353
+ record = await readRecord(path);
354
+ }
355
+ if (record?.state === "active") return cachedClient(path, profile, record.ownerInstanceId);
356
+ const ownerInstanceId = randomUUID2();
357
+ await writeRecord(path, { version: RECORD_VERSION, ownerInstanceId, state: "active" });
358
+ return cachedClient(path, profile, ownerInstanceId);
359
+ });
360
+ }
361
+
362
+ // src/host-hooks.mjs
363
+ async function readHostHookState({ profile, nativeSessionId, applicationPath, clientFor = nativeClientForAtRoot }) {
364
+ const store = new ApplicationIdentityStore({ instanceId: profile.expectedInstanceId }, { path: applicationPath });
365
+ const identities = await store.list();
366
+ const visible = new Set(identities);
367
+ const client = await clientFor(profile, nativeSessionId, dirname3(applicationPath));
368
+ try {
369
+ const [unread, rows] = await Promise.all([client.unread(), client.listIdentities()]);
370
+ const entries = (Array.isArray(unread?.identities) ? unread.identities : []).flatMap((entry) => {
371
+ if (!entry || !visible.has(entry.name)) return [];
372
+ const count = Number.isSafeInteger(entry.count) ? entry.count : 0;
373
+ const files = Number.isSafeInteger(entry.files) ? entry.files : 0;
374
+ if (count <= 0 && files <= 0) return [];
375
+ const recent = (Array.isArray(entry.recent) ? entry.recent.slice(-5) : []).flatMap((message) => message && typeof message.from === "string" ? [{ from: message.from, msg_id: String(message.msg_id ?? "?"), date: typeof message.date === "string" ? message.date : "" }] : []);
376
+ return [{ name: entry.name, count, files, recent }];
377
+ });
378
+ const bindings = (Array.isArray(rows) ? rows : []).flatMap((row) => row && visible.has(row.name) && ["mine", "other-live"].includes(row.session) ? [row.name] : []);
379
+ return { identities, unread: { identities: entries }, bindings };
380
+ } finally {
381
+ await client.close();
382
+ }
383
+ }
384
+ export {
385
+ readHostHookState
386
+ };
@@ -3857,7 +3857,7 @@ var require_fast_uri = __commonJS({
3857
3857
  const resolvedHost = resolved.host;
3858
3858
  const resolvedHostIsIP = resolvedHost !== void 0 && resolvedHost !== "" && (isIPv4(resolvedHost) || normalizeIPv6(resolvedHost).isIPV6);
3859
3859
  canonicalizeHost(resolved, options || {}, resolvedSchemeHandler, resolvedHostIsIP);
3860
- const encodedASCIIHost = resolvedHost && resolvedHost.indexOf("%") !== -1 && !/\P{ASCII}/u.test(resolvedHost);
3860
+ const encodedASCIIHost = resolvedHost && resolvedHost.indexOf("%") !== -1 && !new RegExp("\\P{ASCII}", "u").test(resolvedHost);
3861
3861
  if (resolved.error && !encodedASCIIHost) {
3862
3862
  throw new Error(resolved.error);
3863
3863
  }
@@ -21398,6 +21398,20 @@ var StdioServerTransport = class {
21398
21398
  // src/monitor-mcp.mjs
21399
21399
  import { spawn } from "node:child_process";
21400
21400
  import { createRequire } from "node:module";
21401
+ import { fileURLToPath } from "node:url";
21402
+
21403
+ // ../core/src/host-profile.ts
21404
+ import { readGatewayClientProfile, validateGatewayClientProfile } from "@ours.network/sdk/client";
21405
+ function hostProfileSelectionFromEnv(env = process.env) {
21406
+ const { configPath, ...profile } = readGatewayClientProfile(env);
21407
+ return { profile, configPath };
21408
+ }
21409
+ function hostProfileFromEnv(env = process.env) {
21410
+ return hostProfileSelectionFromEnv(env).profile;
21411
+ }
21412
+
21413
+ // ../core/src/native-session.ts
21414
+ import { attachOursClient } from "@ours.network/sdk";
21401
21415
 
21402
21416
  // src/control-server.mjs
21403
21417
  import { createServer, createConnection } from "node:net";
@@ -21442,10 +21456,20 @@ function sendControlCommand(socketPath, capability, message, timeoutMs = 2e3) {
21442
21456
 
21443
21457
  // src/monitor-mcp.mjs
21444
21458
  var require2 = createRequire(import.meta.url);
21459
+ var networkWatchPath = fileURLToPath(new URL("../bin/network-watch.mjs", import.meta.url));
21445
21460
  var monitorToolNames = ["arm_monitor", "foreground_monitor", "disarm_monitor", "monitor_status"];
21446
21461
  var content = (text, isError = false) => ({ content: [{ type: "text", text }], ...isError ? { isError: true } : {} });
21447
21462
  var foregroundOffer = (identity) => `IMPORTANT: Before offering the fallback, tell the user exactly: "This standard \`codex\` session only supports a blocking foreground monitor. For background monitoring, restart the session with \`ours-codex\` instead; it adds the App Server integration needed for session-scoped background wake." A blocking foreground monitor is available here, but while it waits this Codex session cannot accept another prompt (press Escape to stop it). Ask the user: "Do you want to arm the foreground blocking monitor here?" Only after an explicit yes, call get_messages once to drain existing unread mail, then call foreground_monitor({ identity: ${JSON.stringify(identity)} }).`;
21448
- function foregroundWatchProcess(identity) {
21463
+ function foregroundWatchProcess(identity, nativeSessionId, env = process.env) {
21464
+ if (hostProfileFromEnv(env)) {
21465
+ if (typeof nativeSessionId !== "string" || !nativeSessionId.trim()) {
21466
+ throw new Error("Native session metadata is required for host-profile monitoring.");
21467
+ }
21468
+ return {
21469
+ command: process.execPath,
21470
+ args: [networkWatchPath, identity, nativeSessionId]
21471
+ };
21472
+ }
21449
21473
  try {
21450
21474
  const cliPath = require2.resolve("@ours.network/mcp/dist/cli.js");
21451
21475
  return { command: process.execPath, args: [cliPath, "watch", identity] };
@@ -21468,7 +21492,7 @@ async function handleMonitorCommand(command, args = {}, { env = process.env, sen
21468
21492
  if (command === "disarm") return { text: "Live ours monitor disarmed." };
21469
21493
  return { text: state.armedIdentity ? `Live ours monitor is armed for "${state.armedIdentity}"${state.turnActive ? "; a turn is active" : ""}.` : `Live ours monitor is available but not armed${state.boundIdentity ? `; current binding is "${state.boundIdentity}"` : "; bind an identity first"}.` };
21470
21494
  }
21471
- function waitForForegroundMail(identity, {
21495
+ function waitForForegroundMail(identity, nativeSessionId, {
21472
21496
  env = process.env,
21473
21497
  spawnImpl = spawn,
21474
21498
  signal,
@@ -21476,15 +21500,16 @@ function waitForForegroundMail(identity, {
21476
21500
  } = {}) {
21477
21501
  if (typeof identity !== "string" || !identity.trim()) return Promise.reject(new Error("identity is required"));
21478
21502
  return new Promise((resolve, reject) => {
21479
- const invocation = commandFor(identity);
21503
+ const invocation = commandFor(identity, nativeSessionId, env);
21480
21504
  const child = spawnImpl(invocation.command, invocation.args, {
21481
- env,
21482
- stdio: ["ignore", "pipe", "pipe"]
21505
+ env: invocation.env ?? env,
21506
+ stdio: [invocation.stdin ?? "ignore", "pipe", "pipe"]
21483
21507
  });
21484
21508
  let stdout = "";
21485
21509
  let stderr = "";
21486
21510
  let settled = false;
21487
21511
  const stop = () => {
21512
+ if (invocation.stdin === "pipe") child.stdin?.end();
21488
21513
  if (!child.killed) child.kill("SIGTERM");
21489
21514
  };
21490
21515
  const finish = (fn, value) => {
@@ -21530,7 +21555,7 @@ function createMonitorMcpServer(deps = {}) {
21530
21555
  identity: external_exports.string().min(1).describe("The already-bound ours identity to monitor.")
21531
21556
  }, async ({ identity }, extra) => {
21532
21557
  try {
21533
- const event = await waitForForegroundMail(identity, { ...deps, signal: extra?.signal });
21558
+ const event = await waitForForegroundMail(identity, extra?._meta?.threadId, { ...deps, signal: extra?.signal });
21534
21559
  return content(`Foreground ours monitor received a body-free arrival event: ${event}
21535
21560
  Call get_messages now. After handling the mail, call foreground_monitor again without asking if monitoring should remain armed.`);
21536
21561
  } catch (error2) {