@agenvo/codex-app-server 0.1.0 → 0.2.0-rc.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/README.md CHANGED
@@ -5,11 +5,11 @@
5
5
  Connect to an independently running Codex app-server over a Unix socket or loopback WebSocket. Connector shutdown and updates leave the native service and its turns running.
6
6
 
7
7
  ```sh
8
- npm install --global @agenvo/codex-app-server@0.1.0
8
+ npm install --global @agenvo/codex-app-server@0.2.0-rc.1
9
9
  ```
10
10
 
11
- For AI agents: follow the [installation guide](https://github.com/Xuanwo/agenvo/blob/v0.1.0/docs/installation.md) for relay deployment, device pairing, and client authorization.
11
+ For AI agents: follow the [installation guide](https://github.com/Xuanwo/agenvo/blob/v0.2.0-rc.1/docs/installation.md) for relay deployment, device pairing, and client authorization.
12
12
 
13
13
  Requires Node.js 24.13+. Installing a Connector does not install Herdr or Codex.
14
14
 
15
- [Usage and deployment](https://github.com/Xuanwo/agenvo/blob/v0.1.0/docs/usage.md)
15
+ [Usage and deployment](https://github.com/Xuanwo/agenvo/blob/v0.2.0-rc.1/docs/usage.md)
package/README.zh-CN.md CHANGED
@@ -5,11 +5,11 @@
5
5
  通过 Unix socket 或 loopback WebSocket 连接独立运行的 Codex app-server。关闭或更新 Connector 不会停止原生服务及其轮次。
6
6
 
7
7
  ```sh
8
- npm install --global @agenvo/codex-app-server@0.1.0
8
+ npm install --global @agenvo/codex-app-server@0.2.0-rc.1
9
9
  ```
10
10
 
11
- AI Agent 请按照[安装指南](https://github.com/Xuanwo/agenvo/blob/v0.1.0/docs/installation.zh-CN.md)完成中继部署、设备配对和客户端授权。
11
+ AI Agent 请按照[安装指南](https://github.com/Xuanwo/agenvo/blob/v0.2.0-rc.1/docs/installation.zh-CN.md)完成中继部署、设备配对和客户端授权。
12
12
 
13
13
  需要 Node.js 24.13+。安装 Connector 不会安装 Herdr 或 Codex。
14
14
 
15
- [使用与部署说明](https://github.com/Xuanwo/agenvo/blob/v0.1.0/docs/usage.zh-CN.md)
15
+ [使用与部署说明](https://github.com/Xuanwo/agenvo/blob/v0.2.0-rc.1/docs/usage.zh-CN.md)
package/dist/cli.js CHANGED
@@ -5,15 +5,6 @@ var __export = (target, all) => {
5
5
  __defProp(target, name, { get: all[name], enumerable: true });
6
6
  };
7
7
 
8
- // ../../packages/connector/src/cli/main.ts
9
- import { parseArgs } from "node:util";
10
- import { execFile as execFile2, spawn } from "node:child_process";
11
- import { promisify as promisify2 } from "node:util";
12
- import { randomBytes } from "node:crypto";
13
- import { readFile as readFile3, unlink as unlink3, stat as stat2 } from "node:fs/promises";
14
- import { join as join4 } from "node:path";
15
- import { hostname as hostname3 } from "node:os";
16
-
17
8
  // ../../node_modules/zod/v4/classic/external.js
18
9
  var external_exports = {};
19
10
  __export(external_exports, {
@@ -19682,6 +19673,64 @@ function date4(params) {
19682
19673
  return _coercedDate(ZodDate, params);
19683
19674
  }
19684
19675
 
19676
+ // ../../packages/protocol/src/address.ts
19677
+ var RelayAddress = class {
19678
+ baseUrl;
19679
+ origin;
19680
+ prefix;
19681
+ constructor(value) {
19682
+ const url2 = new URL(value);
19683
+ const rawPath = value.replace(/^https:\/\/[^/]+/i, "");
19684
+ if (!/^https:\/\//i.test(value) || /[\s\x00-\x1f\x7f]/.test(value) || url2.protocol !== "https:" || url2.username || url2.password || value.includes("?") || value.includes("#") || value.includes("\\") || /%2f|%5c/i.test(url2.pathname) || url2.pathname.includes("//") || rawPath.split("/").some((part) => /^(\.|%2e){1,2}$/i.test(part)))
19685
+ throw new Error(
19686
+ "Expected an HTTPS base URL without credentials, query, fragment or ambiguous path segments"
19687
+ );
19688
+ this.origin = url2.origin;
19689
+ this.prefix = url2.pathname.replace(/\/$/, "");
19690
+ this.baseUrl = this.origin + this.prefix;
19691
+ }
19692
+ get authorizationMetadataUrl() {
19693
+ return this.origin + "/.well-known/oauth-authorization-server" + this.prefix;
19694
+ }
19695
+ get resourceMetadataUrl() {
19696
+ return this.origin + "/.well-known/oauth-protected-resource" + this.path("/mcp");
19697
+ }
19698
+ path(route) {
19699
+ return this.prefix + route;
19700
+ }
19701
+ url(route) {
19702
+ return this.baseUrl + route;
19703
+ }
19704
+ /** Undefined means this URL does not belong to this deployment. */
19705
+ route(value) {
19706
+ const url2 = new URL(value);
19707
+ if (url2.origin !== this.origin) return;
19708
+ if (url2.pathname === this.prefix) return "/";
19709
+ if (!url2.pathname.startsWith(this.prefix + "/")) return;
19710
+ return url2.pathname.slice(this.prefix.length);
19711
+ }
19712
+ };
19713
+ var baseUrlSchema = external_exports.string().transform((value, ctx) => {
19714
+ try {
19715
+ return new RelayAddress(value).baseUrl;
19716
+ } catch {
19717
+ ctx.addIssue({
19718
+ code: "custom",
19719
+ message: "Expected an HTTPS base URL with an optional path prefix"
19720
+ });
19721
+ return external_exports.NEVER;
19722
+ }
19723
+ });
19724
+
19725
+ // ../../packages/connector/src/cli/main.ts
19726
+ import { parseArgs } from "node:util";
19727
+ import { execFile as execFile2, spawn } from "node:child_process";
19728
+ import { promisify as promisify2 } from "node:util";
19729
+ import { randomBytes } from "node:crypto";
19730
+ import { readFile as readFile3, unlink as unlink3, stat as stat2 } from "node:fs/promises";
19731
+ import { join as join4 } from "node:path";
19732
+ import { hostname as hostname3 } from "node:os";
19733
+
19685
19734
  // ../../packages/connector/src/config.ts
19686
19735
  import { homedir } from "node:os";
19687
19736
  import { join, resolve, dirname, isAbsolute } from "node:path";
@@ -19697,9 +19746,10 @@ import {
19697
19746
  unlink
19698
19747
  } from "node:fs/promises";
19699
19748
  import { randomUUID } from "node:crypto";
19749
+ import { setTimeout as delay } from "node:timers/promises";
19700
19750
 
19701
19751
  // ../../packages/protocol/src/index.ts
19702
- var VERSION = "0.1.0";
19752
+ var VERSION = "0.2.0-rc.1";
19703
19753
  var PROTOCOL = 1;
19704
19754
  var LIMITS = {
19705
19755
  frame: 64 * 1024,
@@ -19819,10 +19869,7 @@ var commonInstanceFields = {
19819
19869
  };
19820
19870
  var configSchema = (instanceSchema2) => external_exports.strictObject({
19821
19871
  schema: external_exports.literal(1),
19822
- relay: external_exports.string().url().refine((value) => {
19823
- const url2 = new URL(value);
19824
- return url2.protocol === "https:" && url2.origin === value;
19825
- }, "Relay must be a canonical HTTPS origin").optional(),
19872
+ relay: baseUrlSchema.optional(),
19826
19873
  deviceId: external_exports.string().uuid().optional(),
19827
19874
  name: external_exports.string().max(128),
19828
19875
  instances: external_exports.array(instanceSchema2).max(128)
@@ -19842,7 +19889,19 @@ async function atomicJson(path, value) {
19842
19889
  mode: 384,
19843
19890
  flag: "wx"
19844
19891
  });
19845
- await rename(temp, path);
19892
+ const deadline = Date.now() + 1e3;
19893
+ for (let attempt = 0; ; attempt++) {
19894
+ try {
19895
+ await rename(temp, path);
19896
+ break;
19897
+ } catch (error62) {
19898
+ if (process.platform !== "win32" || !["EACCES", "EPERM", "EBUSY"].includes(
19899
+ error62.code ?? ""
19900
+ ) || Date.now() >= deadline)
19901
+ throw error62;
19902
+ await delay(Math.min(10 * 2 ** attempt, 100, deadline - Date.now()));
19903
+ }
19904
+ }
19846
19905
  } finally {
19847
19906
  await unlink(temp).catch(() => {
19848
19907
  });
@@ -20038,7 +20097,17 @@ async function run(dir, backend2) {
20038
20097
  const save = () => {
20039
20098
  status.updatedAt = Date.now();
20040
20099
  const snapshot = structuredClone(status);
20041
- saving = saving.then(() => atomicJson(join2(dir, "status.json"), snapshot)).catch(() => {
20100
+ saving = saving.then(() => atomicJson(join2(dir, "status.json"), snapshot)).catch((err) => {
20101
+ connectionLog.error(
20102
+ {
20103
+ event: "connector.status.write_failed",
20104
+ state: snapshot.state,
20105
+ code: err.code,
20106
+ syscall: err.syscall,
20107
+ err
20108
+ },
20109
+ "Failed to publish connector status"
20110
+ );
20042
20111
  });
20043
20112
  };
20044
20113
  const send = (current, packet) => {
@@ -20099,7 +20168,7 @@ async function run(dir, backend2) {
20099
20168
  }
20100
20169
  const connect = () => {
20101
20170
  if (stopped || terminal) return;
20102
- const url2 = new URL("/connect", target);
20171
+ const url2 = new URL(target + "/connect");
20103
20172
  url2.protocol = url2.protocol === "https:" ? "wss:" : "ws:";
20104
20173
  const current = socket = new WebSocket(url2, {
20105
20174
  headers: {
@@ -20438,21 +20507,22 @@ async function service(dir, action, entry) {
20438
20507
  }
20439
20508
 
20440
20509
  // ../../packages/connector/src/cli/admin-client.ts
20441
- function ownerOrigin(value) {
20442
- if (!value) throw new Fault("origin_required", "Pass --origin https://RELAY");
20443
- const url2 = new URL(value);
20444
- if (url2.protocol !== "https:" || url2.origin !== value)
20445
- throw new Fault("https_origin_required");
20446
- return url2.origin;
20447
- }
20448
- async function adminRequest(origin, method, path, body = "") {
20510
+ function ownerBaseUrl(value) {
20511
+ if (!value)
20512
+ throw new Fault(
20513
+ "base_url_required",
20514
+ "Pass --base-url https://RELAY[/PREFIX]"
20515
+ );
20516
+ return baseUrlSchema.parse(value);
20517
+ }
20518
+ async function adminRequest(baseUrl, method, path, body = "") {
20449
20519
  const secret = process.env.AGENVO_ADMIN_SECRET;
20450
20520
  if (!secret)
20451
20521
  throw new Fault(
20452
20522
  "admin_secret_required",
20453
20523
  "Set AGENVO_ADMIN_SECRET for explicit administrator automation, or use the management page."
20454
20524
  );
20455
- const response = await fetch(origin + path, {
20525
+ const response = await fetch(baseUrl + path, {
20456
20526
  method,
20457
20527
  redirect: "error",
20458
20528
  signal: AbortSignal.timeout(15e3),
@@ -20472,7 +20542,7 @@ async function adminRequest(origin, method, path, body = "") {
20472
20542
 
20473
20543
  // ../../packages/connector/src/cli/admin.ts
20474
20544
  async function adminCommand(action, kind, options) {
20475
- const origin = ownerOrigin(String(options.origin ?? ""));
20545
+ const baseUrl = ownerBaseUrl(String(options["base-url"] ?? ""));
20476
20546
  let path, body = "", method = "POST";
20477
20547
  if (action === "state") {
20478
20548
  path = "/api/admin/state";
@@ -20496,12 +20566,12 @@ async function adminCommand(action, kind, options) {
20496
20566
  ...options["instance-id"] ? { instanceId: options["instance-id"] } : {}
20497
20567
  });
20498
20568
  } else throw new Fault("invalid_arguments");
20499
- return adminRequest(origin, method, path, body);
20569
+ return adminRequest(baseUrl, method, path, body);
20500
20570
  }
20501
20571
 
20502
20572
  // ../../packages/connector/src/cli/pairing.ts
20503
20573
  async function pairingCommand(action, code, options) {
20504
- const origin = ownerOrigin(String(options.origin ?? ""));
20574
+ const baseUrl = ownerBaseUrl(String(options["base-url"] ?? ""));
20505
20575
  if (!["list", "approve"].includes(action))
20506
20576
  throw new Fault("invalid_arguments");
20507
20577
  const method = action === "list" ? "GET" : "POST";
@@ -20512,7 +20582,7 @@ async function pairingCommand(action, code, options) {
20512
20582
  "Pass a pairing code and --fingerprint from the device terminal"
20513
20583
  );
20514
20584
  const body = action === "list" ? "" : JSON.stringify({ code, digest: options.fingerprint });
20515
- return adminRequest(origin, method, path, body);
20585
+ return adminRequest(baseUrl, method, path, body);
20516
20586
  }
20517
20587
 
20518
20588
  // ../../packages/connector/src/cli/main.ts
@@ -20526,7 +20596,7 @@ async function connectorCli(backend2, entry) {
20526
20596
  "label",
20527
20597
  "binary",
20528
20598
  "cwd",
20529
- "origin",
20599
+ "base-url",
20530
20600
  "fingerprint",
20531
20601
  "device-id",
20532
20602
  "instance-id",
@@ -20599,11 +20669,11 @@ async function connectorCli(backend2, entry) {
20599
20669
  ${backend2.command} instance add --id ID ${backend2.help}
20600
20670
  ${backend2.command} connect https://RELAY [--name NAME] [--approve | --no-wait --no-browser]
20601
20671
  ${backend2.command} connect --cancel
20602
- ${backend2.command} pairing list --origin https://RELAY
20603
- ${backend2.command} pairing approve CODE --fingerprint SHA256 --origin https://RELAY
20604
- ${backend2.command} admin state --origin https://RELAY
20605
- ${backend2.command} admin approve-instance --device-id ID --instance-id ID --fingerprint SHA256 --origin https://RELAY
20606
- ${backend2.command} admin revoke device|instance|grant --id ID [--instance-id ID] --origin https://RELAY
20672
+ ${backend2.command} pairing list --base-url https://RELAY
20673
+ ${backend2.command} pairing approve CODE --fingerprint SHA256 --base-url https://RELAY
20674
+ ${backend2.command} admin state --base-url https://RELAY
20675
+ ${backend2.command} admin approve-instance --device-id ID --instance-id ID --fingerprint SHA256 --base-url https://RELAY
20676
+ ${backend2.command} admin revoke device|instance|grant --id ID [--instance-id ID] --base-url https://RELAY
20607
20677
  ${backend2.command} run
20608
20678
  ${backend2.command} service install|uninstall
20609
20679
  ${backend2.command} status --json
@@ -20640,7 +20710,7 @@ After changing instances, restart this connector and approve the changed scope.`
20640
20710
  config: join4(dir, "config.json"),
20641
20711
  scope: instance,
20642
20712
  ...c.relay ? {
20643
- next: "Restart the connector, inspect " + backend2.command + " admin state --origin " + c.relay + ", then use " + backend2.command + " admin approve-instance."
20713
+ next: "Restart the connector, inspect " + backend2.command + " admin state --base-url " + c.relay + ", then use " + backend2.command + " admin approve-instance."
20644
20714
  } : {}
20645
20715
  });
20646
20716
  return;
@@ -20658,9 +20728,7 @@ After changing instances, restart this connector and approve the changed scope.`
20658
20728
  });
20659
20729
  return output2({ cancelled: true });
20660
20730
  }
20661
- const relay = new URL(subcommand);
20662
- if (relay.protocol !== "https:" || relay.pathname !== "/" || relay.username || relay.password || relay.search || relay.hash)
20663
- throw new Fault("https_origin_required");
20731
+ const relay = baseUrlSchema.parse(subcommand);
20664
20732
  const c = await config2();
20665
20733
  if (c.deviceId)
20666
20734
  throw new Fault(
@@ -20670,7 +20738,7 @@ After changing instances, restart this connector and approve the changed scope.`
20670
20738
  let p;
20671
20739
  try {
20672
20740
  p = JSON.parse(await readFile3(join4(dir, "pairing.json"), "utf8"));
20673
- if (p.relay !== relay.origin)
20741
+ if (p.relay !== relay)
20674
20742
  throw new Fault(
20675
20743
  "pending_pairing",
20676
20744
  "Cancel the existing pairing before choosing a different relay"
@@ -20701,14 +20769,14 @@ After changing instances, restart this connector and approve the changed scope.`
20701
20769
  )
20702
20770
  )
20703
20771
  );
20704
- p = await post(new URL("/pairings", relay).href, {
20772
+ p = await post(relay + "/pairings", {
20705
20773
  digest: await digest(secret),
20706
20774
  label: String(options.name ?? c.name),
20707
20775
  instances
20708
20776
  });
20709
20777
  p = {
20710
20778
  ...p,
20711
- relay: relay.origin,
20779
+ relay,
20712
20780
  expires: Date.now() + p.expiresIn * 1e3,
20713
20781
  name: String(options.name ?? c.name),
20714
20782
  configHash: await digest(JSON.stringify(c.instances))
@@ -20723,7 +20791,7 @@ After changing instances, restart this connector and approve the changed scope.`
20723
20791
  if (options["no-wait"]) return;
20724
20792
  if (options.approve) {
20725
20793
  await pairingCommand("approve", p.code, {
20726
- origin: relay.origin,
20794
+ "base-url": relay,
20727
20795
  fingerprint: p.fingerprint
20728
20796
  });
20729
20797
  } else {
@@ -20732,13 +20800,13 @@ After changing instances, restart this connector and approve the changed scope.`
20732
20800
  const expires = p.expires;
20733
20801
  while (Date.now() < expires) {
20734
20802
  const result = await post(
20735
- new URL("/pairings/poll", relay).href,
20803
+ relay + "/pairings/poll",
20736
20804
  { code: p.code },
20737
20805
  p.pollSecret
20738
20806
  );
20739
20807
  if (result.status === "approved") {
20740
20808
  c.deviceId = result.deviceId;
20741
- c.relay = relay.origin;
20809
+ c.relay = relay;
20742
20810
  c.name = p.name;
20743
20811
  await saveConfig(c, dir, backend2.schema);
20744
20812
  await unlink3(join4(dir, "pairing.json"));
@@ -20882,7 +20950,7 @@ After changing instances, restart this connector and approve the changed scope.`
20882
20950
  cloudRevoked,
20883
20951
  serviceUninstalled,
20884
20952
  ...cloudRevoked ? {} : {
20885
- next: "When connectivity returns, run " + backend2.command + " admin revoke device --id " + revokedDeviceId + " --origin " + c.relay
20953
+ next: "When connectivity returns, run " + backend2.command + " admin revoke device --id " + revokedDeviceId + " --base-url " + c.relay
20886
20954
  }
20887
20955
  });
20888
20956
  return;
@@ -23045,7 +23113,7 @@ var CodexAdapter = class {
23045
23113
  ws.once("close", () => reject(new Fault("runtime_unavailable")));
23046
23114
  });
23047
23115
  const init = await this.rpc("initialize", {
23048
- clientInfo: { name: "agenvo", version: "0.1.0" },
23116
+ clientInfo: { name: "agenvo", version: VERSION },
23049
23117
  capabilities: { experimentalApi: true }
23050
23118
  });
23051
23119
  if (await realpath2(init.codexHome) !== await realpath2(this.config.home))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agenvo/codex-app-server",
3
- "version": "0.1.0",
3
+ "version": "0.2.0-rc.1",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -12,8 +12,8 @@
12
12
  "node": ">=24.13.0"
13
13
  },
14
14
  "devDependencies": {
15
- "@agenvo/connector": "0.1.0",
16
- "@agenvo/protocol": "0.1.0",
15
+ "@agenvo/connector": "0.2.0-rc.1",
16
+ "@agenvo/protocol": "0.2.0-rc.1",
17
17
  "zod": "4.6.5"
18
18
  },
19
19
  "description": "Codex app-server connector for Agenvo",