pwragent 0.0.0 → 1.3.7

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.
Files changed (88) hide show
  1. package/README.md +494 -2
  2. package/bin/pwragent.js +10 -1
  3. package/dist/THIRD_PARTY_LICENSES +165 -0
  4. package/dist/federation-core.js +19569 -0
  5. package/dist/ws-LICENSE +20 -0
  6. package/examples/pwragent-headless.service +25 -0
  7. package/licenses/xterm-LICENSE +26 -0
  8. package/node_modules/node-addon-api/LICENSE.md +9 -0
  9. package/node_modules/node-addon-api/README.md +319 -0
  10. package/node_modules/node-addon-api/common.gypi +20 -0
  11. package/node_modules/node-addon-api/except.gypi +25 -0
  12. package/node_modules/node-addon-api/index.js +12 -0
  13. package/node_modules/node-addon-api/napi-inl.deprecated.h +186 -0
  14. package/node_modules/node-addon-api/napi-inl.h +6607 -0
  15. package/node_modules/node-addon-api/napi.h +3201 -0
  16. package/node_modules/node-addon-api/node_addon_api.gyp +32 -0
  17. package/node_modules/node-addon-api/node_api.gyp +9 -0
  18. package/node_modules/node-addon-api/noexcept.gypi +26 -0
  19. package/node_modules/node-addon-api/nothing.c +0 -0
  20. package/node_modules/node-addon-api/package-support.json +21 -0
  21. package/node_modules/node-addon-api/package.json +480 -0
  22. package/node_modules/node-addon-api/tools/README.md +73 -0
  23. package/node_modules/node-addon-api/tools/check-napi.js +99 -0
  24. package/node_modules/node-addon-api/tools/clang-format.js +71 -0
  25. package/node_modules/node-addon-api/tools/conversion.js +301 -0
  26. package/node_modules/node-addon-api/tools/eslint-format.js +79 -0
  27. package/node_modules/node-pty/LICENSE +69 -0
  28. package/node_modules/node-pty/README.md +164 -0
  29. package/node_modules/node-pty/binding.gyp +150 -0
  30. package/node_modules/node-pty/lib/conpty_console_list_agent.js +25 -0
  31. package/node_modules/node-pty/lib/eventEmitter2.js +47 -0
  32. package/node_modules/node-pty/lib/index.js +52 -0
  33. package/node_modules/node-pty/lib/interfaces.js +7 -0
  34. package/node_modules/node-pty/lib/shared/conout.js +10 -0
  35. package/node_modules/node-pty/lib/terminal.js +190 -0
  36. package/node_modules/node-pty/lib/types.js +7 -0
  37. package/node_modules/node-pty/lib/unixTerminal.js +351 -0
  38. package/node_modules/node-pty/lib/utils.js +38 -0
  39. package/node_modules/node-pty/lib/windowsConoutConnection.js +131 -0
  40. package/node_modules/node-pty/lib/windowsPtyAgent.js +344 -0
  41. package/node_modules/node-pty/lib/windowsTerminal.js +209 -0
  42. package/node_modules/node-pty/lib/worker/conoutSocketWorker.js +22 -0
  43. package/node_modules/node-pty/package.json +66 -0
  44. package/node_modules/node-pty/prebuilds/darwin-arm64/pty.node +0 -0
  45. package/node_modules/node-pty/prebuilds/darwin-arm64/spawn-helper +0 -0
  46. package/node_modules/node-pty/prebuilds/darwin-x64/pty.node +0 -0
  47. package/node_modules/node-pty/prebuilds/darwin-x64/spawn-helper +0 -0
  48. package/node_modules/node-pty/prebuilds/linux-arm64/pty.node +0 -0
  49. package/node_modules/node-pty/prebuilds/linux-x64/pty.node +0 -0
  50. package/node_modules/node-pty/prebuilds/win32-arm64/conpty/OpenConsole.exe +0 -0
  51. package/node_modules/node-pty/prebuilds/win32-arm64/conpty/conpty.dll +0 -0
  52. package/node_modules/node-pty/prebuilds/win32-arm64/conpty.node +0 -0
  53. package/node_modules/node-pty/prebuilds/win32-arm64/conpty.pdb +0 -0
  54. package/node_modules/node-pty/prebuilds/win32-arm64/conpty_console_list.node +0 -0
  55. package/node_modules/node-pty/prebuilds/win32-arm64/conpty_console_list.pdb +0 -0
  56. package/node_modules/node-pty/prebuilds/win32-x64/conpty/OpenConsole.exe +0 -0
  57. package/node_modules/node-pty/prebuilds/win32-x64/conpty/conpty.dll +0 -0
  58. package/node_modules/node-pty/prebuilds/win32-x64/conpty.node +0 -0
  59. package/node_modules/node-pty/prebuilds/win32-x64/conpty.pdb +0 -0
  60. package/node_modules/node-pty/prebuilds/win32-x64/conpty_console_list.node +0 -0
  61. package/node_modules/node-pty/prebuilds/win32-x64/conpty_console_list.pdb +0 -0
  62. package/node_modules/node-pty/scripts/post-install.js +76 -0
  63. package/node_modules/node-pty/scripts/prebuild.js +34 -0
  64. package/node_modules/node-pty/src/unix/pty.cc +876 -0
  65. package/node_modules/node-pty/src/unix/spawn-helper.cc +23 -0
  66. package/node_modules/node-pty/src/win/conpty.cc +616 -0
  67. package/node_modules/node-pty/src/win/conpty.h +41 -0
  68. package/node_modules/node-pty/src/win/conpty_console_list.cc +44 -0
  69. package/node_modules/node-pty/src/win/path_util.cc +95 -0
  70. package/node_modules/node-pty/src/win/path_util.h +26 -0
  71. package/node_modules/node-pty/third_party/conpty/1.25.260303002/win10-arm64/OpenConsole.exe +0 -0
  72. package/node_modules/node-pty/third_party/conpty/1.25.260303002/win10-arm64/conpty.dll +0 -0
  73. package/node_modules/node-pty/third_party/conpty/1.25.260303002/win10-x64/OpenConsole.exe +0 -0
  74. package/node_modules/node-pty/third_party/conpty/1.25.260303002/win10-x64/conpty.dll +0 -0
  75. package/node_modules/node-pty/typings/node-pty.d.ts +215 -0
  76. package/package.json +32 -5
  77. package/src/cli.js +123 -0
  78. package/src/client.js +27 -0
  79. package/src/codex.js +112 -0
  80. package/src/daemon.js +125 -0
  81. package/src/federation-backend.js +453 -0
  82. package/src/federation-core.ts +24 -0
  83. package/src/federation.js +454 -0
  84. package/src/handoffs.js +141 -0
  85. package/src/projects.js +140 -0
  86. package/src/runtime.js +265 -0
  87. package/src/store.js +80 -0
  88. package/src/wire.js +33 -0
