@ours.network/codex 1.2.0-nightly.7 → 1.2.0-nightly.9
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/dist/hooks-runner.mjs +49 -239
- package/dist/host-hooks.mjs +9 -49
- package/dist/monitor-mcp.mjs +20 -123
- package/dist/network-watch.mjs +17 -119
- package/dist/profile.mjs +14 -154
- package/install.sh +14 -33
- package/package.json +3 -3
- package/skills/ours/SKILL.md +6 -6
- package/skills/ours/references/configuration.md +20 -22
- package/src/hooks/runner.mjs +1 -48
- package/src/launcher.mjs +1 -9
- package/src/profile.mjs +1 -40
package/dist/host-hooks.mjs
CHANGED
|
@@ -127,53 +127,13 @@ var ApplicationIdentityStore = class {
|
|
|
127
127
|
};
|
|
128
128
|
|
|
129
129
|
// ../core/src/host-profile.ts
|
|
130
|
-
import {
|
|
131
|
-
|
|
132
|
-
var PROFILE_KEYS = ["endpoint", "expectedInstanceId", "credentialPath"];
|
|
133
|
-
var LEGACY_SELECTION_KEYS = ["port", "stateDir", "apiToken", "apiVisibility"];
|
|
134
|
-
var UUID2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
135
|
-
function profileError(reason) {
|
|
136
|
-
return new Error(`Invalid external host profile: ${reason}`);
|
|
137
|
-
}
|
|
138
|
-
function validateHostProfile(value) {
|
|
139
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
140
|
-
throw profileError("expected a complete host profile object.");
|
|
141
|
-
}
|
|
142
|
-
const record = value;
|
|
143
|
-
const present = PROFILE_KEYS.filter((key) => Object.hasOwn(record, key));
|
|
144
|
-
if (present.length !== PROFILE_KEYS.length) {
|
|
145
|
-
throw profileError("expected a complete host profile tuple: endpoint, expectedInstanceId, credentialPath.");
|
|
146
|
-
}
|
|
147
|
-
const mixed = LEGACY_SELECTION_KEYS.filter((key) => Object.hasOwn(record, key));
|
|
148
|
-
if (mixed.length) throw profileError(`legacy selection keys cannot be mixed with a host profile (${mixed.join(", ")}).`);
|
|
149
|
-
const endpoint = record.endpoint;
|
|
150
|
-
const expectedInstanceId = record.expectedInstanceId;
|
|
151
|
-
const credentialPath = record.credentialPath;
|
|
152
|
-
if (typeof endpoint !== "string" || endpoint.trim() !== endpoint || endpoint === "") {
|
|
153
|
-
throw profileError("endpoint must be a non-empty HTTP or HTTPS base URL.");
|
|
154
|
-
}
|
|
155
|
-
if (typeof expectedInstanceId !== "string" || !UUID2.test(expectedInstanceId)) {
|
|
156
|
-
throw profileError("expectedInstanceId must be a lowercase UUID.");
|
|
157
|
-
}
|
|
158
|
-
if (typeof credentialPath !== "string" || credentialPath === "" || !credentialPath.startsWith("/") || resolve2(credentialPath) !== credentialPath) {
|
|
159
|
-
throw profileError("credentialPath must be a normalized absolute path.");
|
|
160
|
-
}
|
|
161
|
-
let url;
|
|
162
|
-
try {
|
|
163
|
-
url = new URL(endpoint);
|
|
164
|
-
} catch {
|
|
165
|
-
throw profileError("endpoint must be an HTTP or HTTPS base URL.");
|
|
166
|
-
}
|
|
167
|
-
if (url.protocol !== "http:" && url.protocol !== "https:" || url.username || url.password || /[\s\\?#]/.test(endpoint) || url.search || url.hash) {
|
|
168
|
-
throw profileError("endpoint must be an HTTP or HTTPS base URL without credentials, query, or fragment.");
|
|
169
|
-
}
|
|
170
|
-
return { endpoint: normalizeDaemonEndpoint(endpoint), expectedInstanceId, credentialPath };
|
|
171
|
-
}
|
|
130
|
+
import { readGatewayClientProfile, validateGatewayClientProfile } from "@ours.network/sdk/client";
|
|
131
|
+
var validateHostProfile = validateGatewayClientProfile;
|
|
172
132
|
|
|
173
133
|
// ../core/src/native-session.ts
|
|
174
134
|
import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
|
|
175
135
|
import { chmod as chmod2, mkdir as mkdir2, readFile as readFile2, rename as rename2, unlink as unlink2, writeFile as writeFile2 } from "node:fs/promises";
|
|
176
|
-
import { dirname as dirname2, join as join2, resolve as
|
|
136
|
+
import { dirname as dirname2, join as join2, resolve as resolve2 } from "node:path";
|
|
177
137
|
import { attachOursClient } from "@ours.network/sdk";
|
|
178
138
|
|
|
179
139
|
// ../core/src/session-guard.ts
|
|
@@ -251,7 +211,7 @@ function acquireSessionGuard(recordPath) {
|
|
|
251
211
|
|
|
252
212
|
// ../core/src/native-session.ts
|
|
253
213
|
var RECORD_VERSION = 1;
|
|
254
|
-
var
|
|
214
|
+
var UUID2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
255
215
|
var clients = /* @__PURE__ */ new Map();
|
|
256
216
|
var operations = /* @__PURE__ */ new Map();
|
|
257
217
|
function selector(value) {
|
|
@@ -261,10 +221,10 @@ function selector(value) {
|
|
|
261
221
|
return value;
|
|
262
222
|
}
|
|
263
223
|
function nativeSessionRecordPath(hostRecordRoot, instanceId, nativeSessionId) {
|
|
264
|
-
if (!hostRecordRoot || !hostRecordRoot.startsWith("/") ||
|
|
224
|
+
if (!hostRecordRoot || !hostRecordRoot.startsWith("/") || resolve2(hostRecordRoot) !== hostRecordRoot) {
|
|
265
225
|
throw new Error("Native session host record root must be a normalized absolute path.");
|
|
266
226
|
}
|
|
267
|
-
if (!
|
|
227
|
+
if (!UUID2.test(instanceId)) throw new Error("Native session daemon instance must be a lowercase UUID.");
|
|
268
228
|
const hash = createHash2("sha256").update(selector(nativeSessionId), "utf8").digest("hex");
|
|
269
229
|
return join2(hostRecordRoot, "sessions", instanceId, `${hash}.json`);
|
|
270
230
|
}
|
|
@@ -280,7 +240,7 @@ function parseRecord(text, path) {
|
|
|
280
240
|
}
|
|
281
241
|
const record = value;
|
|
282
242
|
const keys = Object.keys(record).sort();
|
|
283
|
-
if (keys.join(",") !== "ownerInstanceId,state,version" || record.version !== RECORD_VERSION || typeof record.ownerInstanceId !== "string" || !
|
|
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))) {
|
|
284
244
|
throw new Error(`Invalid native session record at ${path}.`);
|
|
285
245
|
}
|
|
286
246
|
return record;
|
|
@@ -314,8 +274,8 @@ async function writeRecord(path, record) {
|
|
|
314
274
|
async function serialized(key, operation) {
|
|
315
275
|
const predecessor = operations.get(key) ?? Promise.resolve();
|
|
316
276
|
let release;
|
|
317
|
-
const gate = new Promise((
|
|
318
|
-
release =
|
|
277
|
+
const gate = new Promise((resolve3) => {
|
|
278
|
+
release = resolve3;
|
|
319
279
|
});
|
|
320
280
|
const tail = predecessor.catch(() => {
|
|
321
281
|
}).then(() => gate);
|
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,116 +21401,13 @@ 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
|
-
import { normalizeDaemonEndpoint } from "@ours.network/sdk/client";
|
|
21408
|
-
var PROFILE_KEYS = ["endpoint", "expectedInstanceId", "credentialPath"];
|
|
21409
|
-
var LEGACY_SELECTION_KEYS = ["port", "stateDir", "apiToken", "apiVisibility"];
|
|
21410
|
-
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
21411
|
-
var CONFLICTING_ENV = ["OURS_API_TOKEN", "OURS_PORT", "OURS_STATE_DIR", "OURS_DAEMON_ID"];
|
|
21412
|
-
function profileError(reason) {
|
|
21413
|
-
return new Error(`Invalid external host profile: ${reason}`);
|
|
21414
|
-
}
|
|
21415
|
-
function validateHostProfile(value) {
|
|
21416
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
21417
|
-
throw profileError("expected a complete host profile object.");
|
|
21418
|
-
}
|
|
21419
|
-
const record2 = value;
|
|
21420
|
-
const present = PROFILE_KEYS.filter((key) => Object.hasOwn(record2, key));
|
|
21421
|
-
if (present.length !== PROFILE_KEYS.length) {
|
|
21422
|
-
throw profileError("expected a complete host profile tuple: endpoint, expectedInstanceId, credentialPath.");
|
|
21423
|
-
}
|
|
21424
|
-
const mixed = LEGACY_SELECTION_KEYS.filter((key) => Object.hasOwn(record2, key));
|
|
21425
|
-
if (mixed.length) throw profileError(`legacy selection keys cannot be mixed with a host profile (${mixed.join(", ")}).`);
|
|
21426
|
-
const endpoint = record2.endpoint;
|
|
21427
|
-
const expectedInstanceId = record2.expectedInstanceId;
|
|
21428
|
-
const credentialPath = record2.credentialPath;
|
|
21429
|
-
if (typeof endpoint !== "string" || endpoint.trim() !== endpoint || endpoint === "") {
|
|
21430
|
-
throw profileError("endpoint must be a non-empty HTTP or HTTPS base URL.");
|
|
21431
|
-
}
|
|
21432
|
-
if (typeof expectedInstanceId !== "string" || !UUID.test(expectedInstanceId)) {
|
|
21433
|
-
throw profileError("expectedInstanceId must be a lowercase UUID.");
|
|
21434
|
-
}
|
|
21435
|
-
if (typeof credentialPath !== "string" || credentialPath === "" || !credentialPath.startsWith("/") || resolve(credentialPath) !== credentialPath) {
|
|
21436
|
-
throw profileError("credentialPath must be a normalized absolute path.");
|
|
21437
|
-
}
|
|
21438
|
-
let url;
|
|
21439
|
-
try {
|
|
21440
|
-
url = new URL(endpoint);
|
|
21441
|
-
} catch {
|
|
21442
|
-
throw profileError("endpoint must be an HTTP or HTTPS base URL.");
|
|
21443
|
-
}
|
|
21444
|
-
if (url.protocol !== "http:" && url.protocol !== "https:" || url.username || url.password || /[\s\\?#]/.test(endpoint) || url.search || url.hash) {
|
|
21445
|
-
throw profileError("endpoint must be an HTTP or HTTPS base URL without credentials, query, or fragment.");
|
|
21446
|
-
}
|
|
21447
|
-
return { endpoint: normalizeDaemonEndpoint(endpoint), expectedInstanceId, credentialPath };
|
|
21448
|
-
}
|
|
21449
|
-
function readProfileObject(configPath) {
|
|
21450
|
-
let text;
|
|
21451
|
-
try {
|
|
21452
|
-
text = readFileSync(configPath, "utf8");
|
|
21453
|
-
} catch {
|
|
21454
|
-
throw new Error(`Cannot read host profile ${JSON.stringify(configPath)}.`);
|
|
21455
|
-
}
|
|
21456
|
-
let value;
|
|
21457
|
-
try {
|
|
21458
|
-
value = JSON.parse(text);
|
|
21459
|
-
} catch {
|
|
21460
|
-
throw profileError(`config ${JSON.stringify(configPath)} is not valid JSON.`);
|
|
21461
|
-
}
|
|
21462
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) throw profileError(`config ${JSON.stringify(configPath)} must be an object.`);
|
|
21463
|
-
return value;
|
|
21464
|
-
}
|
|
21465
|
-
function assertPrivateRegularFile(configPath) {
|
|
21466
|
-
let stat;
|
|
21467
|
-
try {
|
|
21468
|
-
stat = lstatSync(configPath);
|
|
21469
|
-
} catch {
|
|
21470
|
-
throw new Error(`Cannot read host profile ${JSON.stringify(configPath)}.`);
|
|
21471
|
-
}
|
|
21472
|
-
if (!stat.isFile()) throw profileError(`config ${JSON.stringify(configPath)} must be a regular file.`);
|
|
21473
|
-
const currentUid = process.getuid?.();
|
|
21474
|
-
if (currentUid === void 0 || stat.uid !== currentUid) throw profileError(`config ${JSON.stringify(configPath)} must be owned by the current user.`);
|
|
21475
|
-
if ((stat.mode & 63) !== 0) throw profileError(`config ${JSON.stringify(configPath)} must have private permissions.`);
|
|
21476
|
-
}
|
|
21477
|
-
function readHostProfile(configPath) {
|
|
21478
|
-
const record2 = readProfileObject(configPath);
|
|
21479
|
-
const found = PROFILE_KEYS.filter((key) => Object.hasOwn(record2, key));
|
|
21480
|
-
if (found.length === 0) return null;
|
|
21481
|
-
assertPrivateRegularFile(configPath);
|
|
21482
|
-
return validateHostProfile(record2);
|
|
21483
|
-
}
|
|
21484
|
-
function nonempty(env, key) {
|
|
21485
|
-
return (env[key] ?? "").trim() !== "";
|
|
21486
|
-
}
|
|
21404
|
+
import { readGatewayClientProfile, validateGatewayClientProfile } from "@ours.network/sdk/client";
|
|
21487
21405
|
function hostProfileSelectionFromEnv(env = process.env) {
|
|
21488
|
-
const
|
|
21489
|
-
const managedPath = resolve(env.HOME || homedir(), ".ours-client", "profile.json");
|
|
21490
|
-
let configPath = explicit;
|
|
21491
|
-
if (!configPath) {
|
|
21492
|
-
try {
|
|
21493
|
-
lstatSync(managedPath);
|
|
21494
|
-
configPath = managedPath;
|
|
21495
|
-
} catch (error2) {
|
|
21496
|
-
if (error2.code !== "ENOENT") {
|
|
21497
|
-
throw new Error(`Cannot read host profile ${JSON.stringify(managedPath)}.`);
|
|
21498
|
-
}
|
|
21499
|
-
configPath = resolve(homedir(), ".ours", "config.json");
|
|
21500
|
-
if (!existsSync(configPath)) return null;
|
|
21501
|
-
}
|
|
21502
|
-
}
|
|
21503
|
-
const profile = readHostProfile(configPath);
|
|
21504
|
-
if (profile === null) {
|
|
21505
|
-
if (resolve(configPath) === managedPath) throw profileError("managed client config must contain a complete host profile tuple.");
|
|
21506
|
-
return null;
|
|
21507
|
-
}
|
|
21508
|
-
const conflicting = CONFLICTING_ENV.filter((key) => nonempty(env, key));
|
|
21509
|
-
if (conflicting.length) throw profileError(`${conflicting.join(", ")} conflicts with host-profile mode.`);
|
|
21406
|
+
const { configPath, ...profile } = readGatewayClientProfile(env);
|
|
21510
21407
|
return { profile, configPath };
|
|
21511
21408
|
}
|
|
21512
21409
|
function hostProfileFromEnv(env = process.env) {
|
|
21513
|
-
return hostProfileSelectionFromEnv(env)
|
|
21410
|
+
return hostProfileSelectionFromEnv(env).profile;
|
|
21514
21411
|
}
|
|
21515
21412
|
|
|
21516
21413
|
// ../core/src/native-session.ts
|
|
@@ -21528,7 +21425,7 @@ function encodeControlResponse(value) {
|
|
|
21528
21425
|
|
|
21529
21426
|
// src/control-server.mjs
|
|
21530
21427
|
function sendControlCommand(socketPath, capability, message, timeoutMs = 2e3) {
|
|
21531
|
-
return new Promise((
|
|
21428
|
+
return new Promise((resolve, reject) => {
|
|
21532
21429
|
const socket = createConnection(socketPath);
|
|
21533
21430
|
let buffer = "";
|
|
21534
21431
|
const timer = setTimeout(() => {
|
|
@@ -21549,7 +21446,7 @@ function sendControlCommand(socketPath, capability, message, timeoutMs = 2e3) {
|
|
|
21549
21446
|
try {
|
|
21550
21447
|
const response = JSON.parse(buffer);
|
|
21551
21448
|
if (!response.ok) reject(new Error(response.error || "monitor control failed"));
|
|
21552
|
-
else
|
|
21449
|
+
else resolve(response);
|
|
21553
21450
|
} catch (error2) {
|
|
21554
21451
|
reject(error2);
|
|
21555
21452
|
}
|
|
@@ -21602,7 +21499,7 @@ function waitForForegroundMail(identity, nativeSessionId, {
|
|
|
21602
21499
|
commandFor = foregroundWatchProcess
|
|
21603
21500
|
} = {}) {
|
|
21604
21501
|
if (typeof identity !== "string" || !identity.trim()) return Promise.reject(new Error("identity is required"));
|
|
21605
|
-
return new Promise((
|
|
21502
|
+
return new Promise((resolve, reject) => {
|
|
21606
21503
|
const invocation = commandFor(identity, nativeSessionId, env);
|
|
21607
21504
|
const child = spawnImpl(invocation.command, invocation.args, {
|
|
21608
21505
|
env: invocation.env ?? env,
|
|
@@ -21634,7 +21531,7 @@ function waitForForegroundMail(identity, nativeSessionId, {
|
|
|
21634
21531
|
const newline = stdout.indexOf("\n");
|
|
21635
21532
|
if (newline === -1) return;
|
|
21636
21533
|
const event = stdout.slice(0, newline).trim();
|
|
21637
|
-
if (event) finish(
|
|
21534
|
+
if (event) finish(resolve, event);
|
|
21638
21535
|
});
|
|
21639
21536
|
child.once("exit", (code, childSignal) => {
|
|
21640
21537
|
if (settled) return;
|
package/dist/network-watch.mjs
CHANGED
|
@@ -1,129 +1,27 @@
|
|
|
1
1
|
// src/network-watch.mjs
|
|
2
|
-
import { homedir
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
3
|
import { dirname as dirname2, join as join3 } from "node:path";
|
|
4
4
|
|
|
5
5
|
// ../core/src/host-profile.ts
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
import { resolve } from "node:path";
|
|
9
|
-
import { normalizeDaemonEndpoint } from "@ours.network/sdk/client";
|
|
10
|
-
var PROFILE_KEYS = ["endpoint", "expectedInstanceId", "credentialPath"];
|
|
11
|
-
var LEGACY_SELECTION_KEYS = ["port", "stateDir", "apiToken", "apiVisibility"];
|
|
12
|
-
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
13
|
-
var CONFLICTING_ENV = ["OURS_API_TOKEN", "OURS_PORT", "OURS_STATE_DIR", "OURS_DAEMON_ID"];
|
|
14
|
-
function profileError(reason) {
|
|
15
|
-
return new Error(`Invalid external host profile: ${reason}`);
|
|
16
|
-
}
|
|
17
|
-
function validateHostProfile(value) {
|
|
18
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
19
|
-
throw profileError("expected a complete host profile object.");
|
|
20
|
-
}
|
|
21
|
-
const record = value;
|
|
22
|
-
const present = PROFILE_KEYS.filter((key) => Object.hasOwn(record, key));
|
|
23
|
-
if (present.length !== PROFILE_KEYS.length) {
|
|
24
|
-
throw profileError("expected a complete host profile tuple: endpoint, expectedInstanceId, credentialPath.");
|
|
25
|
-
}
|
|
26
|
-
const mixed = LEGACY_SELECTION_KEYS.filter((key) => Object.hasOwn(record, key));
|
|
27
|
-
if (mixed.length) throw profileError(`legacy selection keys cannot be mixed with a host profile (${mixed.join(", ")}).`);
|
|
28
|
-
const endpoint = record.endpoint;
|
|
29
|
-
const expectedInstanceId = record.expectedInstanceId;
|
|
30
|
-
const credentialPath = record.credentialPath;
|
|
31
|
-
if (typeof endpoint !== "string" || endpoint.trim() !== endpoint || endpoint === "") {
|
|
32
|
-
throw profileError("endpoint must be a non-empty HTTP or HTTPS base URL.");
|
|
33
|
-
}
|
|
34
|
-
if (typeof expectedInstanceId !== "string" || !UUID.test(expectedInstanceId)) {
|
|
35
|
-
throw profileError("expectedInstanceId must be a lowercase UUID.");
|
|
36
|
-
}
|
|
37
|
-
if (typeof credentialPath !== "string" || credentialPath === "" || !credentialPath.startsWith("/") || resolve(credentialPath) !== credentialPath) {
|
|
38
|
-
throw profileError("credentialPath must be a normalized absolute path.");
|
|
39
|
-
}
|
|
40
|
-
let url;
|
|
41
|
-
try {
|
|
42
|
-
url = new URL(endpoint);
|
|
43
|
-
} catch {
|
|
44
|
-
throw profileError("endpoint must be an HTTP or HTTPS base URL.");
|
|
45
|
-
}
|
|
46
|
-
if (url.protocol !== "http:" && url.protocol !== "https:" || url.username || url.password || /[\s\\?#]/.test(endpoint) || url.search || url.hash) {
|
|
47
|
-
throw profileError("endpoint must be an HTTP or HTTPS base URL without credentials, query, or fragment.");
|
|
48
|
-
}
|
|
49
|
-
return { endpoint: normalizeDaemonEndpoint(endpoint), expectedInstanceId, credentialPath };
|
|
50
|
-
}
|
|
51
|
-
function readProfileObject(configPath) {
|
|
52
|
-
let text;
|
|
53
|
-
try {
|
|
54
|
-
text = readFileSync(configPath, "utf8");
|
|
55
|
-
} catch {
|
|
56
|
-
throw new Error(`Cannot read host profile ${JSON.stringify(configPath)}.`);
|
|
57
|
-
}
|
|
58
|
-
let value;
|
|
59
|
-
try {
|
|
60
|
-
value = JSON.parse(text);
|
|
61
|
-
} catch {
|
|
62
|
-
throw profileError(`config ${JSON.stringify(configPath)} is not valid JSON.`);
|
|
63
|
-
}
|
|
64
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) throw profileError(`config ${JSON.stringify(configPath)} must be an object.`);
|
|
65
|
-
return value;
|
|
66
|
-
}
|
|
67
|
-
function assertPrivateRegularFile(configPath) {
|
|
68
|
-
let stat;
|
|
69
|
-
try {
|
|
70
|
-
stat = lstatSync(configPath);
|
|
71
|
-
} catch {
|
|
72
|
-
throw new Error(`Cannot read host profile ${JSON.stringify(configPath)}.`);
|
|
73
|
-
}
|
|
74
|
-
if (!stat.isFile()) throw profileError(`config ${JSON.stringify(configPath)} must be a regular file.`);
|
|
75
|
-
const currentUid = process.getuid?.();
|
|
76
|
-
if (currentUid === void 0 || stat.uid !== currentUid) throw profileError(`config ${JSON.stringify(configPath)} must be owned by the current user.`);
|
|
77
|
-
if ((stat.mode & 63) !== 0) throw profileError(`config ${JSON.stringify(configPath)} must have private permissions.`);
|
|
78
|
-
}
|
|
79
|
-
function readHostProfile(configPath) {
|
|
80
|
-
const record = readProfileObject(configPath);
|
|
81
|
-
const found = PROFILE_KEYS.filter((key) => Object.hasOwn(record, key));
|
|
82
|
-
if (found.length === 0) return null;
|
|
83
|
-
assertPrivateRegularFile(configPath);
|
|
84
|
-
return validateHostProfile(record);
|
|
85
|
-
}
|
|
86
|
-
function nonempty(env, key) {
|
|
87
|
-
return (env[key] ?? "").trim() !== "";
|
|
88
|
-
}
|
|
6
|
+
import { readGatewayClientProfile, validateGatewayClientProfile } from "@ours.network/sdk/client";
|
|
7
|
+
var validateHostProfile = validateGatewayClientProfile;
|
|
89
8
|
function hostProfileSelectionFromEnv(env = process.env) {
|
|
90
|
-
const
|
|
91
|
-
const managedPath = resolve(env.HOME || homedir(), ".ours-client", "profile.json");
|
|
92
|
-
let configPath = explicit;
|
|
93
|
-
if (!configPath) {
|
|
94
|
-
try {
|
|
95
|
-
lstatSync(managedPath);
|
|
96
|
-
configPath = managedPath;
|
|
97
|
-
} catch (error) {
|
|
98
|
-
if (error.code !== "ENOENT") {
|
|
99
|
-
throw new Error(`Cannot read host profile ${JSON.stringify(managedPath)}.`);
|
|
100
|
-
}
|
|
101
|
-
configPath = resolve(homedir(), ".ours", "config.json");
|
|
102
|
-
if (!existsSync(configPath)) return null;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
const profile = readHostProfile(configPath);
|
|
106
|
-
if (profile === null) {
|
|
107
|
-
if (resolve(configPath) === managedPath) throw profileError("managed client config must contain a complete host profile tuple.");
|
|
108
|
-
return null;
|
|
109
|
-
}
|
|
110
|
-
const conflicting = CONFLICTING_ENV.filter((key) => nonempty(env, key));
|
|
111
|
-
if (conflicting.length) throw profileError(`${conflicting.join(", ")} conflicts with host-profile mode.`);
|
|
9
|
+
const { configPath, ...profile } = readGatewayClientProfile(env);
|
|
112
10
|
return { profile, configPath };
|
|
113
11
|
}
|
|
114
12
|
function hostProfileFromEnv(env = process.env) {
|
|
115
|
-
return hostProfileSelectionFromEnv(env)
|
|
13
|
+
return hostProfileSelectionFromEnv(env).profile;
|
|
116
14
|
}
|
|
117
15
|
|
|
118
16
|
// ../core/src/native-session.ts
|
|
119
17
|
import { createHash as createHash2, randomUUID } from "node:crypto";
|
|
120
18
|
import { chmod, mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
121
|
-
import { dirname, join as join2, resolve
|
|
19
|
+
import { dirname, join as join2, resolve } from "node:path";
|
|
122
20
|
import { attachOursClient } from "@ours.network/sdk";
|
|
123
21
|
|
|
124
22
|
// ../core/src/session-guard.ts
|
|
125
23
|
import { createHash } from "node:crypto";
|
|
126
|
-
import { openSync, closeSync, lstatSync
|
|
24
|
+
import { openSync, closeSync, lstatSync, mkdirSync, chmodSync, constants } from "node:fs";
|
|
127
25
|
import { join } from "node:path";
|
|
128
26
|
import { createRequire } from "node:module";
|
|
129
27
|
var BUSY_TIMEOUT_MS = 5e3;
|
|
@@ -154,7 +52,7 @@ function ensureGuardFile(guardPath) {
|
|
|
154
52
|
} catch (error) {
|
|
155
53
|
if (error.code !== "EEXIST") throw error;
|
|
156
54
|
}
|
|
157
|
-
const stat =
|
|
55
|
+
const stat = lstatSync(guardPath);
|
|
158
56
|
if (!stat.isFile() || stat.uid !== process.getuid() || (stat.mode & 63) !== 0 || stat.nlink !== 1) {
|
|
159
57
|
throw new Error("Native session guard file must be an owner-only regular file");
|
|
160
58
|
}
|
|
@@ -170,11 +68,11 @@ function acquireSessionGuard(recordPath) {
|
|
|
170
68
|
const hash = createHash("sha256").update(recordPath).digest("hex");
|
|
171
69
|
const guardPath = join(base, `${hash}.guard`);
|
|
172
70
|
ensureGuardFile(guardPath);
|
|
173
|
-
const before =
|
|
71
|
+
const before = lstatSync(guardPath);
|
|
174
72
|
const Database = resolveDatabase();
|
|
175
73
|
const db = new Database(guardPath);
|
|
176
74
|
try {
|
|
177
|
-
const after =
|
|
75
|
+
const after = lstatSync(guardPath);
|
|
178
76
|
if (before.dev !== after.dev || before.ino !== after.ino || before.mode !== after.mode || before.uid !== after.uid) {
|
|
179
77
|
throw new Error("Native session guard file changed during acquisition");
|
|
180
78
|
}
|
|
@@ -196,7 +94,7 @@ function acquireSessionGuard(recordPath) {
|
|
|
196
94
|
|
|
197
95
|
// ../core/src/native-session.ts
|
|
198
96
|
var RECORD_VERSION = 1;
|
|
199
|
-
var
|
|
97
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
200
98
|
var clients = /* @__PURE__ */ new Map();
|
|
201
99
|
var operations = /* @__PURE__ */ new Map();
|
|
202
100
|
function selector(value) {
|
|
@@ -206,10 +104,10 @@ function selector(value) {
|
|
|
206
104
|
return value;
|
|
207
105
|
}
|
|
208
106
|
function nativeSessionRecordPath(hostRecordRoot, instanceId, nativeSessionId) {
|
|
209
|
-
if (!hostRecordRoot || !hostRecordRoot.startsWith("/") ||
|
|
107
|
+
if (!hostRecordRoot || !hostRecordRoot.startsWith("/") || resolve(hostRecordRoot) !== hostRecordRoot) {
|
|
210
108
|
throw new Error("Native session host record root must be a normalized absolute path.");
|
|
211
109
|
}
|
|
212
|
-
if (!
|
|
110
|
+
if (!UUID.test(instanceId)) throw new Error("Native session daemon instance must be a lowercase UUID.");
|
|
213
111
|
const hash = createHash2("sha256").update(selector(nativeSessionId), "utf8").digest("hex");
|
|
214
112
|
return join2(hostRecordRoot, "sessions", instanceId, `${hash}.json`);
|
|
215
113
|
}
|
|
@@ -225,7 +123,7 @@ function parseRecord(text, path) {
|
|
|
225
123
|
}
|
|
226
124
|
const record = value;
|
|
227
125
|
const keys = Object.keys(record).sort();
|
|
228
|
-
if (keys.join(",") !== "ownerInstanceId,state,version" || record.version !== RECORD_VERSION || typeof record.ownerInstanceId !== "string" || !
|
|
126
|
+
if (keys.join(",") !== "ownerInstanceId,state,version" || record.version !== RECORD_VERSION || typeof record.ownerInstanceId !== "string" || !UUID.test(record.ownerInstanceId) || !["active", "pending", "ended"].includes(String(record.state))) {
|
|
229
127
|
throw new Error(`Invalid native session record at ${path}.`);
|
|
230
128
|
}
|
|
231
129
|
return record;
|
|
@@ -259,8 +157,8 @@ async function writeRecord(path, record) {
|
|
|
259
157
|
async function serialized(key, operation) {
|
|
260
158
|
const predecessor = operations.get(key) ?? Promise.resolve();
|
|
261
159
|
let release;
|
|
262
|
-
const gate = new Promise((
|
|
263
|
-
release =
|
|
160
|
+
const gate = new Promise((resolve2) => {
|
|
161
|
+
release = resolve2;
|
|
264
162
|
});
|
|
265
163
|
const tail = predecessor.catch(() => {
|
|
266
164
|
}).then(() => gate);
|
|
@@ -359,7 +257,7 @@ async function runNetworkWatch({
|
|
|
359
257
|
if (typeof nativeSessionId !== "string" || !nativeSessionId.trim()) throw new Error("native session id is required");
|
|
360
258
|
const profile = profileValue ?? hostProfileFromEnv(env);
|
|
361
259
|
if (!profile) throw new Error("A selected host network profile is required.");
|
|
362
|
-
const appPath = env.OURS_MCP_CONFIG || join3(env.HOME ||
|
|
260
|
+
const appPath = env.OURS_MCP_CONFIG || join3(env.HOME || homedir(), ".ours-mcp", "config.json");
|
|
363
261
|
const hostRecordRoot = hostRecordRootValue ?? dirname2(appPath);
|
|
364
262
|
const controller = new AbortController();
|
|
365
263
|
const stop = () => controller.abort();
|