@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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ours",
3
- "version": "1.2.0-nightly.7",
3
+ "version": "1.2.0-nightly.9",
4
4
  "description": "Secure agent-to-agent messaging and explicitly armed live mail wake for Codex CLI.",
5
5
  "author": {
6
6
  "name": "Adapt Toolkit",
package/README.md CHANGED
@@ -51,38 +51,21 @@ The launcher never starts, stops, restarts, or reconfigures the ours daemon. If
51
51
  selected daemon is absent or incompatible, it exits with an error and leaves standard
52
52
  `codex` available.
53
53
 
54
- ## Selecting a daemon
55
-
56
- Multiple daemons may run on one host when each uses a distinct port and state directory.
57
- For network clients, explicit `OURS_CONFIG` takes precedence over the managed
58
- `~/.ours-client/profile.json`. When the managed file exists, ordinary `ours-codex`
59
- launches need no environment override. Invalid or unreadable managed profiles
60
- fail before selecting another daemon. With neither selection, the existing
61
- local-daemon precedence remains:
62
-
63
- 1. `ours-codex --ours-port <port>`
64
- 2. `OURS_PORT`
65
- 3. the config selected by `OURS_CONFIG`
66
- 4. `~/.ours/config.json`
67
- 5. port `3050`
68
-
69
- All MCP, hooks, unread, and watcher calls inherit the same selected profile. Host-profile
70
- tools run in the local `@ours.network/mcp` stdio server included as a plugin dependency.
71
- The MCP server calls the selected daemon's authenticated HTTP API through the SDK.
72
- Example:
54
+ ## Selecting a gateway
55
+
56
+ Every client uses `~/.ours-client/profile.json`; `OURS_CONFIG` can select a complete
57
+ alternate profile. It contains `serverUrl`, `expectedInstanceId`, and `credentialPath`.
58
+ The daemon endpoint is derived as `serverUrl + /daemon`, including gateway path prefixes.
59
+ Missing, unsafe, or invalid profiles fail closed. Remove legacy port/state/token
60
+ selectors; `--ours-port` is rejected. Example after provisioning with `ours-install client`:
73
61
 
74
62
  ```sh
75
- OURS_CONFIG="$HOME/.ours/testing.json" ours-codex --ours-port 4050
63
+ OURS_CONFIG="$HOME/.ours-client/testing.json" ours-codex
76
64
  ```
77
65
 
78
- An operator-provisioned host profile uses the complete
79
- `endpoint`/`expectedInstanceId`/`credentialPath` tuple in the selected file. In this mode,
80
- do not combine the profile with `--ours-port`, `OURS_PORT`, `OURS_API_TOKEN`, or
81
- `OURS_STATE_DIR`. Codex forwards the actual selected path as `OURS_CONFIG` and the optional `OURS_MCP_CONFIG` host
82
- record location; the SDK verifies the expected
83
- daemon instance and rereads `credentialPath` for every request, so an operator token
84
- update takes effect in the running hook and live monitor without a restart or token
85
- snapshot in child process environments.
66
+ MCP, hooks, unread and watcher requests inherit that profile. The SDK verifies the
67
+ instance and rereads the issued credential for each request. Client installation and
68
+ launch never install, start, restart or reconfigure server services.
86
69
 
87
70
  Native ownership is keyed by Codex's actual thread ID. A normal live TUI exit forwards
88
71
  that ID through the same `SessionEnd` input used by the hook. If Codex has not produced
@@ -33,7 +33,7 @@ export function planTomlInstall(text) {
33
33
  }
34
34
 
35
35
  // Render the managed TOML block: the `ours` MCP server pointing at the globally-installed
36
- // daemon proxy (`ours-mcp proxy`). Equivalent to `codex mcp add ours -- ours-mcp proxy`.
36
+ // gateway client (`ours-mcp proxy`). Equivalent to `codex mcp add ours -- ours-mcp proxy`.
37
37
  export function renderTomlBlock() {
38
38
  return `${SENTINEL}
39
39
  # Added by @ours.network/codex install.sh. Remove this whole block to uninstall.
@@ -41,9 +41,9 @@ export function renderTomlBlock() {
41
41
  [mcp_servers.ours]
42
42
  command = "ours-mcp"
43
43
  args = ["proxy"]
44
- # Optional per-server environment overrides go here, e.g.:
44
+ # Optional whole shared gateway profile override:
45
45
  # [mcp_servers.ours.env]
46
- # OURS_PORT = "3050"
46
+ # OURS_CONFIG = "/absolute/private/path/profile.json"
47
47
  ${SENTINEL_END}
48
48
  `;
49
49
  }
@@ -5,14 +5,7 @@
5
5
  // npm i -g @ours.network/codex
6
6
  // ours-codex-install
7
7
  //
8
- // SUPERSEDED BY `ours-install`: the unified `ours-install` is now the single front door for the
9
- // WHOLE stack (ours core + harness plugins + ours-fleet + Telegram). `ours-codex-install` is kept
10
- // as a THIN ALIAS — by default it hands off to `ours-install` when that's available, so an old
11
- // muscle-memory command still lands the user in the current, unified flow. Pass `--codex-only`
12
- // (or set OURS_CODEX_LEGACY=1) to force the legacy Codex-only install below.
13
- //
14
- // Usage:
15
- // ours-codex-install [--codex-only] [--codex-dir DIR] [--skills-dir DIR] [--skip-daemon]
8
+ // Installs client plugin wiring only after shared gateway profile validation.
16
9
  import { spawnSync } from 'node:child_process';
17
10
  import { fileURLToPath } from 'node:url';
18
11
  import { dirname, join } from 'node:path';
@@ -33,38 +26,19 @@ for (let i = 0; i < argv.length; i++) {
33
26
  else { console.error(`ours-codex-install: unknown argument "${a}"`); help(); process.exit(2); }
34
27
  }
35
28
 
36
- // Thin-alias hand-off: unless the caller forced the legacy Codex-only path, run the unified
37
- // `ours-install` if we can find it — on PATH (published), or the monorepo sibling (dev/test).
38
- if (!opts.codexOnly && !process.env.OURS_CODEX_LEGACY) {
39
- const onPath = spawnSync('bash', ['-c', 'command -v ours-install'], { encoding: 'utf8' });
40
- const unified = (onPath.status === 0 && (onPath.stdout || '').trim())
41
- ? { cmd: (onPath.stdout || '').trim(), args: argv }
42
- : (() => {
43
- const sib = join(PKG, '..', 'installer', 'install.mjs');
44
- return existsSync(sib) ? { cmd: process.execPath, args: [sib, ...argv] } : null;
45
- })();
46
- if (unified) {
47
- const r = spawnSync(unified.cmd, unified.args, { stdio: 'inherit', env: process.env });
48
- if (r.error) { console.error(`ours-codex-install: ${r.error.message}`); process.exit(1); }
49
- process.exit(r.status ?? 0);
50
- }
51
- // No unified installer found → fall through to the legacy Codex-only install (still never a dead end).
52
- }
53
-
54
29
  function help() {
55
30
  console.log(`ours-codex-install — set up the ours.network plugin for the OpenAI Codex CLI.
56
31
 
57
32
  ours-codex-install [options]
58
33
 
59
- By default this hands off to the unified \`ours-install\` (the single front door for the whole
60
- stack). Use --codex-only to run the legacy Codex-only install instead. Standard mode uses
34
+ Requires clients and a shared gateway profile provisioned by \`ours-install client\`. Standard mode uses
61
35
  \`codex\`; live mode uses \`ours-codex\`. Live monitoring still requires explicit consent.
62
36
 
63
37
  Options:
64
- --codex-only run the legacy Codex-only install (skip the unified ours-install)
38
+ --codex-only compatibility flag; plugin setup is always client-only
65
39
  --codex-dir <dir> Codex config+AGENTS.md root (default ~/.codex) [--codex-only]
66
40
  --skills-dir <dir> skills root (default ~/.agents/skills — USER scope) [--codex-only]
67
- --skip-daemon do not install/start the ours daemon [--codex-only]
41
+ --skip-daemon compatibility flag; server lifecycle is never changed [--codex-only]
68
42
  -h, --help show this help
69
43
 
70
44
  Idempotent: safe to re-run. Start a new Codex thread after installation and review the
@@ -81,7 +55,6 @@ if (!existsSync(INSTALL)) {
81
55
  const env = { ...process.env };
82
56
  if (opts.codexDir) env.CODEX_DIR = opts.codexDir;
83
57
  if (opts.skillsDir) env.SKILLS_DIR = opts.skillsDir;
84
- if (opts.skipDaemon) env.OURS_INSTALL_SKIP_DAEMON = '1';
85
58
 
86
59
  const res = spawnSync('bash', [INSTALL], { stdio: 'inherit', env });
87
60
  if (res.error) { console.error(`ours-codex-install: ${res.error.message}`); process.exit(1); }
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/hooks/runner.mjs
4
- import { readFileSync as readFileSync2 } from "node:fs";
4
+ import { readFileSync } from "node:fs";
5
5
  import { readFile as readFile3 } from "node:fs/promises";
6
- import { homedir as homedir4 } from "node:os";
7
- import { dirname as dirname4, join as join4, resolve as resolve4 } from "node:path";
6
+ import { homedir as homedir2 } from "node:os";
7
+ import { dirname as dirname4, join as join3, resolve as resolve3 } from "node:path";
8
8
  import { pathToFileURL } from "node:url";
9
9
 
10
10
  // src/control-server.mjs
@@ -19,7 +19,7 @@ function encodeControlResponse(value) {
19
19
 
20
20
  // src/control-server.mjs
21
21
  function sendControlCommand(socketPath, capability, message, timeoutMs = 2e3) {
22
- return new Promise((resolve5, reject) => {
22
+ return new Promise((resolve4, reject) => {
23
23
  const socket = createConnection(socketPath);
24
24
  let buffer = "";
25
25
  const timer = setTimeout(() => {
@@ -40,7 +40,7 @@ function sendControlCommand(socketPath, capability, message, timeoutMs = 2e3) {
40
40
  try {
41
41
  const response = JSON.parse(buffer);
42
42
  if (!response.ok) reject(new Error(response.error || "monitor control failed"));
43
- else resolve5(response);
43
+ else resolve4(response);
44
44
  } catch (error) {
45
45
  reject(error);
46
46
  }
@@ -49,142 +49,38 @@ function sendControlCommand(socketPath, capability, message, timeoutMs = 2e3) {
49
49
  }
50
50
 
51
51
  // src/profile.mjs
52
- import { join as join3 } from "node:path";
53
- import { homedir as homedir3 } from "node:os";
54
52
  import { randomUUID as randomUUID3 } from "node:crypto";
55
- import { assertDaemonStateDir, attachOursClient as attachOursClient2, resolveDaemonConfig } from "@ours.network/sdk";
53
+ import { attachOursClient as attachOursClient2 } from "@ours.network/sdk";
56
54
 
57
55
  // ../core/src/host-profile.ts
58
- import { existsSync, lstatSync, readFileSync } from "node:fs";
59
- import { homedir } from "node:os";
60
- import { resolve } from "node:path";
61
- import { normalizeDaemonEndpoint } from "@ours.network/sdk/client";
62
- var PROFILE_KEYS = ["endpoint", "expectedInstanceId", "credentialPath"];
63
- var LEGACY_SELECTION_KEYS = ["port", "stateDir", "apiToken", "apiVisibility"];
64
- var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
65
- var CONFLICTING_ENV = ["OURS_API_TOKEN", "OURS_PORT", "OURS_STATE_DIR", "OURS_DAEMON_ID"];
66
- function profileError(reason) {
67
- return new Error(`Invalid external host profile: ${reason}`);
68
- }
69
- function validateHostProfile(value) {
70
- if (!value || typeof value !== "object" || Array.isArray(value)) {
71
- throw profileError("expected a complete host profile object.");
72
- }
73
- const record = value;
74
- const present = PROFILE_KEYS.filter((key) => Object.hasOwn(record, key));
75
- if (present.length !== PROFILE_KEYS.length) {
76
- throw profileError("expected a complete host profile tuple: endpoint, expectedInstanceId, credentialPath.");
77
- }
78
- const mixed = LEGACY_SELECTION_KEYS.filter((key) => Object.hasOwn(record, key));
79
- if (mixed.length) throw profileError(`legacy selection keys cannot be mixed with a host profile (${mixed.join(", ")}).`);
80
- const endpoint = record.endpoint;
81
- const expectedInstanceId = record.expectedInstanceId;
82
- const credentialPath = record.credentialPath;
83
- if (typeof endpoint !== "string" || endpoint.trim() !== endpoint || endpoint === "") {
84
- throw profileError("endpoint must be a non-empty HTTP or HTTPS base URL.");
85
- }
86
- if (typeof expectedInstanceId !== "string" || !UUID.test(expectedInstanceId)) {
87
- throw profileError("expectedInstanceId must be a lowercase UUID.");
88
- }
89
- if (typeof credentialPath !== "string" || credentialPath === "" || !credentialPath.startsWith("/") || resolve(credentialPath) !== credentialPath) {
90
- throw profileError("credentialPath must be a normalized absolute path.");
91
- }
92
- let url;
93
- try {
94
- url = new URL(endpoint);
95
- } catch {
96
- throw profileError("endpoint must be an HTTP or HTTPS base URL.");
97
- }
98
- if (url.protocol !== "http:" && url.protocol !== "https:" || url.username || url.password || /[\s\\?#]/.test(endpoint) || url.search || url.hash) {
99
- throw profileError("endpoint must be an HTTP or HTTPS base URL without credentials, query, or fragment.");
100
- }
101
- return { endpoint: normalizeDaemonEndpoint(endpoint), expectedInstanceId, credentialPath };
102
- }
103
- function readProfileObject(configPath) {
104
- let text;
105
- try {
106
- text = readFileSync(configPath, "utf8");
107
- } catch {
108
- throw new Error(`Cannot read host profile ${JSON.stringify(configPath)}.`);
109
- }
110
- let value;
111
- try {
112
- value = JSON.parse(text);
113
- } catch {
114
- throw profileError(`config ${JSON.stringify(configPath)} is not valid JSON.`);
115
- }
116
- if (!value || typeof value !== "object" || Array.isArray(value)) throw profileError(`config ${JSON.stringify(configPath)} must be an object.`);
117
- return value;
118
- }
119
- function assertPrivateRegularFile(configPath) {
120
- let stat;
121
- try {
122
- stat = lstatSync(configPath);
123
- } catch {
124
- throw new Error(`Cannot read host profile ${JSON.stringify(configPath)}.`);
125
- }
126
- if (!stat.isFile()) throw profileError(`config ${JSON.stringify(configPath)} must be a regular file.`);
127
- const currentUid = process.getuid?.();
128
- if (currentUid === void 0 || stat.uid !== currentUid) throw profileError(`config ${JSON.stringify(configPath)} must be owned by the current user.`);
129
- if ((stat.mode & 63) !== 0) throw profileError(`config ${JSON.stringify(configPath)} must have private permissions.`);
130
- }
131
- function readHostProfile(configPath) {
132
- const record = readProfileObject(configPath);
133
- const found = PROFILE_KEYS.filter((key) => Object.hasOwn(record, key));
134
- if (found.length === 0) return null;
135
- assertPrivateRegularFile(configPath);
136
- return validateHostProfile(record);
137
- }
138
- function nonempty(env, key) {
139
- return (env[key] ?? "").trim() !== "";
140
- }
56
+ import { readGatewayClientProfile, validateGatewayClientProfile } from "@ours.network/sdk/client";
57
+ var validateHostProfile = validateGatewayClientProfile;
141
58
  function hostProfileSelectionFromEnv(env = process.env) {
142
- const explicit = (env.OURS_CONFIG ?? "").trim();
143
- const managedPath = resolve(env.HOME || homedir(), ".ours-client", "profile.json");
144
- let configPath = explicit;
145
- if (!configPath) {
146
- try {
147
- lstatSync(managedPath);
148
- configPath = managedPath;
149
- } catch (error) {
150
- if (error.code !== "ENOENT") {
151
- throw new Error(`Cannot read host profile ${JSON.stringify(managedPath)}.`);
152
- }
153
- configPath = resolve(homedir(), ".ours", "config.json");
154
- if (!existsSync(configPath)) return null;
155
- }
156
- }
157
- const profile = readHostProfile(configPath);
158
- if (profile === null) {
159
- if (resolve(configPath) === managedPath) throw profileError("managed client config must contain a complete host profile tuple.");
160
- return null;
161
- }
162
- const conflicting = CONFLICTING_ENV.filter((key) => nonempty(env, key));
163
- if (conflicting.length) throw profileError(`${conflicting.join(", ")} conflicts with host-profile mode.`);
59
+ const { configPath, ...profile } = readGatewayClientProfile(env);
164
60
  return { profile, configPath };
165
61
  }
166
62
  function hostProfileFromEnv(env = process.env) {
167
- return hostProfileSelectionFromEnv(env)?.profile ?? null;
63
+ return hostProfileSelectionFromEnv(env).profile;
168
64
  }
169
65
 
170
66
  // ../core/src/native-session.ts
171
67
  import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
172
68
  import { chmod as chmod2, mkdir as mkdir2, readFile as readFile2, rename as rename2, unlink as unlink2, writeFile as writeFile2 } from "node:fs/promises";
173
- import { dirname as dirname2, join as join2, resolve as resolve3 } from "node:path";
69
+ import { dirname as dirname2, join as join2, resolve as resolve2 } from "node:path";
174
70
  import { attachOursClient } from "@ours.network/sdk";
175
71
 
176
72
  // ../core/src/application-identities.ts
177
73
  import { randomUUID } from "node:crypto";
178
74
  import { chmod, mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
179
- import { homedir as homedir2 } from "node:os";
180
- import { dirname, resolve as resolve2 } from "node:path";
75
+ import { homedir } from "node:os";
76
+ import { dirname, resolve } from "node:path";
181
77
  var APPLICATION_IDENTITIES_VERSION = 1;
182
78
  var APPLICATION_IDENTITIES_ENV = "OURS_MCP_CONFIG";
183
- var UUID2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
79
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
184
80
  var emptyConfig = () => ({ version: 1, daemons: {}, instances: {} });
185
81
  function applicationIdentityConfigPath(env = process.env) {
186
82
  const explicit = (env[APPLICATION_IDENTITIES_ENV] ?? "").trim();
187
- return resolve2(explicit || resolve2(homedir2(), ".ours-mcp", "config.json"));
83
+ return resolve(explicit || resolve(homedir(), ".ours-mcp", "config.json"));
188
84
  }
189
85
  function parseRows(raw, path, namespace, normalizeKey) {
190
86
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
@@ -215,9 +111,9 @@ function parseConfig(text, path) {
215
111
  if (record.version !== APPLICATION_IDENTITIES_VERSION) {
216
112
  throw new Error(`Unsupported ours-mcp application identity config version at ${path}: expected ${APPLICATION_IDENTITIES_VERSION}, found ${String(record.version)}.`);
217
113
  }
218
- const daemons = parseRows(record.daemons, path, "daemon", resolve2);
114
+ const daemons = parseRows(record.daemons, path, "daemon", resolve);
219
115
  const instances = record.instances === void 0 ? {} : parseRows(record.instances, path, "instance", (instanceId) => {
220
- if (!UUID2.test(instanceId)) throw new Error(`Invalid ours-mcp application identity config at ${path}: instance ${JSON.stringify(instanceId)} must be a lowercase UUID.`);
116
+ if (!UUID.test(instanceId)) throw new Error(`Invalid ours-mcp application identity config at ${path}: instance ${JSON.stringify(instanceId)} must be a lowercase UUID.`);
221
117
  return instanceId;
222
118
  });
223
119
  return { version: 1, daemons, instances };
@@ -253,14 +149,14 @@ var ApplicationIdentityStore = class {
253
149
  path;
254
150
  constructor(target, options = {}) {
255
151
  if (typeof target === "string") {
256
- this.stateDir = resolve2(target);
152
+ this.stateDir = resolve(target);
257
153
  this.instanceId = null;
258
154
  } else {
259
- if (!target || !UUID2.test(target.instanceId)) throw new Error("Application identity instanceId must be a lowercase UUID.");
155
+ if (!target || !UUID.test(target.instanceId)) throw new Error("Application identity instanceId must be a lowercase UUID.");
260
156
  this.stateDir = null;
261
157
  this.instanceId = target.instanceId;
262
158
  }
263
- this.path = resolve2(options.path ?? applicationIdentityConfigPath(options.env));
159
+ this.path = resolve(options.path ?? applicationIdentityConfigPath(options.env));
264
160
  }
265
161
  rows(config) {
266
162
  return this.instanceId === null ? config.daemons : config.instances;
@@ -300,7 +196,7 @@ var ApplicationIdentityStore = class {
300
196
 
301
197
  // ../core/src/session-guard.ts
302
198
  import { createHash } from "node:crypto";
303
- import { openSync, closeSync, lstatSync as lstatSync2, mkdirSync, chmodSync, constants } from "node:fs";
199
+ import { openSync, closeSync, lstatSync, mkdirSync, chmodSync, constants } from "node:fs";
304
200
  import { join } from "node:path";
305
201
  import { createRequire } from "node:module";
306
202
  var BUSY_TIMEOUT_MS = 5e3;
@@ -331,7 +227,7 @@ function ensureGuardFile(guardPath) {
331
227
  } catch (error) {
332
228
  if (error.code !== "EEXIST") throw error;
333
229
  }
334
- const stat = lstatSync2(guardPath);
230
+ const stat = lstatSync(guardPath);
335
231
  if (!stat.isFile() || stat.uid !== process.getuid() || (stat.mode & 63) !== 0 || stat.nlink !== 1) {
336
232
  throw new Error("Native session guard file must be an owner-only regular file");
337
233
  }
@@ -347,11 +243,11 @@ function acquireSessionGuard(recordPath) {
347
243
  const hash = createHash("sha256").update(recordPath).digest("hex");
348
244
  const guardPath = join(base, `${hash}.guard`);
349
245
  ensureGuardFile(guardPath);
350
- const before = lstatSync2(guardPath);
246
+ const before = lstatSync(guardPath);
351
247
  const Database = resolveDatabase();
352
248
  const db = new Database(guardPath);
353
249
  try {
354
- const after = lstatSync2(guardPath);
250
+ const after = lstatSync(guardPath);
355
251
  if (before.dev !== after.dev || before.ino !== after.ino || before.mode !== after.mode || before.uid !== after.uid) {
356
252
  throw new Error("Native session guard file changed during acquisition");
357
253
  }
@@ -373,7 +269,7 @@ function acquireSessionGuard(recordPath) {
373
269
 
374
270
  // ../core/src/native-session.ts
375
271
  var RECORD_VERSION = 1;
376
- var UUID3 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
272
+ var UUID2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
377
273
  var clients = /* @__PURE__ */ new Map();
378
274
  var operations = /* @__PURE__ */ new Map();
379
275
  function selector(value) {
@@ -383,10 +279,10 @@ function selector(value) {
383
279
  return value;
384
280
  }
385
281
  function nativeSessionRecordPath(hostRecordRoot, instanceId, nativeSessionId) {
386
- if (!hostRecordRoot || !hostRecordRoot.startsWith("/") || resolve3(hostRecordRoot) !== hostRecordRoot) {
282
+ if (!hostRecordRoot || !hostRecordRoot.startsWith("/") || resolve2(hostRecordRoot) !== hostRecordRoot) {
387
283
  throw new Error("Native session host record root must be a normalized absolute path.");
388
284
  }
389
- if (!UUID3.test(instanceId)) throw new Error("Native session daemon instance must be a lowercase UUID.");
285
+ if (!UUID2.test(instanceId)) throw new Error("Native session daemon instance must be a lowercase UUID.");
390
286
  const hash = createHash2("sha256").update(selector(nativeSessionId), "utf8").digest("hex");
391
287
  return join2(hostRecordRoot, "sessions", instanceId, `${hash}.json`);
392
288
  }
@@ -402,7 +298,7 @@ function parseRecord(text, path) {
402
298
  }
403
299
  const record = value;
404
300
  const keys = Object.keys(record).sort();
405
- if (keys.join(",") !== "ownerInstanceId,state,version" || record.version !== RECORD_VERSION || typeof record.ownerInstanceId !== "string" || !UUID3.test(record.ownerInstanceId) || !["active", "pending", "ended"].includes(String(record.state))) {
301
+ 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))) {
406
302
  throw new Error(`Invalid native session record at ${path}.`);
407
303
  }
408
304
  return record;
@@ -436,8 +332,8 @@ async function writeRecord(path, record) {
436
332
  async function serialized(key, operation) {
437
333
  const predecessor = operations.get(key) ?? Promise.resolve();
438
334
  let release;
439
- const gate = new Promise((resolve5) => {
440
- release = resolve5;
335
+ const gate = new Promise((resolve4) => {
336
+ release = resolve4;
441
337
  });
442
338
  const tail = predecessor.catch(() => {
443
339
  }).then(() => gate);
@@ -546,53 +442,18 @@ function parseOursArgs(argv = []) {
546
442
  async function resolveDaemonProfile({ argv = [], env = process.env, fetch: fetchImpl = globalThis.fetch, attach: attach2 = attachOursClient2 } = {}) {
547
443
  const parsed = parseOursArgs(argv);
548
444
  const hostSelection = hostProfileSelectionFromEnv(env);
549
- if (hostSelection !== null) {
550
- const { profile: hostProfile, configPath } = hostSelection;
551
- if (parsed.port != null) throw new Error("ours: --ours-port conflicts with host-profile mode");
552
- const client = await attach2({ ...hostProfile, sessionMode: "external", leaseToken: randomUUID3(), env: {} });
553
- let info2;
554
- try {
555
- info2 = await client.version();
556
- await client.identities();
557
- await client.unread();
558
- } finally {
559
- await client.close();
560
- }
561
- return { profile: hostProfile, info: info2, baseUrl: hostProfile.endpoint, configPath, codexArgs: parsed.codexArgs };
445
+ const { profile: hostProfile, configPath } = hostSelection;
446
+ if (parsed.port != null) throw new Error("ours: --ours-port conflicts with host-profile mode");
447
+ const client = await attach2({ ...hostProfile, sessionMode: "external", leaseToken: randomUUID3(), env: {} });
448
+ let info;
449
+ try {
450
+ info = await client.version();
451
+ await client.identities();
452
+ await client.unread();
453
+ } finally {
454
+ await client.close();
562
455
  }
563
- const selection = resolveDaemonConfig({
564
- ...parsed.port == null ? {} : { port: parsed.port },
565
- env,
566
- homeDir: env.HOME || homedir3()
567
- });
568
- await assertDaemonStateDir(selection, { fetch: fetchImpl, timeoutMs: 2e3 });
569
- const baseUrl = selection.baseUrl.value;
570
- const token = selection.token?.value ?? null;
571
- const headers = token ? { "x-ours-api-token": token } : {};
572
- const response = await fetchImpl(`${baseUrl}/info`, { signal: AbortSignal.timeout(2e3) });
573
- if (!response.ok) throw new Error(`ours daemon at ${baseUrl} returned HTTP ${response.status}`);
574
- const info = await response.json();
575
- if (info?.name !== "ours" || !Number.isInteger(info?.protocol) || info.protocol < 1) {
576
- throw new Error(`incompatible service at ${baseUrl}; expected an ours daemon with notification protocol 1`);
577
- }
578
- for (const path of ["/identities", "/unread"]) {
579
- const capability = await fetchImpl(`${baseUrl}${path}`, { headers, signal: AbortSignal.timeout(2e3) });
580
- if (capability.status === 401 || capability.status === 403) {
581
- throw new Error(`ours daemon authentication failed at ${baseUrl}; supply the matching coherent selection`);
582
- }
583
- if (!capability.ok) throw new Error(`selected daemon lacks ${path} (HTTP ${capability.status})`);
584
- }
585
- return {
586
- port: selection.port.value,
587
- stateDir: selection.expectStateDir,
588
- token,
589
- visibility: "owner",
590
- source: selection.baseUrl.source,
591
- info,
592
- baseUrl,
593
- configPath: selection.configPath?.value ?? join3(selection.expectStateDir, "config.json"),
594
- codexArgs: parsed.codexArgs
595
- };
456
+ return { profile: hostProfile, info, baseUrl: hostProfile.endpoint, configPath, codexArgs: parsed.codexArgs };
596
457
  }
597
458
 
598
459
  // src/host-hooks.mjs
@@ -622,10 +483,10 @@ async function readHostHookState({ profile, nativeSessionId, applicationPath, cl
622
483
  // src/hooks/runner.mjs
623
484
  import { attachOursClient as attachOursClient3 } from "@ours.network/sdk";
624
485
  async function defaultFindPin(cwd) {
625
- let dir = resolve4(cwd || process.cwd());
486
+ let dir = resolve3(cwd || process.cwd());
626
487
  for (; ; ) {
627
488
  try {
628
- const value = JSON.parse(await readFile3(join4(dir, ".ours-identity"), "utf8"));
489
+ const value = JSON.parse(await readFile3(join3(dir, ".ours-identity"), "utf8"));
629
490
  if (typeof value.identity === "string" && value.identity.trim()) return value;
630
491
  return null;
631
492
  } catch {
@@ -635,21 +496,6 @@ async function defaultFindPin(cwd) {
635
496
  dir = parent;
636
497
  }
637
498
  }
638
- var safeUnread = (value) => (Array.isArray(value?.identities) ? value.identities : []).flatMap((entry) => {
639
- if (!entry || typeof entry.name !== "string") return [];
640
- const count = Number.isSafeInteger(entry.count) ? entry.count : 0;
641
- const files = Number.isSafeInteger(entry.files) ? entry.files : 0;
642
- if (count <= 0 && files <= 0) return [];
643
- const recent = Array.isArray(entry.recent) ? entry.recent.slice(-5).flatMap((m) => m && typeof m.from === "string" ? [{ from: m.from, msg_id: String(m.msg_id ?? "?"), date: typeof m.date === "string" ? m.date : "" }] : []) : [];
644
- return [{ name: entry.name, count, files, recent }];
645
- });
646
- async function applicationIdentityNames(env, selection) {
647
- const path = env.OURS_MCP_CONFIG || join4(homedir4(), ".ours-mcp", "config.json");
648
- const value = JSON.parse(await readFile3(path, "utf8"));
649
- if (value?.version !== 1 || !value.daemons || typeof value.daemons !== "object") return /* @__PURE__ */ new Set();
650
- const identities = selection.instanceId ? value.instances?.[selection.instanceId]?.identities : value.daemons[resolve4(selection.stateDir)]?.identities;
651
- return new Set(Array.isArray(identities) ? identities.filter((name) => typeof name === "string") : []);
652
- }
653
499
  function renderContext(unread, pin) {
654
500
  const lines = [];
655
501
  if (unread.length) {
@@ -682,9 +528,6 @@ async function handleHook(payload, { env = process.env, fetch: fetchImpl = globa
682
528
  if (event === "SessionStart" && socket && capability && payload.session_id && payload.cwd) {
683
529
  await send(socket, capability, { command: "register_session", sessionId: payload.session_id, threadId: payload.session_id, cwd: payload.cwd });
684
530
  }
685
- let port = env.OURS_PORT || "3050";
686
- let selectedStateDir = resolve4(env.OURS_STATE_DIR || join4(homedir4(), ".ours"));
687
- let selectedToken = env.OURS_API_TOKEN || "";
688
531
  let hostProfile;
689
532
  try {
690
533
  hostProfile = hostProfileFromEnv(env);
@@ -694,46 +537,13 @@ async function handleHook(payload, { env = process.env, fetch: fetchImpl = globa
694
537
  if (hostProfile) {
695
538
  const nativeSessionId = payload.session_id;
696
539
  if (typeof nativeSessionId !== "string" || !nativeSessionId) return { continue: true };
697
- const appPath = env.OURS_MCP_CONFIG || join4(env.HOME || homedir4(), ".ours-mcp", "config.json");
540
+ const appPath = env.OURS_MCP_CONFIG || join3(env.HOME || homedir2(), ".ours-mcp", "config.json");
698
541
  const state = await readHostState({ profile: hostProfile, nativeSessionId, applicationPath: appPath });
699
- const pin2 = await findPin(payload.cwd || process.cwd());
700
- const context2 = renderContext(state.unread.identities, pin2);
701
- return context2 ? { continue: true, hookSpecificOutput: { hookEventName: event, additionalContext: context2 } } : { continue: true };
542
+ const pin = await findPin(payload.cwd || process.cwd());
543
+ const context = renderContext(state.unread.identities, pin);
544
+ return context ? { continue: true, hookSpecificOutput: { hookEventName: event, additionalContext: context } } : { continue: true };
702
545
  }
703
- try {
704
- const selected = await profileResolver({ env, fetch: fetchImpl });
705
- if (selected.profile) {
706
- let client;
707
- try {
708
- client = await clientFactory({ ...selected.profile, sessionMode: "external", leaseToken: `codex-hook-${process.pid}`, env: {} });
709
- const visible = await applicationIdentityNames(env, { instanceId: selected.profile.expectedInstanceId });
710
- const unread2 = safeUnread(await client.unread()).filter((entry) => visible.has(entry.name));
711
- const pin2 = await findPin(payload.cwd || process.cwd());
712
- const context2 = renderContext(unread2, pin2);
713
- return context2 ? { continue: true, hookSpecificOutput: { hookEventName: event, additionalContext: context2 } } : { continue: true };
714
- } finally {
715
- await client?.close();
716
- }
717
- }
718
- port = String(selected.port);
719
- selectedToken = selected.token || "";
720
- selectedStateDir = resolve4(selected.stateDir);
721
- } catch {
722
- }
723
- const headers = selectedToken ? { "x-ours-api-token": selectedToken } : {};
724
- let unread = [];
725
- try {
726
- const response = await fetchImpl(`http://127.0.0.1:${port}/unread`, { headers, signal: AbortSignal.timeout(1500) });
727
- if (response.ok) {
728
- const visible = await applicationIdentityNames(env, { stateDir: selectedStateDir });
729
- unread = safeUnread(await response.json()).filter((entry) => visible.has(entry.name));
730
- }
731
- } catch {
732
- }
733
- const pin = await findPin(payload.cwd || process.cwd());
734
- const context = renderContext(unread, pin);
735
- if (!context) return { continue: true };
736
- return { continue: true, hookSpecificOutput: { hookEventName: event, additionalContext: context } };
546
+ return { continue: true };
737
547
  } catch {
738
548
  return { continue: true };
739
549
  }
@@ -741,7 +551,7 @@ async function handleHook(payload, { env = process.env, fetch: fetchImpl = globa
741
551
  async function main() {
742
552
  let payload = {};
743
553
  try {
744
- payload = JSON.parse(readFileSync2(0, "utf8"));
554
+ payload = JSON.parse(readFileSync(0, "utf8"));
745
555
  } catch {
746
556
  }
747
557
  process.stdout.write(JSON.stringify(await handleHook(payload)));