package/src/codex.js ADDED
@@ -0,0 +1,112 @@
1
+ import { spawn } from "node:child_process";
2
+ import { readMessages, MAX_RESPONSE_BYTES } from "./wire.js";
3
+
4
+ // This adapter deliberately supports only the stable App Server job surface.
5
+ // It never reads Codex's storage or imports Electron/desktop modules.
6
+ export class Codex {
7
+ constructor({ command, home, workspace, onNotification, onRequest, onFailure, timeout = 30000 }) {
8
+ this.pending = new Map();
9
+ this.sequence = 0;
10
+ this.timeout = timeout;
11
+ this.onFailure = onFailure;
12
+ const env = { ...process.env, CODEX_HOME: home };
13
+ for (const key of Object.keys(env)) {
14
+ if (key.toUpperCase() === "ELECTRON_RENDERER_URL") delete env[key];
15
+ }
16
+ this.child = spawn(command, ["app-server", "--listen", "stdio://"], {
17
+ cwd: workspace,
18
+ env,
19
+ stdio: ["pipe", "pipe", "pipe"],
20
+ // A dedicated group lets shutdown reap ordinary descendants on POSIX.
21
+ detached: true,
22
+ });
23
+ this.closed = new Promise((resolve) => this.child.once("close", resolve));
24
+ this.child.on("error", () => this.fail(new Error("Could not start Codex App Server")));
25
+ this.child.on("exit", () => this.fail(new Error("Codex App Server exited")));
26
+ this.child.stdin.on("error", () => this.fail(new Error("Codex App Server input closed")));
27
+ // Drain diagnostics without copying potentially sensitive provider text to
28
+ // the journal. Request errors are returned to the private local client.
29
+ this.child.stderr.resume();
30
+ readMessages(this.child.stdout, MAX_RESPONSE_BYTES, (message) => {
31
+ if (typeof message.method === "string") {
32
+ if (message.id !== undefined) onRequest(message);
33
+ else onNotification(message);
34
+ return;
35
+ }
36
+ const request = this.pending.get(message.id);
37
+ if (!request) return;
38
+ if (!("error" in message) && !("result" in message)) {
39
+ this.fail(new Error("Invalid Codex response"));
40
+ return;
41
+ }
42
+ this.pending.delete(message.id);
43
+ clearTimeout(request.timer);
44
+ if (message.error) request.reject(new Error(message.error.message || "Codex request failed"));
45
+ else if ("result" in message) request.resolve(message.result);
46
+ }, (error) => this.fail(error));
47
+ }
48
+
49
+ fail(error) {
50
+ if (this.failure) return;
51
+ this.failure = error;
52
+ for (const request of this.pending.values()) {
53
+ clearTimeout(request.timer);
54
+ request.reject(error);
55
+ }
56
+ this.pending.clear();
57
+ if (!this.stopping) this.onFailure(error);
58
+ }
59
+
60
+ send(message) {
61
+ if (this.failure || this.stopping) throw this.failure || new Error("Codex is stopping");
62
+ this.child.stdin.write(JSON.stringify(message) + "\n");
63
+ }
64
+
65
+ request(method, params) {
66
+ if (this.failure || this.stopping) return Promise.reject(this.failure || new Error("Codex is stopping"));
67
+ if (this.pending.size >= 32) return Promise.reject(new Error("Too many pending Codex requests"));
68
+ const id = ++this.sequence;
69
+ return new Promise((resolve, reject) => {
70
+ const timer = setTimeout(() => {
71
+ // A timeout may mean a mutation executed but its reply was lost.
72
+ // Stop the runtime; never retry an uncertain turn/start.
73
+ this.fail(new Error(`Codex request timed out: ${method}`));
74
+ }, this.timeout);
75
+ this.pending.set(id, { resolve, reject, timer });
76
+ try {
77
+ this.send({ id, method, params });
78
+ } catch (error) {
79
+ this.fail(error);
80
+ }
81
+ });
82
+ }
83
+
84
+ async initialize() {
85
+ await this.request("initialize", {
86
+ clientInfo: { name: "pwragent-headless", title: "PwrAgent Headless", version: "0.0.0" },
87
+ capabilities: { experimentalApi: true },
88
+ });
89
+ this.send({ method: "initialized", params: {} });
90
+ }
91
+
92
+ async stop() {
93
+ this.stopping = true;
94
+ this.fail(new Error("Codex stopped"));
95
+ const signal = (name) => {
96
+ if (!this.child.pid) return;
97
+ try {
98
+ process.kill(-this.child.pid, name);
99
+ } catch (error) {
100
+ if (error.code !== "ESRCH") throw error;
101
+ }
102
+ };
103
+ signal("SIGTERM");
104
+ let timer;
105
+ await Promise.race([this.closed, new Promise((resolve) => { timer = setTimeout(resolve, 2000); })]);
106
+ clearTimeout(timer);
107
+ // Also reap descendants that outlived the group leader. systemd's cgroup
108
+ // owns processes that deliberately leave this group.
109
+ signal("SIGKILL");
110
+ await this.closed;
111
+ }
112
+ }
package/src/daemon.js ADDED
@@ -0,0 +1,125 @@
1
+ import { closeSync, openSync, realpathSync, statSync, unlinkSync, writeFileSync } from "node:fs";
2
+ import net from "node:net";
3
+ import path from "node:path";
4
+ import { JobStore, privateDirectory } from "./store.js";
5
+ import { Runtime } from "./runtime.js";
6
+ import { HeadlessFederation } from "./federation.js";
7
+ import { MAX_REQUEST_BYTES, MAX_RESPONSE_BYTES, readMessages } from "./wire.js";
8
+
9
+ export async function serve({ directory, workspace, home, command, profile }) {
10
+ if (!["linux", "darwin"].includes(process.platform)) throw new Error("Headless currently supports Linux and macOS only");
11
+ for (const value of [workspace, home, command]) {
12
+ if (!value || !path.isAbsolute(value)) throw new Error("serve requires absolute --workspace, --codex-home and --codex-bin paths");
13
+ }
14
+ workspace = realpathSync(workspace);
15
+ home = realpathSync(home);
16
+ if (!statSync(workspace).isDirectory() || !statSync(home).isDirectory()) throw new Error("Workspace and Codex home must be directories");
17
+ privateDirectory(directory);
18
+ directory = realpathSync(directory);
19
+ const roots = [directory, workspace, home];
20
+ for (let i = 0; i < roots.length; i++) {
21
+ for (let j = i + 1; j < roots.length; j++) {
22
+ if (roots[i] === roots[j] || roots[i].startsWith(roots[j] + path.sep) || roots[j].startsWith(roots[i] + path.sep)) {
23
+ throw new Error("Workspace, Codex home and headless state must be separate, non-overlapping directories");
24
+ }
25
+ }
26
+ }
27
+ const socketPath = path.join(directory, "rpc.sock");
28
+ if (Buffer.byteLength(socketPath) > 100) throw new Error("Headless socket path exceeds 100 bytes; choose a shorter PWRAGENT_HOME");
29
+ const lockPath = path.join(directory, "owner.lock");
30
+ let lock;
31
+ try { lock = openSync(lockPath, "wx", 0o600); } catch (error) {
32
+ if (error.code === "EEXIST") throw new Error(`Headless owner lock already exists: ${lockPath}. See README crash recovery; never remove a live owner's lock.`, { cause: error });
33
+ throw error;
34
+ }
35
+ let runtime;
36
+ let federation;
37
+ let server;
38
+ let listening = false;
39
+ let shutdown;
40
+ let exitCode = 0;
41
+ let resolveDone;
42
+ const done = new Promise((resolve) => { resolveDone = resolve; });
43
+ const sockets = new Set();
44
+ const stop = (error) => {
45
+ if (error) exitCode = 1;
46
+ if (shutdown) return shutdown;
47
+ shutdown = (async () => {
48
+ if (listening) server.close();
49
+ for (const socket of sockets) socket.destroy();
50
+ try {
51
+ try { await federation?.stop(); } finally { await runtime?.stop(); }
52
+ } finally {
53
+ closeSync(lock);
54
+ unlinkSync(lockPath);
55
+ process.off("SIGTERM", signalStop);
56
+ process.off("SIGINT", signalStop);
57
+ resolveDone(exitCode);
58
+ }
59
+ })();
60
+ // Persistence/cleanup failure must fail the process, not become an
61
+ // unhandled rejection that leaves a healthy-looking daemon running.
62
+ shutdown.catch((failure) => { console.error(failure.message); resolveDone(1); });
63
+ return shutdown;
64
+ };
65
+ const signalStop = () => { void stop(); };
66
+ try {
67
+ writeFileSync(lock, JSON.stringify({ pid: process.pid }) + "\n");
68
+ const store = new JobStore(directory, { version: 1, workspace, codexHome: home });
69
+ runtime = new Runtime({ store, workspace, home, command,
70
+ onFailure: (error) => { console.error(`Headless runtime stopped: ${error.message}`); void stop(error); } });
71
+ writeFileSync(lockPath, JSON.stringify({ pid: process.pid, codexPid: runtime.codex.child.pid }) + "\n");
72
+ process.on("SIGTERM", signalStop);
73
+ process.on("SIGINT", signalStop);
74
+ await runtime.codex.initialize();
75
+ federation = new HeadlessFederation({ directory, runtime, profile, onFailure: (error) => { void stop(error); } });
76
+ await federation.start();
77
+ if (shutdown) return done;
78
+ server = net.createServer((socket) => {
79
+ if (sockets.size >= 64) { socket.destroy(); return; }
80
+ sockets.add(socket);
81
+ socket.on("close", () => sockets.delete(socket));
82
+ socket.on("error", () => socket.destroy());
83
+ socket.setTimeout(60000, () => socket.destroy());
84
+ let accepted = false;
85
+ readMessages(socket, MAX_REQUEST_BYTES, (message) => {
86
+ if (accepted) { socket.destroy(); return; }
87
+ accepted = true;
88
+ const respond = (response) => {
89
+ if (socket.destroyed) return;
90
+ const data = JSON.stringify(response) + "\n";
91
+ if (Buffer.byteLength(data) > MAX_RESPONSE_BYTES) {
92
+ socket.end(JSON.stringify({ error: "Response exceeds 32 MiB; inspect the thread using a compatible Codex client" }) + "\n");
93
+ } else socket.end(data);
94
+ };
95
+ if (message.version !== 1 || typeof message.method !== "string") {
96
+ respond({ error: "Unsupported headless protocol" });
97
+ return;
98
+ }
99
+ Promise.resolve().then(() => {
100
+ const params = message.params || {};
101
+ switch (message.method) {
102
+ case "federation-status": return federation.status();
103
+ case "federation-join": return federation.join(params);
104
+ case "federation-invite": return federation.invite(params);
105
+ case "federation-leave": return federation.leave();
106
+ case "federation-revoke": return federation.revoke(params.peer);
107
+ default: return runtime.handle(message.method, params);
108
+ }
109
+ })
110
+ .then((result) => respond({ result }), (error) => respond({ error: error.message }));
111
+ }, () => socket.destroy());
112
+ });
113
+ await new Promise((resolve, reject) => {
114
+ server.once("error", reject);
115
+ server.listen(socketPath, () => { listening = true; resolve(); });
116
+ });
117
+ if (shutdown) { server.close(); return await done; }
118
+ server.on("error", (error) => { void stop(error); });
119
+ console.log(JSON.stringify({ ready: true, socket: socketPath, pid: process.pid }));
120
+ return await done;
121
+ } catch (error) {
122
+ await stop(error);
123
+ throw error;
124
+ }
125
+ }