@ours.network/codex 1.1.0 → 1.2.0-nightly.1
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/.mcp.json +2 -2
- package/README.md +33 -5
- package/bin/container-file-transfer.mjs +147 -0
- package/bin/container-launch.mjs +40 -0
- package/bin/network-watch.mjs +8 -0
- package/bin/proxy.mjs +42 -21
- package/dist/hooks-runner.mjs +16649 -0
- package/dist/monitor-mcp.mjs +154 -24
- package/dist/network-proxy.mjs +16782 -0
- package/dist/network-watch.mjs +391 -0
- package/dist/profile.mjs +198 -0
- package/hooks/hooks.json +3 -3
- package/package.json +3 -4
- package/skills/ours/SKILL.md +2 -1
- package/src/hooks/runner.mjs +54 -7
- package/src/launcher.mjs +50 -15
- package/src/monitor-mcp.mjs +19 -6
- package/src/network-proxy.mjs +308 -0
- package/src/network-watch.mjs +45 -0
- package/src/profile.mjs +17 -2
- package/src/watcher.mjs +29 -3
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 = resolve2.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 resolve2(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 resolve2(baseURI, relativeURI, options) {
|
|
3835
3835
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
3836
3836
|
const {
|
|
3837
3837
|
parsed: baseParsed,
|
|
@@ -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 &&
|
|
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
|
}
|
|
@@ -4193,7 +4193,7 @@ var require_fast_uri = __commonJS({
|
|
|
4193
4193
|
var fastUri = {
|
|
4194
4194
|
SCHEMES,
|
|
4195
4195
|
normalize,
|
|
4196
|
-
resolve,
|
|
4196
|
+
resolve: resolve2,
|
|
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((resolve2) => setTimeout(resolve2, 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((resolve2, 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
|
+
resolve2(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((resolve2, 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(resolve2, 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((resolve2) => setTimeout(resolve2, 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((resolve2) => {
|
|
21388
21388
|
const json = serializeMessage(message);
|
|
21389
21389
|
if (this._stdout.write(json)) {
|
|
21390
|
-
|
|
21390
|
+
resolve2();
|
|
21391
21391
|
} else {
|
|
21392
|
-
this._stdout.once("drain",
|
|
21392
|
+
this._stdout.once("drain", resolve2);
|
|
21393
21393
|
}
|
|
21394
21394
|
});
|
|
21395
21395
|
}
|
|
@@ -21398,6 +21398,125 @@ 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 { existsSync, lstatSync, readFileSync } from "node:fs";
|
|
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
|
+
}
|
|
21486
|
+
function hostProfileSelectionFromEnv(env = process.env) {
|
|
21487
|
+
const explicit = (env.OURS_CONFIG ?? "").trim();
|
|
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.`);
|
|
21509
|
+
return { profile, configPath };
|
|
21510
|
+
}
|
|
21511
|
+
function hostProfileFromEnv(env = process.env) {
|
|
21512
|
+
return hostProfileSelectionFromEnv(env)?.profile ?? null;
|
|
21513
|
+
}
|
|
21514
|
+
|
|
21515
|
+
// ../core/src/host-client/files.ts
|
|
21516
|
+
import { buildIdentityFile, writeIdentityFile } from "@ours.network/sdk/connector";
|
|
21517
|
+
|
|
21518
|
+
// ../core/src/native-session.ts
|
|
21519
|
+
import { attachOursClient } from "@ours.network/sdk";
|
|
21401
21520
|
|
|
21402
21521
|
// src/control-server.mjs
|
|
21403
21522
|
import { createServer, createConnection } from "node:net";
|
|
@@ -21411,7 +21530,7 @@ function encodeControlResponse(value) {
|
|
|
21411
21530
|
|
|
21412
21531
|
// src/control-server.mjs
|
|
21413
21532
|
function sendControlCommand(socketPath, capability, message, timeoutMs = 2e3) {
|
|
21414
|
-
return new Promise((
|
|
21533
|
+
return new Promise((resolve2, reject) => {
|
|
21415
21534
|
const socket = createConnection(socketPath);
|
|
21416
21535
|
let buffer = "";
|
|
21417
21536
|
const timer = setTimeout(() => {
|
|
@@ -21432,7 +21551,7 @@ function sendControlCommand(socketPath, capability, message, timeoutMs = 2e3) {
|
|
|
21432
21551
|
try {
|
|
21433
21552
|
const response = JSON.parse(buffer);
|
|
21434
21553
|
if (!response.ok) reject(new Error(response.error || "monitor control failed"));
|
|
21435
|
-
else
|
|
21554
|
+
else resolve2(response);
|
|
21436
21555
|
} catch (error2) {
|
|
21437
21556
|
reject(error2);
|
|
21438
21557
|
}
|
|
@@ -21442,10 +21561,20 @@ function sendControlCommand(socketPath, capability, message, timeoutMs = 2e3) {
|
|
|
21442
21561
|
|
|
21443
21562
|
// src/monitor-mcp.mjs
|
|
21444
21563
|
var require2 = createRequire(import.meta.url);
|
|
21564
|
+
var networkWatchPath = fileURLToPath(new URL("../bin/network-watch.mjs", import.meta.url));
|
|
21445
21565
|
var monitorToolNames = ["arm_monitor", "foreground_monitor", "disarm_monitor", "monitor_status"];
|
|
21446
21566
|
var content = (text, isError = false) => ({ content: [{ type: "text", text }], ...isError ? { isError: true } : {} });
|
|
21447
21567
|
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) {
|
|
21568
|
+
function foregroundWatchProcess(identity, nativeSessionId, env = process.env) {
|
|
21569
|
+
if (hostProfileFromEnv(env)) {
|
|
21570
|
+
if (typeof nativeSessionId !== "string" || !nativeSessionId.trim()) {
|
|
21571
|
+
throw new Error("Native session metadata is required for host-profile monitoring.");
|
|
21572
|
+
}
|
|
21573
|
+
return {
|
|
21574
|
+
command: process.execPath,
|
|
21575
|
+
args: [networkWatchPath, identity, nativeSessionId]
|
|
21576
|
+
};
|
|
21577
|
+
}
|
|
21449
21578
|
try {
|
|
21450
21579
|
const cliPath = require2.resolve("@ours.network/mcp/dist/cli.js");
|
|
21451
21580
|
return { command: process.execPath, args: [cliPath, "watch", identity] };
|
|
@@ -21468,23 +21597,24 @@ async function handleMonitorCommand(command, args = {}, { env = process.env, sen
|
|
|
21468
21597
|
if (command === "disarm") return { text: "Live ours monitor disarmed." };
|
|
21469
21598
|
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
21599
|
}
|
|
21471
|
-
function waitForForegroundMail(identity, {
|
|
21600
|
+
function waitForForegroundMail(identity, nativeSessionId, {
|
|
21472
21601
|
env = process.env,
|
|
21473
21602
|
spawnImpl = spawn,
|
|
21474
21603
|
signal,
|
|
21475
21604
|
commandFor = foregroundWatchProcess
|
|
21476
21605
|
} = {}) {
|
|
21477
21606
|
if (typeof identity !== "string" || !identity.trim()) return Promise.reject(new Error("identity is required"));
|
|
21478
|
-
return new Promise((
|
|
21479
|
-
const invocation = commandFor(identity);
|
|
21607
|
+
return new Promise((resolve2, reject) => {
|
|
21608
|
+
const invocation = commandFor(identity, nativeSessionId, env);
|
|
21480
21609
|
const child = spawnImpl(invocation.command, invocation.args, {
|
|
21481
|
-
env,
|
|
21482
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
21610
|
+
env: invocation.env ?? env,
|
|
21611
|
+
stdio: [invocation.stdin ?? "ignore", "pipe", "pipe"]
|
|
21483
21612
|
});
|
|
21484
21613
|
let stdout = "";
|
|
21485
21614
|
let stderr = "";
|
|
21486
21615
|
let settled = false;
|
|
21487
21616
|
const stop = () => {
|
|
21617
|
+
if (invocation.stdin === "pipe") child.stdin?.end();
|
|
21488
21618
|
if (!child.killed) child.kill("SIGTERM");
|
|
21489
21619
|
};
|
|
21490
21620
|
const finish = (fn, value) => {
|
|
@@ -21506,7 +21636,7 @@ function waitForForegroundMail(identity, {
|
|
|
21506
21636
|
const newline = stdout.indexOf("\n");
|
|
21507
21637
|
if (newline === -1) return;
|
|
21508
21638
|
const event = stdout.slice(0, newline).trim();
|
|
21509
|
-
if (event) finish(
|
|
21639
|
+
if (event) finish(resolve2, event);
|
|
21510
21640
|
});
|
|
21511
21641
|
child.once("exit", (code, childSignal) => {
|
|
21512
21642
|
if (settled) return;
|
|
@@ -21530,7 +21660,7 @@ function createMonitorMcpServer(deps = {}) {
|
|
|
21530
21660
|
identity: external_exports.string().min(1).describe("The already-bound ours identity to monitor.")
|
|
21531
21661
|
}, async ({ identity }, extra) => {
|
|
21532
21662
|
try {
|
|
21533
|
-
const event = await waitForForegroundMail(identity, { ...deps, signal: extra?.signal });
|
|
21663
|
+
const event = await waitForForegroundMail(identity, extra?._meta?.threadId, { ...deps, signal: extra?.signal });
|
|
21534
21664
|
return content(`Foreground ours monitor received a body-free arrival event: ${event}
|
|
21535
21665
|
Call get_messages now. After handling the mail, call foreground_monitor again without asking if monitoring should remain armed.`);
|
|
21536
21666
|
} catch (error2) {
|