@ours.network/codex 0.18.0-nightly.5 → 0.18.0-nightly.6

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": "0.18.0-nightly.5",
3
+ "version": "0.18.0-nightly.6",
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/.mcp.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "command": "node",
5
5
  "args": ["bin/proxy.mjs"],
6
6
  "cwd": ".",
7
- "env_vars": ["OURS_PORT", "OURS_CONFIG", "OURS_API_TOKEN", "OURS_STATE_DIR", "OURS_AUTOSTART", "OURS_CODEX_LIVE"]
7
+ "env_vars": ["OURS_PORT", "OURS_CONFIG", "OURS_API_TOKEN", "OURS_STATE_DIR", "OURS_AUTOSTART", "OURS_CODEX_LIVE", "OURS_CLIENT_PID"]
8
8
  },
9
9
  "ours_monitor": {
10
10
  "command": "node",
package/README.md CHANGED
@@ -77,6 +77,9 @@ lifecycle directly, while the hooks add standard-mode context and defensive stat
77
77
  - `SessionStart` surfaces body-free unread metadata and an advisory `.ours-identity` pin.
78
78
  - `UserPromptSubmit` can re-surface unresolved unread/pin context.
79
79
  - `PostToolUse` records successful identity bindings and disarms on a switch.
80
+ - `SessionEnd` releases the session lease and waits for all session-owned temporary
81
+ roles to send best-effort removal notices and delete local state. Live mode also
82
+ invokes this cleanup directly when its TUI exits, so it does not depend on hook trust.
80
83
 
81
84
  Codex requires review and trust of the exact hook definitions before running them.
82
85
  Installation does not bypass hook trust, and live monitoring remains available when the
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { readFileSync, writeFileSync, existsSync, renameSync, rmSync } from 'node:fs';
2
+ import { readFileSync, writeFileSync, existsSync, renameSync, realpathSync } from 'node:fs';
3
3
  import { resolve, join } from 'node:path';
4
4
  import { homedir } from 'node:os';
5
+ import { fileURLToPath } from 'node:url';
5
6
 
6
7
  const codexDir = resolve(process.env.CODEX_DIR || process.env.CODEX_HOME || join(homedir(), '.codex'));
7
8
  const skillsDir = resolve(process.env.SKILLS_DIR || join(homedir(), '.agents', 'skills'));
@@ -42,13 +43,22 @@ function stripOrphanedMcpConfig(path) {
42
43
  return true;
43
44
  }
44
45
 
45
- const configPath = join(codexDir, 'config.toml');
46
- if (!stripManaged(configPath, '# >>> ours.network plugin', '# <<< ours.network plugin')) stripOrphanedMcpConfig(configPath);
47
- stripManaged(join(codexDir, 'AGENTS.md'), '<!-- >>> ours.network plugin', '<!-- <<< ours.network plugin -->');
46
+ export function backupLegacySkill(path, backup, rename = renameSync) {
47
+ if (!existsSync(path)) return false;
48
+ rename(path, backup);
49
+ return true;
50
+ }
48
51
 
49
- for (const name of ['ours', 'writing-agent-bios']) {
50
- const path = join(skillsDir, name);
51
- if (!existsSync(path)) continue;
52
- const backup = `${path}.ours-legacy-${stamp}`;
53
- try { renameSync(path, backup); } catch { rmSync(path, { recursive: true, force: true }); }
52
+ function main() {
53
+ const configPath = join(codexDir, 'config.toml');
54
+ if (!stripManaged(configPath, '# >>> ours.network plugin', '# <<< ours.network plugin')) stripOrphanedMcpConfig(configPath);
55
+ stripManaged(join(codexDir, 'AGENTS.md'), '<!-- >>> ours.network plugin', '<!-- <<< ours.network plugin -->');
56
+
57
+ for (const name of ['ours', 'writing-agent-bios']) {
58
+ const path = join(skillsDir, name);
59
+ backupLegacySkill(path, `${path}.ours-legacy-${stamp}`);
60
+ }
54
61
  }
62
+
63
+ const invokedDirectly = process.argv[1] && realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
64
+ if (invokedDirectly) main();
package/bin/proxy.mjs CHANGED
@@ -22,9 +22,12 @@ if (!cliPath) {
22
22
  }
23
23
  const env = { ...process.env };
24
24
  if (env.OURS_CODEX_LIVE === '1') env.OURS_AUTOSTART = '0';
25
- if (process.ppid > 1) env.OURS_CLIENT_PID = String(process.ppid);
25
+ if (!env.OURS_CLIENT_PID && process.ppid > 1) env.OURS_CLIENT_PID = String(process.ppid);
26
+ const sessionEnd = process.argv[2] === 'session-end';
27
+ const command = sessionEnd ? 'session-end' : 'proxy';
28
+ const forwarded = process.argv.slice(sessionEnd ? 3 : 2);
26
29
  const child = cliPath
27
- ? spawn(process.execPath, [cliPath, 'proxy', ...process.argv.slice(2)], { stdio: 'inherit', env })
28
- : spawn('ours-mcp', ['proxy', ...process.argv.slice(2)], { stdio: 'inherit', env });
30
+ ? spawn(process.execPath, [cliPath, command, ...forwarded], { stdio: 'inherit', env })
31
+ : spawn('ours-mcp', [command, ...forwarded], { stdio: 'inherit', env });
29
32
  child.on('error', (error) => { process.stderr.write(`ours: cannot launch @ours.network/mcp proxy: ${error.message}\n`); process.exit(1); });
30
33
  child.on('exit', (code, signal) => { if (signal) process.kill(process.pid, signal); else process.exit(code ?? 0); });
@@ -3106,7 +3106,25 @@ var require_utils = __commonJS({
3106
3106
  var isIPv4 = RegExp.prototype.test.bind(/^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/u);
3107
3107
  var isHexPair = RegExp.prototype.test.bind(/^[\da-f]{2}$/iu);
3108
3108
  var isUnreserved = RegExp.prototype.test.bind(/^[\da-z\-._~]$/iu);
3109
- var isPathCharacter = RegExp.prototype.test.bind(/^[\da-z\-._~!$&'()*+,;=:@/]$/iu);
3109
+ var isPathCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=:@/]$/u);
3110
+ var isQueryFragmentCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=:@/?]$/u);
3111
+ var isUserinfoCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=:]$/u);
3112
+ var BYTE_HEX = new Array(256);
3113
+ {
3114
+ const HEX_DIGITS = "0123456789ABCDEF";
3115
+ for (let i = 0; i < 256; i++) {
3116
+ BYTE_HEX[i] = "%" + HEX_DIGITS[i >> 4] + HEX_DIGITS[i & 15];
3117
+ }
3118
+ }
3119
+ function percentEncodeNonAscii(cp) {
3120
+ if (cp < 2048) {
3121
+ return BYTE_HEX[192 | cp >> 6] + BYTE_HEX[128 | cp & 63];
3122
+ }
3123
+ if (cp < 65536) {
3124
+ return BYTE_HEX[224 | cp >> 12] + BYTE_HEX[128 | cp >> 6 & 63] + BYTE_HEX[128 | cp & 63];
3125
+ }
3126
+ return BYTE_HEX[240 | cp >> 18] + BYTE_HEX[128 | cp >> 12 & 63] + BYTE_HEX[128 | cp >> 6 & 63] + BYTE_HEX[128 | cp & 63];
3127
+ }
3110
3128
  function stringArrayToHexStripped(input) {
3111
3129
  let acc = "";
3112
3130
  let code = 0;
@@ -3131,91 +3149,105 @@ var require_utils = __commonJS({
3131
3149
  }
3132
3150
  return acc;
3133
3151
  }
3152
+ var isHextet = RegExp.prototype.test.bind(/^[\dA-Fa-f]{1,4}$/);
3153
+ var isIPvFuture = RegExp.prototype.test.bind(/^[vV][\dA-Fa-f]+\.[A-Za-z\d\-._~!$&'()*+,;=:]+$/);
3154
+ var isZoneCharacter = RegExp.prototype.test.bind(/^[A-Za-z\d\-._~]$/);
3134
3155
  var nonSimpleDomain = RegExp.prototype.test.bind(/[^!"$&'()*+,\-.;=_`a-z{}~]/u);
3135
- function consumeIsZone(buffer) {
3136
- buffer.length = 0;
3137
- return true;
3138
- }
3139
- function consumeHextets(buffer, address, output) {
3140
- if (buffer.length) {
3141
- const hex = stringArrayToHexStripped(buffer);
3142
- if (hex !== "") {
3143
- address.push(hex);
3144
- } else {
3145
- output.error = true;
3146
- return false;
3156
+ function isZoneIdentifier(zone) {
3157
+ if (zone.length === 0) return false;
3158
+ for (let i = 0; i < zone.length; i++) {
3159
+ if (isZoneCharacter(zone[i])) continue;
3160
+ if (zone[i] === "%" && i + 2 < zone.length && isHexPair(zone.slice(i + 1, i + 3))) {
3161
+ i += 2;
3162
+ continue;
3147
3163
  }
3148
- buffer.length = 0;
3164
+ return false;
3149
3165
  }
3150
3166
  return true;
3151
3167
  }
3152
- function getIPV6(input) {
3153
- let tokenCount = 0;
3154
- const output = { error: false, address: "", zone: "" };
3155
- const address = [];
3156
- const buffer = [];
3157
- let endipv6Encountered = false;
3158
- let endIpv6 = false;
3159
- let consume = consumeHextets;
3160
- for (let i = 0; i < input.length; i++) {
3161
- const cursor = input[i];
3162
- if (cursor === "[" || cursor === "]") {
3163
- continue;
3164
- }
3165
- if (cursor === ":") {
3166
- if (endipv6Encountered === true) {
3167
- endIpv6 = true;
3168
- }
3169
- if (!consume(buffer, address, output)) {
3170
- break;
3171
- }
3172
- if (++tokenCount > 7) {
3173
- output.error = true;
3174
- break;
3168
+ function compressIPv6ZeroRun(hextets) {
3169
+ let bestStart = -1;
3170
+ let bestLength = 0;
3171
+ let runStart = -1;
3172
+ let runLength = 0;
3173
+ for (let i = 0; i < hextets.length; i++) {
3174
+ if (hextets[i] === "0") {
3175
+ if (runStart === -1) runStart = i;
3176
+ runLength++;
3177
+ if (runLength > bestLength) {
3178
+ bestLength = runLength;
3179
+ bestStart = runStart;
3175
3180
  }
3176
- if (i > 0 && input[i - 1] === ":") {
3177
- endipv6Encountered = true;
3178
- }
3179
- address.push(":");
3180
- continue;
3181
- } else if (cursor === "%") {
3182
- if (!consume(buffer, address, output)) {
3183
- break;
3184
- }
3185
- consume = consumeIsZone;
3186
3181
  } else {
3187
- buffer.push(cursor);
3182
+ runStart = -1;
3183
+ runLength = 0;
3184
+ }
3185
+ }
3186
+ if (bestLength < 2) return hextets.join(":");
3187
+ const head = hextets.slice(0, bestStart).join(":");
3188
+ const tail = hextets.slice(bestStart + bestLength).join(":");
3189
+ return head + "::" + tail;
3190
+ }
3191
+ function normalizeIPv6Address(input) {
3192
+ const compression = input.indexOf("::");
3193
+ if (compression !== -1 && input.indexOf("::", compression + 1) !== -1) return void 0;
3194
+ const left = compression === -1 ? input.split(":") : input.slice(0, compression).split(":");
3195
+ const right = compression === -1 ? [] : input.slice(compression + 2).split(":");
3196
+ if (compression !== -1) {
3197
+ if (left.length === 1 && left[0] === "") left.length = 0;
3198
+ if (right.length === 1 && right[0] === "") right.length = 0;
3199
+ }
3200
+ const parts = left.concat(right);
3201
+ let hextetCount = 0;
3202
+ for (let i = 0; i < parts.length; i++) {
3203
+ const part = parts[i];
3204
+ if (part === "") return void 0;
3205
+ if (part.indexOf(".") !== -1) {
3206
+ if (i !== parts.length - 1 || compression !== -1 && right.length === 0 || !isIPv4(part)) return void 0;
3207
+ hextetCount += 2;
3188
3208
  continue;
3189
3209
  }
3210
+ if (!isHextet(part)) return void 0;
3211
+ parts[i] = parseInt(part, 16).toString(16);
3212
+ hextetCount++;
3190
3213
  }
3191
- if (buffer.length) {
3192
- if (consume === consumeIsZone) {
3193
- output.zone = buffer.join("");
3194
- } else if (endIpv6) {
3195
- address.push(buffer.join(""));
3196
- } else {
3197
- address.push(stringArrayToHexStripped(buffer));
3198
- }
3214
+ if (compression === -1) {
3215
+ if (hextetCount !== 8) return void 0;
3216
+ return compressIPv6ZeroRun(parts);
3199
3217
  }
3200
- output.address = address.join("");
3201
- return output;
3218
+ if (hextetCount >= 8) return void 0;
3219
+ const expanded = parts.slice(0, left.length);
3220
+ for (let i = hextetCount; i < 8; i++) expanded.push("0");
3221
+ for (let i = left.length; i < parts.length; i++) expanded.push(parts[i]);
3222
+ return compressIPv6ZeroRun(expanded);
3202
3223
  }
3203
3224
  function normalizeIPv6(host) {
3204
- if (findToken(host, ":") < 2) {
3205
- return { host, isIPV6: false };
3206
- }
3207
- const ipv62 = getIPV6(host);
3208
- if (!ipv62.error) {
3209
- let newHost = ipv62.address;
3210
- let escapedHost = ipv62.address;
3211
- if (ipv62.zone) {
3212
- newHost += "%" + ipv62.zone;
3213
- escapedHost += "%25" + ipv62.zone;
3214
- }
3215
- return { host: newHost, isIPV6: true, escapedHost };
3216
- } else {
3217
- return { host, isIPV6: false };
3218
- }
3225
+ const bracketed = host[0] === "[" && host[host.length - 1] === "]";
3226
+ const hasBracket = host[0] === "[" || host[host.length - 1] === "]";
3227
+ if (hasBracket && !bracketed) return { host, isIPV6: false, error: true };
3228
+ let input = bracketed ? host.slice(1, -1) : host;
3229
+ if (bracketed && isIPvFuture(input)) {
3230
+ input = input.toLowerCase();
3231
+ return { host: `[${input}]`, escapedHost: input, isIPV6: false, isIPVFuture: true };
3232
+ }
3233
+ if (findToken(input, ":") < 2) {
3234
+ return { host, isIPV6: false, error: bracketed };
3235
+ }
3236
+ let zoneIdentifier = "";
3237
+ const zoneSeparator = input.indexOf("%");
3238
+ if (zoneSeparator !== -1) {
3239
+ const separatorLength = input.slice(zoneSeparator, zoneSeparator + 3).toLowerCase() === "%25" ? 3 : 1;
3240
+ zoneIdentifier = input.slice(zoneSeparator + separatorLength);
3241
+ if (!isZoneIdentifier(zoneIdentifier)) return { host, isIPV6: false, error: true };
3242
+ input = input.slice(0, zoneSeparator);
3243
+ }
3244
+ const address = normalizeIPv6Address(input);
3245
+ if (address === void 0) return { host, isIPV6: false, error: true };
3246
+ return {
3247
+ host: address + (zoneIdentifier ? "%" + zoneIdentifier : ""),
3248
+ escapedHost: address + (zoneIdentifier ? "%25" + zoneIdentifier : ""),
3249
+ isIPV6: true
3250
+ };
3219
3251
  }
3220
3252
  function findToken(str, token) {
3221
3253
  let ind = 0;
@@ -3334,7 +3366,8 @@ var require_utils = __commonJS({
3334
3366
  function normalizePathEncoding(input) {
3335
3367
  let output = "";
3336
3368
  for (let i = 0; i < input.length; i++) {
3337
- if (input[i] === "%" && i + 2 < input.length) {
3369
+ const ch = input[i];
3370
+ if (ch === "%" && i + 2 < input.length) {
3338
3371
  const hex = input.slice(i + 1, i + 3);
3339
3372
  if (isHexPair(hex)) {
3340
3373
  const normalizedHex = hex.toUpperCase();
@@ -3348,10 +3381,152 @@ var require_utils = __commonJS({
3348
3381
  continue;
3349
3382
  }
3350
3383
  }
3351
- if (isPathCharacter(input[i])) {
3352
- output += input[i];
3384
+ if (isPathCharacter(ch)) {
3385
+ output += ch;
3386
+ } else {
3387
+ const code = input.charCodeAt(i);
3388
+ if (code < 128) {
3389
+ output += isEscapeSafe(code) ? ch : BYTE_HEX[code];
3390
+ } else if (code < 55296 || code > 57343) {
3391
+ output += percentEncodeNonAscii(code);
3392
+ } else if (code <= 56319 && i + 1 < input.length) {
3393
+ const low = input.charCodeAt(i + 1);
3394
+ if (low >= 56320 && low <= 57343) {
3395
+ output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
3396
+ i++;
3397
+ } else {
3398
+ output += percentEncodeNonAscii(65533);
3399
+ }
3400
+ } else {
3401
+ output += percentEncodeNonAscii(65533);
3402
+ }
3403
+ }
3404
+ }
3405
+ return output;
3406
+ }
3407
+ function serializePathEncoding(input, pathNoScheme = false) {
3408
+ let output = "";
3409
+ let firstSegment = pathNoScheme && input[0] !== "/";
3410
+ for (let i = 0; i < input.length; i++) {
3411
+ const ch = input[i];
3412
+ if (ch === "%" && i + 2 < input.length) {
3413
+ const hex = input.slice(i + 1, i + 3);
3414
+ if (isHexPair(hex)) {
3415
+ output += "%" + hex.toUpperCase();
3416
+ i += 2;
3417
+ continue;
3418
+ }
3419
+ }
3420
+ if (ch === "/") {
3421
+ firstSegment = false;
3422
+ }
3423
+ if (isPathCharacter(ch) && (ch !== ":" || !firstSegment)) {
3424
+ output += ch;
3425
+ } else {
3426
+ const code = input.charCodeAt(i);
3427
+ if (code < 128) {
3428
+ output += BYTE_HEX[code];
3429
+ } else if (code < 55296 || code > 57343) {
3430
+ output += percentEncodeNonAscii(code);
3431
+ } else if (code <= 56319 && i + 1 < input.length) {
3432
+ const low = input.charCodeAt(i + 1);
3433
+ if (low >= 56320 && low <= 57343) {
3434
+ output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
3435
+ i++;
3436
+ } else {
3437
+ output += percentEncodeNonAscii(65533);
3438
+ }
3439
+ } else {
3440
+ output += percentEncodeNonAscii(65533);
3441
+ }
3442
+ }
3443
+ }
3444
+ return output;
3445
+ }
3446
+ function encodeComponent(input, isAllowed) {
3447
+ let output = "";
3448
+ for (let i = 0; i < input.length; i++) {
3449
+ const ch = input[i];
3450
+ if (ch === "%" && i + 2 < input.length) {
3451
+ const hex = input.slice(i + 1, i + 3);
3452
+ if (isHexPair(hex)) {
3453
+ output += "%" + hex.toUpperCase();
3454
+ i += 2;
3455
+ continue;
3456
+ }
3457
+ }
3458
+ if (isAllowed(ch)) {
3459
+ output += ch;
3353
3460
  } else {
3354
- output += escape(input[i]);
3461
+ const code = input.charCodeAt(i);
3462
+ if (code < 128) {
3463
+ output += BYTE_HEX[code];
3464
+ } else if (code < 55296 || code > 57343) {
3465
+ output += percentEncodeNonAscii(code);
3466
+ } else if (code <= 56319 && i + 1 < input.length) {
3467
+ const low = input.charCodeAt(i + 1);
3468
+ if (low >= 56320 && low <= 57343) {
3469
+ output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
3470
+ i++;
3471
+ } else {
3472
+ output += percentEncodeNonAscii(65533);
3473
+ }
3474
+ } else {
3475
+ output += percentEncodeNonAscii(65533);
3476
+ }
3477
+ }
3478
+ }
3479
+ return output;
3480
+ }
3481
+ function encodeUserinfo(input) {
3482
+ return encodeComponent(input, isUserinfoCharacter);
3483
+ }
3484
+ function encodeQuery(input) {
3485
+ return encodeComponent(input, isQueryFragmentCharacter);
3486
+ }
3487
+ function encodeFragment(input) {
3488
+ return encodeComponent(input, isQueryFragmentCharacter);
3489
+ }
3490
+ function isEscapeSafe(cp) {
3491
+ return cp >= 48 && cp <= 57 || cp >= 65 && cp <= 90 || cp >= 97 && cp <= 122 || cp === 42 || cp === 43 || cp === 45 || cp === 46 || cp === 47 || cp === 64 || cp === 95;
3492
+ }
3493
+ function normalizeQueryFragmentEncoding(input) {
3494
+ let output = "";
3495
+ for (let i = 0; i < input.length; i++) {
3496
+ const ch = input[i];
3497
+ if (ch === "%" && i + 2 < input.length) {
3498
+ const hex = input.slice(i + 1, i + 3);
3499
+ if (isHexPair(hex)) {
3500
+ const normalizedHex = hex.toUpperCase();
3501
+ const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
3502
+ if (isUnreserved(decoded)) {
3503
+ output += decoded;
3504
+ } else {
3505
+ output += "%" + normalizedHex;
3506
+ }
3507
+ i += 2;
3508
+ continue;
3509
+ }
3510
+ }
3511
+ if (isQueryFragmentCharacter(ch)) {
3512
+ output += ch;
3513
+ } else {
3514
+ const code = input.charCodeAt(i);
3515
+ if (code < 128) {
3516
+ output += isEscapeSafe(code) ? ch : BYTE_HEX[code];
3517
+ } else if (code < 55296 || code > 57343) {
3518
+ output += percentEncodeNonAscii(code);
3519
+ } else if (code <= 56319 && i + 1 < input.length) {
3520
+ const low = input.charCodeAt(i + 1);
3521
+ if (low >= 56320 && low <= 57343) {
3522
+ output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
3523
+ i++;
3524
+ } else {
3525
+ output += percentEncodeNonAscii(65533);
3526
+ }
3527
+ } else {
3528
+ output += percentEncodeNonAscii(65533);
3529
+ }
3355
3530
  }
3356
3531
  }
3357
3532
  return output;
@@ -3374,14 +3549,18 @@ var require_utils = __commonJS({
3374
3549
  function recomposeAuthority(component) {
3375
3550
  const uriTokens = [];
3376
3551
  if (component.userinfo !== void 0) {
3377
- uriTokens.push(component.userinfo);
3552
+ uriTokens.push(encodeUserinfo(component.userinfo));
3378
3553
  uriTokens.push("@");
3379
3554
  }
3380
3555
  if (component.host !== void 0) {
3381
- let host = unescape(component.host);
3556
+ let host = component.host;
3382
3557
  if (!isIPv4(host)) {
3383
- const ipV6res = normalizeIPv6(host);
3384
- if (ipV6res.isIPV6 === true) {
3558
+ let ipV6res = normalizeIPv6(host);
3559
+ if (ipV6res.isIPV6 !== true && ipV6res.isIPVFuture !== true) {
3560
+ host = normalizePercentEncoding(host, true);
3561
+ ipV6res = normalizeIPv6(host);
3562
+ }
3563
+ if (ipV6res.isIPV6 === true || ipV6res.isIPVFuture === true) {
3385
3564
  host = `[${ipV6res.escapedHost}]`;
3386
3565
  } else {
3387
3566
  host = reescapeHostDelimiters(host, false);
@@ -3401,6 +3580,11 @@ var require_utils = __commonJS({
3401
3580
  reescapeHostDelimiters,
3402
3581
  normalizePercentEncoding,
3403
3582
  normalizePathEncoding,
3583
+ serializePathEncoding,
3584
+ normalizeQueryFragmentEncoding,
3585
+ encodeUserinfo,
3586
+ encodeQuery,
3587
+ encodeFragment,
3404
3588
  escapePreservingEscapes,
3405
3589
  removeDotSegments,
3406
3590
  isIPv4,
@@ -3416,7 +3600,7 @@ var require_schemes = __commonJS({
3416
3600
  "../../node_modules/fast-uri/lib/schemes.js"(exports, module) {
3417
3601
  "use strict";
3418
3602
  var { isUUID } = require_utils();
3419
- var URN_REG = /([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-.:;=@]|%[\da-f]{2})+)/iu;
3603
+ var URN_REG = /^([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-./:;=@]|%[\da-f]{2})+)$/iu;
3420
3604
  var supportedSchemeNames = (
3421
3605
  /** @type {const} */
3422
3606
  [
@@ -3477,9 +3661,10 @@ var require_schemes = __commonJS({
3477
3661
  wsComponent.secure = void 0;
3478
3662
  }
3479
3663
  if (wsComponent.resourceName) {
3480
- const [path, query] = wsComponent.resourceName.split("?");
3664
+ const queryIndex = wsComponent.resourceName.indexOf("?");
3665
+ const path = queryIndex === -1 ? wsComponent.resourceName : wsComponent.resourceName.slice(0, queryIndex);
3481
3666
  wsComponent.path = path && path !== "/" ? path : void 0;
3482
- wsComponent.query = query;
3667
+ wsComponent.query = queryIndex === -1 ? void 0 : wsComponent.resourceName.slice(queryIndex + 1);
3483
3668
  wsComponent.resourceName = void 0;
3484
3669
  }
3485
3670
  wsComponent.fragment = void 0;
@@ -3491,7 +3676,7 @@ var require_schemes = __commonJS({
3491
3676
  return urnComponent;
3492
3677
  }
3493
3678
  const matches = urnComponent.path.match(URN_REG);
3494
- if (matches) {
3679
+ if (matches && matches[0] === urnComponent.path) {
3495
3680
  const scheme = options.scheme || urnComponent.scheme || "urn";
3496
3681
  urnComponent.nid = matches[1].toLowerCase();
3497
3682
  urnComponent.nss = matches[2];
@@ -3625,8 +3810,17 @@ var require_schemes = __commonJS({
3625
3810
  var require_fast_uri = __commonJS({
3626
3811
  "../../node_modules/fast-uri/index.js"(exports, module) {
3627
3812
  "use strict";
3628
- var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
3813
+ var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, serializePathEncoding, normalizeQueryFragmentEncoding, encodeQuery, encodeFragment, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
3629
3814
  var { SCHEMES, getSchemeHandler } = require_schemes();
3815
+ var VALID_SCHEME = /^[A-Za-z][A-Za-z0-9+.-]*$/u;
3816
+ var MALFORMED_SCHEME_ERROR = "URI scheme is malformed.";
3817
+ function decodeValidScheme(scheme) {
3818
+ const decodedScheme = unescape(String(scheme));
3819
+ if (!VALID_SCHEME.test(decodedScheme)) {
3820
+ throw new TypeError(MALFORMED_SCHEME_ERROR);
3821
+ }
3822
+ return decodedScheme;
3823
+ }
3630
3824
  function normalize(uri, options) {
3631
3825
  if (typeof uri === "string") {
3632
3826
  uri = /** @type {T} */
@@ -3639,7 +3833,34 @@ var require_fast_uri = __commonJS({
3639
3833
  }
3640
3834
  function resolve(baseURI, relativeURI, options) {
3641
3835
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
3642
- const resolved = resolveComponent(parse3(baseURI, schemelessOptions), parse3(relativeURI, schemelessOptions), schemelessOptions, true);
3836
+ const {
3837
+ parsed: baseParsed,
3838
+ malformedAuthorityOrPort: baseMalformed,
3839
+ malformedPercentEncoding: baseMalformedPercentEncoding,
3840
+ malformedSchemeSpecific: baseMalformedSchemeSpecific,
3841
+ malformedHost: baseMalformedHost,
3842
+ malformedScheme: baseMalformedScheme
3843
+ } = parseWithStatus(baseURI, schemelessOptions);
3844
+ const {
3845
+ parsed: relativeParsed,
3846
+ malformedAuthorityOrPort: relativeMalformed,
3847
+ malformedPercentEncoding: relativeMalformedPercentEncoding,
3848
+ malformedSchemeSpecific: relativeMalformedSchemeSpecific,
3849
+ malformedHost: relativeMalformedHost,
3850
+ malformedScheme: relativeMalformedScheme
3851
+ } = parseWithStatus(relativeURI, schemelessOptions);
3852
+ if (baseMalformed || relativeMalformed || baseMalformedPercentEncoding || relativeMalformedPercentEncoding || baseMalformedSchemeSpecific || relativeMalformedSchemeSpecific || baseMalformedHost || relativeMalformedHost || baseMalformedScheme || relativeMalformedScheme) {
3853
+ throw new Error(baseParsed.error || relativeParsed.error || "URI is malformed.");
3854
+ }
3855
+ const resolved = resolveComponent(baseParsed, relativeParsed, schemelessOptions, true);
3856
+ const resolvedSchemeHandler = getSchemeHandler(options && options.scheme || resolved.scheme);
3857
+ const resolvedHost = resolved.host;
3858
+ const resolvedHostIsIP = resolvedHost !== void 0 && resolvedHost !== "" && (isIPv4(resolvedHost) || normalizeIPv6(resolvedHost).isIPV6);
3859
+ canonicalizeHost(resolved, options || {}, resolvedSchemeHandler, resolvedHostIsIP);
3860
+ const encodedASCIIHost = resolvedHost && resolvedHost.indexOf("%") !== -1 && !/\P{ASCII}/u.test(resolvedHost);
3861
+ if (resolved.error && !encodedASCIIHost) {
3862
+ throw new Error(resolved.error);
3863
+ }
3643
3864
  schemelessOptions.skipEscape = true;
3644
3865
  return serialize(resolved, schemelessOptions);
3645
3866
  }
@@ -3699,7 +3920,7 @@ var require_fast_uri = __commonJS({
3699
3920
  function equal(uriA, uriB, options) {
3700
3921
  const normalizedA = normalizeComparableURI(uriA, options);
3701
3922
  const normalizedB = normalizeComparableURI(uriB, options);
3702
- return normalizedA !== void 0 && normalizedB !== void 0 && normalizedA.toLowerCase() === normalizedB.toLowerCase();
3923
+ return normalizedA !== void 0 && normalizedB !== void 0 && normalizedA === normalizedB;
3703
3924
  }
3704
3925
  function serialize(cmpts, opts) {
3705
3926
  const component = {
@@ -3720,19 +3941,22 @@ var require_fast_uri = __commonJS({
3720
3941
  };
3721
3942
  const options = Object.assign({}, opts);
3722
3943
  const uriTokens = [];
3944
+ if (component.scheme) {
3945
+ component.scheme = decodeValidScheme(component.scheme);
3946
+ }
3723
3947
  const schemeHandler = getSchemeHandler(options.scheme || component.scheme);
3724
3948
  if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(component, options);
3949
+ const hasAuthority = component.userinfo !== void 0 || component.host !== void 0 || component.port !== void 0;
3950
+ const pathNoScheme = !options.skipEscape && component.scheme === void 0 && !hasAuthority;
3725
3951
  if (component.path !== void 0) {
3726
3952
  if (!options.skipEscape) {
3727
- component.path = escapePreservingEscapes(component.path);
3728
- if (component.scheme !== void 0) {
3729
- component.path = component.path.split("%3A").join(":");
3730
- }
3953
+ component.path = serializePathEncoding(component.path, pathNoScheme);
3731
3954
  } else {
3732
3955
  component.path = normalizePercentEncoding(component.path);
3733
3956
  }
3734
3957
  }
3735
3958
  if (options.reference !== "suffix" && component.scheme) {
3959
+ component.scheme = decodeValidScheme(component.scheme);
3736
3960
  uriTokens.push(component.scheme, ":");
3737
3961
  }
3738
3962
  const authority = recomposeAuthority(component);
@@ -3750,21 +3974,25 @@ var require_fast_uri = __commonJS({
3750
3974
  if (!options.absolutePath && (!schemeHandler || !schemeHandler.absolutePath)) {
3751
3975
  s = removeDotSegments(s);
3752
3976
  }
3977
+ if (pathNoScheme) {
3978
+ s = serializePathEncoding(s, true);
3979
+ }
3753
3980
  if (authority === void 0 && s[0] === "/" && s[1] === "/") {
3754
3981
  s = "/%2F" + s.slice(2);
3755
3982
  }
3756
3983
  uriTokens.push(s);
3757
3984
  }
3758
3985
  if (component.query !== void 0) {
3759
- uriTokens.push("?", component.query);
3986
+ uriTokens.push("?", encodeQuery(component.query));
3760
3987
  }
3761
3988
  if (component.fragment !== void 0) {
3762
- uriTokens.push("#", component.fragment);
3989
+ uriTokens.push("#", encodeFragment(component.fragment));
3763
3990
  }
3764
3991
  return uriTokens.join("");
3765
3992
  }
3766
3993
  var URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u;
3767
3994
  var AUTHORITY_PREFIX = /^(?:[^#/:?]+:)?\/\/([^/?#]*)/;
3995
+ var AUTHORITY_INTRODUCER_REGION = /^(?:[^#/:?]+:)?([/\\\t\n\r]*)/;
3768
3996
  function getParseError(parsed, matches) {
3769
3997
  if (matches[2] !== void 0 && parsed.path && parsed.path[0] !== "/") {
3770
3998
  return 'URI path must start with "/" when authority is present.';
@@ -3774,6 +4002,32 @@ var require_fast_uri = __commonJS({
3774
4002
  }
3775
4003
  return void 0;
3776
4004
  }
4005
+ function hasMalformedPercentEncoding(component) {
4006
+ if (component === void 0) return false;
4007
+ let percent = component.indexOf("%");
4008
+ while (percent !== -1) {
4009
+ if (percent + 2 >= component.length || !/^[\da-f]{2}$/iu.test(component.slice(percent + 1, percent + 3))) {
4010
+ return true;
4011
+ }
4012
+ percent = component.indexOf("%", percent + 3);
4013
+ }
4014
+ return false;
4015
+ }
4016
+ function hasMalformedComponentPercentEncoding(matches) {
4017
+ const host = matches[4];
4018
+ return hasMalformedPercentEncoding(matches[3]) || host !== void 0 && !(host[0] === "[" && host[host.length - 1] === "]") && hasMalformedPercentEncoding(host) || hasMalformedPercentEncoding(matches[6]) || hasMalformedPercentEncoding(matches[7]) || hasMalformedPercentEncoding(matches[8]);
4019
+ }
4020
+ function canonicalizeHost(parsed, options, schemeHandler, isIP) {
4021
+ if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport) && parsed.host && parsed.host[0] !== "[" && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP === false && nonSimpleDomain(parsed.host)) {
4022
+ try {
4023
+ parsed.host = new URL("http://" + parsed.host).hostname;
4024
+ } catch (e) {
4025
+ parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
4026
+ return true;
4027
+ }
4028
+ }
4029
+ return false;
4030
+ }
3777
4031
  function parseWithStatus(uri, opts) {
3778
4032
  const options = Object.assign({}, opts);
3779
4033
  const parsed = {
@@ -3786,6 +4040,11 @@ var require_fast_uri = __commonJS({
3786
4040
  fragment: void 0
3787
4041
  };
3788
4042
  let malformedAuthorityOrPort = false;
4043
+ let malformedPercentEncoding = false;
4044
+ let malformedSchemeSpecific = false;
4045
+ let malformedHost = false;
4046
+ let malformedIPLiteral = false;
4047
+ let malformedScheme = false;
3789
4048
  let isIP = false;
3790
4049
  if (options.reference === "suffix") {
3791
4050
  if (options.scheme) {
@@ -3799,6 +4058,20 @@ var require_fast_uri = __commonJS({
3799
4058
  parsed.error = "URI authority must not contain a literal backslash.";
3800
4059
  malformedAuthorityOrPort = true;
3801
4060
  }
4061
+ const introducerMatch = uri.match(AUTHORITY_INTRODUCER_REGION);
4062
+ if (introducerMatch !== null) {
4063
+ const region = introducerMatch[1];
4064
+ const normalizedRegion = region.replace(/[\t\n\r]/g, "");
4065
+ if (normalizedRegion.length >= 2) {
4066
+ if (normalizedRegion.slice(0, 2) !== "//") {
4067
+ parsed.error = parsed.error || "URI authority must not contain a literal backslash.";
4068
+ malformedAuthorityOrPort = true;
4069
+ } else if (region.length !== normalizedRegion.length) {
4070
+ parsed.error = parsed.error || "URI authority introducer must not contain whitespace.";
4071
+ malformedAuthorityOrPort = true;
4072
+ }
4073
+ }
4074
+ }
3802
4075
  const matches = uri.match(URI_PARSE);
3803
4076
  if (matches) {
3804
4077
  parsed.scheme = matches[1];
@@ -3808,6 +4081,19 @@ var require_fast_uri = __commonJS({
3808
4081
  parsed.path = matches[6] || "";
3809
4082
  parsed.query = matches[7];
3810
4083
  parsed.fragment = matches[8];
4084
+ if (parsed.scheme !== void 0) {
4085
+ const decodedScheme = unescape(parsed.scheme);
4086
+ if (VALID_SCHEME.test(decodedScheme)) {
4087
+ parsed.scheme = decodedScheme.toLowerCase();
4088
+ } else {
4089
+ parsed.error = parsed.error || MALFORMED_SCHEME_ERROR;
4090
+ malformedScheme = true;
4091
+ }
4092
+ }
4093
+ malformedPercentEncoding = hasMalformedComponentPercentEncoding(matches);
4094
+ if (malformedPercentEncoding) {
4095
+ parsed.error = parsed.error || "URI contains malformed percent-encoding.";
4096
+ }
3811
4097
  if (isNaN(parsed.port)) {
3812
4098
  parsed.port = matches[5];
3813
4099
  }
@@ -3819,9 +4105,15 @@ var require_fast_uri = __commonJS({
3819
4105
  if (parsed.host) {
3820
4106
  const ipv4result = isIPv4(parsed.host);
3821
4107
  if (ipv4result === false) {
4108
+ const bracketedIPLiteral = parsed.host[0] === "[" && parsed.host[parsed.host.length - 1] === "]";
3822
4109
  const ipv6result = normalizeIPv6(parsed.host);
3823
- parsed.host = ipv6result.host.toLowerCase();
3824
- isIP = ipv6result.isIPV6;
4110
+ isIP = ipv6result.isIPV6 || ipv6result.isIPVFuture === true;
4111
+ malformedIPLiteral = bracketedIPLiteral && ipv6result.error === true;
4112
+ parsed.host = isIP ? ipv6result.host : ipv6result.host.toLowerCase();
4113
+ if (malformedIPLiteral) {
4114
+ parsed.error = parsed.error || "URI host is malformed.";
4115
+ malformedAuthorityOrPort = true;
4116
+ }
3825
4117
  } else {
3826
4118
  isIP = true;
3827
4119
  }
@@ -3839,42 +4131,34 @@ var require_fast_uri = __commonJS({
3839
4131
  parsed.error = parsed.error || "URI is not a " + options.reference + " reference.";
3840
4132
  }
3841
4133
  const schemeHandler = getSchemeHandler(options.scheme || parsed.scheme);
3842
- if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport)) {
3843
- if (parsed.host && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP === false && nonSimpleDomain(parsed.host)) {
3844
- try {
3845
- parsed.host = new URL("http://" + parsed.host).hostname;
3846
- } catch (e) {
3847
- parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
3848
- }
3849
- }
3850
- }
4134
+ malformedHost = canonicalizeHost(parsed, options, schemeHandler, isIP);
3851
4135
  if (!schemeHandler || schemeHandler && !schemeHandler.skipNormalize) {
3852
4136
  if (uri.indexOf("%") !== -1) {
3853
- if (parsed.scheme !== void 0) {
3854
- parsed.scheme = unescape(parsed.scheme);
3855
- }
3856
- if (parsed.host !== void 0) {
3857
- parsed.host = reescapeHostDelimiters(unescape(parsed.host), isIP);
4137
+ if (parsed.host !== void 0 && !malformedIPLiteral) {
4138
+ const host = isIP ? parsed.host : normalizePercentEncoding(parsed.host, true);
4139
+ parsed.host = reescapeHostDelimiters(host, isIP);
3858
4140
  }
3859
4141
  }
3860
4142
  if (parsed.path) {
3861
4143
  parsed.path = normalizePathEncoding(parsed.path);
3862
4144
  }
4145
+ if (parsed.query) {
4146
+ parsed.query = normalizeQueryFragmentEncoding(parsed.query);
4147
+ }
3863
4148
  if (parsed.fragment) {
3864
- try {
3865
- parsed.fragment = encodeURI(decodeURIComponent(parsed.fragment));
3866
- } catch {
3867
- parsed.error = parsed.error || "URI malformed";
3868
- }
4149
+ parsed.fragment = normalizeQueryFragmentEncoding(parsed.fragment);
3869
4150
  }
3870
4151
  }
3871
4152
  if (schemeHandler && schemeHandler.parse) {
3872
4153
  schemeHandler.parse(parsed, options);
4154
+ if (schemeHandler === SCHEMES.urn && parsed.nid === void 0) {
4155
+ malformedSchemeSpecific = true;
4156
+ }
3873
4157
  }
3874
4158
  } else {
3875
4159
  parsed.error = parsed.error || "URI can not be parsed.";
3876
4160
  }
3877
- return { parsed, malformedAuthorityOrPort };
4161
+ return { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme };
3878
4162
  }
3879
4163
  function parse3(uri, opts) {
3880
4164
  return parseWithStatus(uri, opts).parsed;
@@ -3883,20 +4167,28 @@ var require_fast_uri = __commonJS({
3883
4167
  return normalizeStringWithStatus(uri, opts).normalized;
3884
4168
  }
3885
4169
  function normalizeStringWithStatus(uri, opts) {
3886
- const { parsed, malformedAuthorityOrPort } = parseWithStatus(uri, opts);
4170
+ const { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme } = parseWithStatus(uri, opts);
3887
4171
  return {
3888
- normalized: malformedAuthorityOrPort ? uri : serialize(parsed, opts),
3889
- malformedAuthorityOrPort
4172
+ normalized: malformedAuthorityOrPort || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? uri : serialize(parsed, opts),
4173
+ malformedAuthorityOrPort,
4174
+ malformedPercentEncoding,
4175
+ malformedSchemeSpecific,
4176
+ malformedHost,
4177
+ malformedScheme
3890
4178
  };
3891
4179
  }
3892
4180
  function normalizeComparableURI(uri, opts) {
3893
- if (typeof uri === "string") {
3894
- const { normalized, malformedAuthorityOrPort } = normalizeStringWithStatus(uri, opts);
3895
- return malformedAuthorityOrPort ? void 0 : normalized;
4181
+ if (typeof uri !== "string" && typeof uri !== "object") {
4182
+ return void 0;
3896
4183
  }
3897
- if (typeof uri === "object") {
3898
- return serialize(uri, opts);
4184
+ let value;
4185
+ try {
4186
+ value = typeof uri === "string" ? uri : serialize(uri, opts);
4187
+ } catch {
4188
+ return void 0;
3899
4189
  }
4190
+ const { normalized, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme } = normalizeStringWithStatus(value, opts);
4191
+ return malformedAuthorityOrPort || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? void 0 : normalized;
3900
4192
  }
3901
4193
  var fastUri = {
3902
4194
  SCHEMES,
package/hooks/hooks.json CHANGED
@@ -26,7 +26,7 @@
26
26
  ],
27
27
  "PostToolUse": [
28
28
  {
29
- "matcher": "choose_identity|create_identity|create_root_identity",
29
+ "matcher": "choose_identity|create_identity|create_root_identity|create_temporary_identity",
30
30
  "hooks": [
31
31
  {
32
32
  "type": "command",
@@ -35,6 +35,18 @@
35
35
  }
36
36
  ]
37
37
  }
38
+ ],
39
+ "SessionEnd": [
40
+ {
41
+ "hooks": [
42
+ {
43
+ "type": "command",
44
+ "command": "exec node \"${PLUGIN_ROOT}/bin/proxy.mjs\" session-end",
45
+ "timeout": 30,
46
+ "statusMessage": "Closing temporary ours identities"
47
+ }
48
+ ]
49
+ }
38
50
  ]
39
51
  }
40
52
  }
package/install.sh CHANGED
@@ -32,9 +32,14 @@ ensure_daemon_latest(){
32
32
  npm i -g @ours.network/cli@latest @ours.network/mcp@latest
33
33
  after="$(ours version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || true)"
34
34
  if ! ours daemon status >/dev/null 2>&1; then
35
- say "starting the ours daemon…"; ours daemon start || say "could not start; run 'ours daemon start' if the tools error."
35
+ say "starting the ours daemon…"
36
+ if ! ours daemon start; then say "could not start the ours daemon; installation stopped."; return 1; fi
36
37
  elif [ -n "$before" ] && [ "$before" != "$after" ]; then
37
- say "operator CLI upgraded (v${before} → v${after}) — restarting its daemon…"; ours daemon restart || ours daemon start || true
38
+ say "operator CLI upgraded (v${before} → v${after}) — restarting its daemon…"
39
+ if ! ours daemon restart; then
40
+ say "restart failed; trying a clean start…"
41
+ if ! ours daemon start; then say "could not restart or start the upgraded daemon; installation stopped."; return 1; fi
42
+ fi
38
43
  else
39
44
  say "daemon already current (v${after:-unknown})."
40
45
  fi
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ours.network/codex",
3
- "version": "0.18.0-nightly.5",
3
+ "version": "0.18.0-nightly.6",
4
4
  "description": "Native Codex plugin for secure ours.network messaging and explicitly armed, session-scoped live mail wake.",
5
5
  "type": "module",
6
6
  "license": "FSL-1.1-Apache-2.0",
@@ -46,8 +46,8 @@
46
46
  },
47
47
  "dependencies": {
48
48
  "@modelcontextprotocol/sdk": "^1.29.0",
49
- "@ours.network/mcp": "0.18.0-nightly.5",
50
- "@ours.network/sdk": "3.0.1",
49
+ "@ours.network/mcp": "0.18.0-nightly.6",
50
+ "@ours.network/sdk": "3.2.2",
51
51
  "ws": "^8.21.0",
52
52
  "zod": "^3.25.76"
53
53
  },
@@ -64,15 +64,15 @@ Walk the user through it, explaining as you go:
64
64
  every one is automatically associated with the Human identity, and its invites carry
65
65
  the verified "agent X of person Y" chain.
66
66
 
67
- **Do not create an agent identity on a host with no Human identity.** That would make a
68
- "flat" identity: no verified human behind it in invites, no control plane. The tool
69
- allows it for legacy reasons; this skill does not.
67
+ **Do not create an agent identity on a host with no Human identity.** Older servers may
68
+ accept that as unassociated legacy state: no verified human behind it in invites, no
69
+ control plane. This skill never creates or exposes such state.
70
70
 
71
71
  | Tempting shortcut | Why it's wrong |
72
72
  |---|---|
73
73
  | "The user clearly asked for an *agent*, so the human question doesn't apply" | The gate is about ORDER, not classification. Create the Human identity first, then the agent they asked for. |
74
74
  | "The user is busy / gave me everything I need for the agent" | Onboarding adds one question — the person's name. Ask it. |
75
- | "`create_identity` works fine without a Human identity" | It creates a flat legacy identity with no human association. Never do it. |
75
+ | "`create_identity` works fine without a Human identity" | Older servers may create unassociated legacy state with no human association. Never do it. |
76
76
  | "I'll create the agent now and the Human identity later" | Later never comes, and the agent's invites go out with no human chain. Human first. |
77
77
 
78
78
  ## Setup — "set up ours" / "set up the plugin"
@@ -229,7 +229,8 @@ the bio, so a persona prompt is only needed if they want to role-play it).
229
229
 
230
230
  For scratch/one-off work ("make a temporary identity", "throwaway identity"):
231
231
  `create_temporary_identity({ name? , bio?, expose_local? })` — name optional (omitted → a
232
- random public-safe `tmp-…` name), binds it to this session, and marks it **temporary**:
232
+ random public-safe `tmp-…` name), requires an existing Human identity, creates an
233
+ ephemeral delegated role under it, binds it to this session, and marks it **temporary**:
233
234
 
234
235
  - **Session-scoped local lifetime.** When this session ends — an explicit
235
236
  `close_temporary_identity()`, releasing the connection, or the client process dying —
@@ -241,8 +242,9 @@ random public-safe `tmp-…` name), binds it to this session, and marks it **tem
241
242
  owning session lives — not even with `force`. A **stale** one (owner process dead) is
242
243
  reclaimed automatically by the daemon, or immediately via
243
244
  `close_temporary_identity({ name })` from any session.
244
- - It is flat (never delegated under the Human identity) and NOT in the local contact book
245
- unless `expose_local: true`.
245
+ - Its verified Human/root delegation is part of creation; the tool fails closed rather
246
+ than creating unassociated state. It is NOT in the local contact book unless
247
+ `expose_local: true`.
246
248
  - `list_identities()` tags each temporary identity with its lease state (owned by this
247
249
  session / another live session / stale / closing).
248
250
 
@@ -62,7 +62,7 @@ export async function handleHook(payload, { env = process.env, fetch: fetchImpl
62
62
  if (event === 'SessionStart' && payload.source === 'compact') return { continue: true };
63
63
  if (event === 'PostToolUse') {
64
64
  if (!socket || !capability || payload.tool_response?.isError) return { continue: true };
65
- if (!/(choose_identity|create_identity|create_root_identity)$/.test(payload.tool_name || '')) return { continue: true };
65
+ if (!/(choose_identity|create_identity|create_root_identity|create_temporary_identity)$/.test(payload.tool_name || '')) return { continue: true };
66
66
  const identity = payload.tool_input?.name;
67
67
  if (typeof identity !== 'string' || !identity.trim()) return { continue: true };
68
68
  await send(socket, capability, { command: 'binding_changed', identity });
package/src/launcher.mjs CHANGED
@@ -2,7 +2,8 @@ import { spawn as nodeSpawn } from 'node:child_process';
2
2
  import { createServer } from 'node:net';
3
3
  import { chmod, mkdtemp, rm } from 'node:fs/promises';
4
4
  import { tmpdir } from 'node:os';
5
- import { join } from 'node:path';
5
+ import { dirname, join } from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
6
7
  import { randomBytes } from 'node:crypto';
7
8
  import { once } from 'node:events';
8
9
  import { resolveDaemonProfile } from './profile.mjs';
@@ -28,6 +29,10 @@ export function launcherEnvironment(env, profile, control) {
28
29
  OURS_CODEX_CONTROL_SOCKET: control.socketPath,
29
30
  OURS_CODEX_CAPABILITY: control.capability,
30
31
  OURS_CODEX_LIVE: '1',
32
+ // One stable owner pid for the MCP proxy, hooks, and launcher's own cleanup
33
+ // call. The bin shim preserves an explicit value instead of replacing it
34
+ // with whichever child happened to spawn it.
35
+ OURS_CLIENT_PID: String(process.pid),
31
36
  };
32
37
  if (profile.token) out.OURS_API_TOKEN = profile.token;
33
38
  else delete out.OURS_API_TOKEN;
@@ -89,10 +94,24 @@ export async function runLauncher({
89
94
  let client;
90
95
  let control;
91
96
  let watcher;
97
+ const sessionEndShim = join(dirname(dirname(fileURLToPath(import.meta.url))), 'bin', 'proxy.mjs');
92
98
  const cleanup = async () => {
93
99
  watcher?.stop();
94
100
  await control?.close().catch(() => {});
95
101
  client?.close();
102
+ // The TUI exit is an authoritative normal session end. Run the same lease
103
+ // DELETE seam as the native hook while the shared daemon is still reachable;
104
+ // duplicate hook+launcher cleanup is intentionally idempotent.
105
+ const end = spawn(process.execPath, [sessionEndShim, 'session-end'], {
106
+ env: processEnvs.appServer,
107
+ stdio: 'ignore',
108
+ });
109
+ end.once('error', () => {});
110
+ await Promise.race([
111
+ waitExit(end),
112
+ new Promise((resolve) => setTimeout(resolve, 30_000)),
113
+ ]).catch(() => {});
114
+ if (end.exitCode == null) end.kill('SIGTERM');
96
115
  if (tui && tui.exitCode == null) tui.kill('SIGTERM');
97
116
  if (appServer && appServer.exitCode == null) appServer.kill('SIGTERM');
98
117
  await rm(runtimeDir, { recursive: true, force: true });