@ours.network/codex 1.2.0-nightly.1 → 1.2.0-nightly.11
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/.codex-plugin/plugin.json +1 -1
- package/README.md +11 -28
- package/bin/codex-config-install.mjs +3 -3
- package/bin/ours-codex-install.mjs +4 -31
- package/bin/proxy.mjs +1 -17
- package/dist/hooks-runner.mjs +422 -16509
- package/dist/host-hooks.mjs +386 -0
- package/dist/monitor-mcp.mjs +20 -125
- package/dist/network-watch.mjs +17 -121
- package/dist/profile.mjs +14 -156
- package/install.sh +14 -33
- package/package.json +4 -3
- package/skills/ours/SKILL.md +6 -6
- package/skills/ours/references/configuration.md +20 -22
- package/src/hooks/runner.mjs +8 -70
- package/src/host-hooks.mjs +25 -0
- package/src/launcher.mjs +1 -9
- package/src/profile.mjs +1 -40
- package/bin/container-launch.mjs +0 -40
- package/dist/network-proxy.mjs +0 -16782
- package/src/network-proxy.mjs +0 -308
|
@@ -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
|
+
};
|
package/dist/monitor-mcp.mjs
CHANGED
|
@@ -2979,7 +2979,7 @@ var require_compile = __commonJS({
|
|
|
2979
2979
|
const schOrFunc = root.refs[ref];
|
|
2980
2980
|
if (schOrFunc)
|
|
2981
2981
|
return schOrFunc;
|
|
2982
|
-
let _sch =
|
|
2982
|
+
let _sch = resolve.call(this, root, ref);
|
|
2983
2983
|
if (_sch === void 0) {
|
|
2984
2984
|
const schema = (_a = root.localRefs) === null || _a === void 0 ? void 0 : _a[ref];
|
|
2985
2985
|
const { schemaId } = this.opts;
|
|
@@ -3006,7 +3006,7 @@ var require_compile = __commonJS({
|
|
|
3006
3006
|
function sameSchemaEnv(s1, s2) {
|
|
3007
3007
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
3008
3008
|
}
|
|
3009
|
-
function
|
|
3009
|
+
function resolve(root, ref) {
|
|
3010
3010
|
let sch;
|
|
3011
3011
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
3012
3012
|
ref = sch;
|
|
@@ -3831,7 +3831,7 @@ var require_fast_uri = __commonJS({
|
|
|
3831
3831
|
}
|
|
3832
3832
|
return uri;
|
|
3833
3833
|
}
|
|
3834
|
-
function
|
|
3834
|
+
function resolve(baseURI, relativeURI, options) {
|
|
3835
3835
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
3836
3836
|
const {
|
|
3837
3837
|
parsed: baseParsed,
|
|
@@ -4193,7 +4193,7 @@ var require_fast_uri = __commonJS({
|
|
|
4193
4193
|
var fastUri = {
|
|
4194
4194
|
SCHEMES,
|
|
4195
4195
|
normalize,
|
|
4196
|
-
resolve
|
|
4196
|
+
resolve,
|
|
4197
4197
|
resolveComponent,
|
|
4198
4198
|
equal,
|
|
4199
4199
|
serialize,
|
|
@@ -19274,7 +19274,7 @@ var Protocol = class {
|
|
|
19274
19274
|
return;
|
|
19275
19275
|
}
|
|
19276
19276
|
const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
|
|
19277
|
-
await new Promise((
|
|
19277
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
19278
19278
|
options?.signal?.throwIfAborted();
|
|
19279
19279
|
}
|
|
19280
19280
|
} catch (error2) {
|
|
@@ -19291,7 +19291,7 @@ var Protocol = class {
|
|
|
19291
19291
|
*/
|
|
19292
19292
|
request(request, resultSchema, options) {
|
|
19293
19293
|
const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
|
|
19294
|
-
return new Promise((
|
|
19294
|
+
return new Promise((resolve, reject) => {
|
|
19295
19295
|
const earlyReject = (error2) => {
|
|
19296
19296
|
reject(error2);
|
|
19297
19297
|
};
|
|
@@ -19369,7 +19369,7 @@ var Protocol = class {
|
|
|
19369
19369
|
if (!parseResult.success) {
|
|
19370
19370
|
reject(parseResult.error);
|
|
19371
19371
|
} else {
|
|
19372
|
-
|
|
19372
|
+
resolve(parseResult.data);
|
|
19373
19373
|
}
|
|
19374
19374
|
} catch (error2) {
|
|
19375
19375
|
reject(error2);
|
|
@@ -19630,12 +19630,12 @@ var Protocol = class {
|
|
|
19630
19630
|
}
|
|
19631
19631
|
} catch {
|
|
19632
19632
|
}
|
|
19633
|
-
return new Promise((
|
|
19633
|
+
return new Promise((resolve, reject) => {
|
|
19634
19634
|
if (signal.aborted) {
|
|
19635
19635
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
19636
19636
|
return;
|
|
19637
19637
|
}
|
|
19638
|
-
const timeoutId = setTimeout(
|
|
19638
|
+
const timeoutId = setTimeout(resolve, interval);
|
|
19639
19639
|
signal.addEventListener("abort", () => {
|
|
19640
19640
|
clearTimeout(timeoutId);
|
|
19641
19641
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
@@ -20735,7 +20735,7 @@ var McpServer = class {
|
|
|
20735
20735
|
let task = createTaskResult.task;
|
|
20736
20736
|
const pollInterval = task.pollInterval ?? 5e3;
|
|
20737
20737
|
while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
|
|
20738
|
-
await new Promise((
|
|
20738
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
20739
20739
|
const updatedTask = await extra.taskStore.getTask(taskId);
|
|
20740
20740
|
if (!updatedTask) {
|
|
20741
20741
|
throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
|
|
@@ -21384,12 +21384,12 @@ var StdioServerTransport = class {
|
|
|
21384
21384
|
this.onclose?.();
|
|
21385
21385
|
}
|
|
21386
21386
|
send(message) {
|
|
21387
|
-
return new Promise((
|
|
21387
|
+
return new Promise((resolve) => {
|
|
21388
21388
|
const json = serializeMessage(message);
|
|
21389
21389
|
if (this._stdout.write(json)) {
|
|
21390
|
-
|
|
21390
|
+
resolve();
|
|
21391
21391
|
} else {
|
|
21392
|
-
this._stdout.once("drain",
|
|
21392
|
+
this._stdout.once("drain", resolve);
|
|
21393
21393
|
}
|
|
21394
21394
|
});
|
|
21395
21395
|
}
|
|
@@ -21401,120 +21401,15 @@ import { createRequire } from "node:module";
|
|
|
21401
21401
|
import { fileURLToPath } from "node:url";
|
|
21402
21402
|
|
|
21403
21403
|
// ../core/src/host-profile.ts
|
|
21404
|
-
import {
|
|
21405
|
-
import { homedir } from "node:os";
|
|
21406
|
-
import { resolve } from "node:path";
|
|
21407
|
-
var PROFILE_KEYS = ["endpoint", "expectedInstanceId", "credentialPath"];
|
|
21408
|
-
var LEGACY_SELECTION_KEYS = ["port", "stateDir", "apiToken", "apiVisibility"];
|
|
21409
|
-
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
21410
|
-
var CONFLICTING_ENV = ["OURS_API_TOKEN", "OURS_PORT", "OURS_STATE_DIR", "OURS_DAEMON_ID"];
|
|
21411
|
-
function profileError(reason) {
|
|
21412
|
-
return new Error(`Invalid external host profile: ${reason}`);
|
|
21413
|
-
}
|
|
21414
|
-
function validateHostProfile(value) {
|
|
21415
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
21416
|
-
throw profileError("expected a complete host profile object.");
|
|
21417
|
-
}
|
|
21418
|
-
const record2 = value;
|
|
21419
|
-
const present = PROFILE_KEYS.filter((key) => Object.hasOwn(record2, key));
|
|
21420
|
-
if (present.length !== PROFILE_KEYS.length) {
|
|
21421
|
-
throw profileError("expected a complete host profile tuple: endpoint, expectedInstanceId, credentialPath.");
|
|
21422
|
-
}
|
|
21423
|
-
const mixed = LEGACY_SELECTION_KEYS.filter((key) => Object.hasOwn(record2, key));
|
|
21424
|
-
if (mixed.length) throw profileError(`legacy selection keys cannot be mixed with a host profile (${mixed.join(", ")}).`);
|
|
21425
|
-
const endpoint = record2.endpoint;
|
|
21426
|
-
const expectedInstanceId = record2.expectedInstanceId;
|
|
21427
|
-
const credentialPath = record2.credentialPath;
|
|
21428
|
-
if (typeof endpoint !== "string" || endpoint.trim() !== endpoint || endpoint === "") {
|
|
21429
|
-
throw profileError("endpoint must be a non-empty HTTP origin.");
|
|
21430
|
-
}
|
|
21431
|
-
if (typeof expectedInstanceId !== "string" || !UUID.test(expectedInstanceId)) {
|
|
21432
|
-
throw profileError("expectedInstanceId must be a lowercase UUID.");
|
|
21433
|
-
}
|
|
21434
|
-
if (typeof credentialPath !== "string" || credentialPath === "" || !credentialPath.startsWith("/") || resolve(credentialPath) !== credentialPath) {
|
|
21435
|
-
throw profileError("credentialPath must be a normalized absolute path.");
|
|
21436
|
-
}
|
|
21437
|
-
let url;
|
|
21438
|
-
try {
|
|
21439
|
-
url = new URL(endpoint);
|
|
21440
|
-
} catch {
|
|
21441
|
-
throw profileError("endpoint must be an HTTP origin.");
|
|
21442
|
-
}
|
|
21443
|
-
if (url.protocol !== "http:" || url.username || url.password || url.pathname !== "/" || url.search || url.hash) {
|
|
21444
|
-
throw profileError("endpoint must be an HTTP origin without credentials, path, query, or fragment.");
|
|
21445
|
-
}
|
|
21446
|
-
return { endpoint: url.origin, expectedInstanceId, credentialPath };
|
|
21447
|
-
}
|
|
21448
|
-
function readProfileObject(configPath) {
|
|
21449
|
-
let text;
|
|
21450
|
-
try {
|
|
21451
|
-
text = readFileSync(configPath, "utf8");
|
|
21452
|
-
} catch {
|
|
21453
|
-
throw new Error(`Cannot read host profile ${JSON.stringify(configPath)}.`);
|
|
21454
|
-
}
|
|
21455
|
-
let value;
|
|
21456
|
-
try {
|
|
21457
|
-
value = JSON.parse(text);
|
|
21458
|
-
} catch {
|
|
21459
|
-
throw profileError(`config ${JSON.stringify(configPath)} is not valid JSON.`);
|
|
21460
|
-
}
|
|
21461
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) throw profileError(`config ${JSON.stringify(configPath)} must be an object.`);
|
|
21462
|
-
return value;
|
|
21463
|
-
}
|
|
21464
|
-
function assertPrivateRegularFile(configPath) {
|
|
21465
|
-
let stat;
|
|
21466
|
-
try {
|
|
21467
|
-
stat = lstatSync(configPath);
|
|
21468
|
-
} catch {
|
|
21469
|
-
throw new Error(`Cannot read host profile ${JSON.stringify(configPath)}.`);
|
|
21470
|
-
}
|
|
21471
|
-
if (!stat.isFile()) throw profileError(`config ${JSON.stringify(configPath)} must be a regular file.`);
|
|
21472
|
-
const currentUid = process.getuid?.();
|
|
21473
|
-
if (currentUid === void 0 || stat.uid !== currentUid) throw profileError(`config ${JSON.stringify(configPath)} must be owned by the current user.`);
|
|
21474
|
-
if ((stat.mode & 63) !== 0) throw profileError(`config ${JSON.stringify(configPath)} must have private permissions.`);
|
|
21475
|
-
}
|
|
21476
|
-
function readHostProfile(configPath) {
|
|
21477
|
-
const record2 = readProfileObject(configPath);
|
|
21478
|
-
const found = PROFILE_KEYS.filter((key) => Object.hasOwn(record2, key));
|
|
21479
|
-
if (found.length === 0) return null;
|
|
21480
|
-
assertPrivateRegularFile(configPath);
|
|
21481
|
-
return validateHostProfile(record2);
|
|
21482
|
-
}
|
|
21483
|
-
function nonempty(env, key) {
|
|
21484
|
-
return (env[key] ?? "").trim() !== "";
|
|
21485
|
-
}
|
|
21404
|
+
import { readGatewayClientProfile, validateGatewayClientProfile } from "@ours.network/sdk/client";
|
|
21486
21405
|
function hostProfileSelectionFromEnv(env = process.env) {
|
|
21487
|
-
const
|
|
21488
|
-
const managedPath = resolve(env.HOME || homedir(), ".ours-client", "profile.json");
|
|
21489
|
-
let configPath = explicit;
|
|
21490
|
-
if (!configPath) {
|
|
21491
|
-
try {
|
|
21492
|
-
lstatSync(managedPath);
|
|
21493
|
-
configPath = managedPath;
|
|
21494
|
-
} catch (error2) {
|
|
21495
|
-
if (error2.code !== "ENOENT") {
|
|
21496
|
-
throw new Error(`Cannot read host profile ${JSON.stringify(managedPath)}.`);
|
|
21497
|
-
}
|
|
21498
|
-
configPath = resolve(homedir(), ".ours", "config.json");
|
|
21499
|
-
if (!existsSync(configPath)) return null;
|
|
21500
|
-
}
|
|
21501
|
-
}
|
|
21502
|
-
const profile = readHostProfile(configPath);
|
|
21503
|
-
if (profile === null) {
|
|
21504
|
-
if (resolve(configPath) === managedPath) throw profileError("managed client config must contain a complete host profile tuple.");
|
|
21505
|
-
return null;
|
|
21506
|
-
}
|
|
21507
|
-
const conflicting = CONFLICTING_ENV.filter((key) => nonempty(env, key));
|
|
21508
|
-
if (conflicting.length) throw profileError(`${conflicting.join(", ")} conflicts with host-profile mode.`);
|
|
21406
|
+
const { configPath, ...profile } = readGatewayClientProfile(env);
|
|
21509
21407
|
return { profile, configPath };
|
|
21510
21408
|
}
|
|
21511
21409
|
function hostProfileFromEnv(env = process.env) {
|
|
21512
|
-
return hostProfileSelectionFromEnv(env)
|
|
21410
|
+
return hostProfileSelectionFromEnv(env).profile;
|
|
21513
21411
|
}
|
|
21514
21412
|
|
|
21515
|
-
// ../core/src/host-client/files.ts
|
|
21516
|
-
import { buildIdentityFile, writeIdentityFile } from "@ours.network/sdk/connector";
|
|
21517
|
-
|
|
21518
21413
|
// ../core/src/native-session.ts
|
|
21519
21414
|
import { attachOursClient } from "@ours.network/sdk";
|
|
21520
21415
|
|
|
@@ -21530,7 +21425,7 @@ function encodeControlResponse(value) {
|
|
|
21530
21425
|
|
|
21531
21426
|
// src/control-server.mjs
|
|
21532
21427
|
function sendControlCommand(socketPath, capability, message, timeoutMs = 2e3) {
|
|
21533
|
-
return new Promise((
|
|
21428
|
+
return new Promise((resolve, reject) => {
|
|
21534
21429
|
const socket = createConnection(socketPath);
|
|
21535
21430
|
let buffer = "";
|
|
21536
21431
|
const timer = setTimeout(() => {
|
|
@@ -21551,7 +21446,7 @@ function sendControlCommand(socketPath, capability, message, timeoutMs = 2e3) {
|
|
|
21551
21446
|
try {
|
|
21552
21447
|
const response = JSON.parse(buffer);
|
|
21553
21448
|
if (!response.ok) reject(new Error(response.error || "monitor control failed"));
|
|
21554
|
-
else
|
|
21449
|
+
else resolve(response);
|
|
21555
21450
|
} catch (error2) {
|
|
21556
21451
|
reject(error2);
|
|
21557
21452
|
}
|
|
@@ -21604,7 +21499,7 @@ function waitForForegroundMail(identity, nativeSessionId, {
|
|
|
21604
21499
|
commandFor = foregroundWatchProcess
|
|
21605
21500
|
} = {}) {
|
|
21606
21501
|
if (typeof identity !== "string" || !identity.trim()) return Promise.reject(new Error("identity is required"));
|
|
21607
|
-
return new Promise((
|
|
21502
|
+
return new Promise((resolve, reject) => {
|
|
21608
21503
|
const invocation = commandFor(identity, nativeSessionId, env);
|
|
21609
21504
|
const child = spawnImpl(invocation.command, invocation.args, {
|
|
21610
21505
|
env: invocation.env ?? env,
|
|
@@ -21636,7 +21531,7 @@ function waitForForegroundMail(identity, nativeSessionId, {
|
|
|
21636
21531
|
const newline = stdout.indexOf("\n");
|
|
21637
21532
|
if (newline === -1) return;
|
|
21638
21533
|
const event = stdout.slice(0, newline).trim();
|
|
21639
|
-
if (event) finish(
|
|
21534
|
+
if (event) finish(resolve, event);
|
|
21640
21535
|
});
|
|
21641
21536
|
child.once("exit", (code, childSignal) => {
|
|
21642
21537
|
if (settled) return;
|