@phren/agent 0.0.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.
Files changed (61) hide show
  1. package/dist/agent-loop.js +328 -0
  2. package/dist/bin.js +3 -0
  3. package/dist/checkpoint.js +103 -0
  4. package/dist/commands.js +292 -0
  5. package/dist/config.js +139 -0
  6. package/dist/context/pruner.js +62 -0
  7. package/dist/context/token-counter.js +28 -0
  8. package/dist/cost.js +71 -0
  9. package/dist/index.js +284 -0
  10. package/dist/mcp-client.js +168 -0
  11. package/dist/memory/anti-patterns.js +69 -0
  12. package/dist/memory/auto-capture.js +72 -0
  13. package/dist/memory/context-flush.js +24 -0
  14. package/dist/memory/context.js +170 -0
  15. package/dist/memory/error-recovery.js +58 -0
  16. package/dist/memory/project-context.js +77 -0
  17. package/dist/memory/session.js +100 -0
  18. package/dist/multi/agent-colors.js +41 -0
  19. package/dist/multi/child-entry.js +173 -0
  20. package/dist/multi/coordinator.js +263 -0
  21. package/dist/multi/diff-renderer.js +175 -0
  22. package/dist/multi/markdown.js +96 -0
  23. package/dist/multi/presets.js +107 -0
  24. package/dist/multi/progress.js +32 -0
  25. package/dist/multi/spawner.js +219 -0
  26. package/dist/multi/tui-multi.js +626 -0
  27. package/dist/multi/types.js +7 -0
  28. package/dist/permissions/allowlist.js +61 -0
  29. package/dist/permissions/checker.js +111 -0
  30. package/dist/permissions/prompt.js +190 -0
  31. package/dist/permissions/sandbox.js +95 -0
  32. package/dist/permissions/shell-safety.js +74 -0
  33. package/dist/permissions/types.js +2 -0
  34. package/dist/plan.js +38 -0
  35. package/dist/providers/anthropic.js +170 -0
  36. package/dist/providers/codex-auth.js +197 -0
  37. package/dist/providers/codex.js +265 -0
  38. package/dist/providers/ollama.js +142 -0
  39. package/dist/providers/openai-compat.js +163 -0
  40. package/dist/providers/openrouter.js +116 -0
  41. package/dist/providers/resolve.js +39 -0
  42. package/dist/providers/retry.js +55 -0
  43. package/dist/providers/types.js +2 -0
  44. package/dist/repl.js +180 -0
  45. package/dist/spinner.js +46 -0
  46. package/dist/system-prompt.js +31 -0
  47. package/dist/tools/edit-file.js +31 -0
  48. package/dist/tools/git.js +98 -0
  49. package/dist/tools/glob.js +65 -0
  50. package/dist/tools/grep.js +108 -0
  51. package/dist/tools/lint-test.js +76 -0
  52. package/dist/tools/phren-finding.js +35 -0
  53. package/dist/tools/phren-search.js +44 -0
  54. package/dist/tools/phren-tasks.js +71 -0
  55. package/dist/tools/read-file.js +44 -0
  56. package/dist/tools/registry.js +46 -0
  57. package/dist/tools/shell.js +48 -0
  58. package/dist/tools/types.js +2 -0
  59. package/dist/tools/write-file.js +27 -0
  60. package/dist/tui.js +451 -0
  61. package/package.json +39 -0
@@ -0,0 +1,219 @@
1
+ /**
2
+ * Agent spawner — forks child agent processes and manages their lifecycle.
3
+ *
4
+ * Usage:
5
+ * const spawner = new AgentSpawner();
6
+ * const id = spawner.spawn({ task: "fix the bug", ... });
7
+ * spawner.on("done", (agentId, result) => { ... });
8
+ * spawner.cancel(id);
9
+ * await spawner.shutdown();
10
+ */
11
+ import { fork } from "node:child_process";
12
+ import { fileURLToPath } from "node:url";
13
+ import { dirname, join } from "node:path";
14
+ import { EventEmitter } from "node:events";
15
+ const __filename = fileURLToPath(import.meta.url);
16
+ const __dirname = dirname(__filename);
17
+ /** Resolved path to the child-entry module. */
18
+ const CHILD_ENTRY = join(__dirname, "child-entry.js");
19
+ /** Keys forwarded from the parent env into child processes. */
20
+ const ENV_FORWARD_KEYS = [
21
+ "OPENROUTER_API_KEY",
22
+ "ANTHROPIC_API_KEY",
23
+ "OPENAI_API_KEY",
24
+ "PHREN_AGENT_PROVIDER",
25
+ "PHREN_AGENT_MODEL",
26
+ "PHREN_OLLAMA_URL",
27
+ "PHREN_PATH",
28
+ "PHREN_PROFILE",
29
+ "PHREN_DEBUG",
30
+ "HOME",
31
+ "PATH",
32
+ "NODE_EXTRA_CA_CERTS",
33
+ ];
34
+ export class AgentSpawner extends EventEmitter {
35
+ agents = new Map();
36
+ processes = new Map();
37
+ nextId = 1;
38
+ /** Spawn a new child agent. Returns the agent ID. */
39
+ spawn(opts) {
40
+ const agentId = `agent-${this.nextId++}`;
41
+ // Build forwarded env
42
+ const childEnv = {};
43
+ for (const key of ENV_FORWARD_KEYS) {
44
+ if (process.env[key])
45
+ childEnv[key] = process.env[key];
46
+ }
47
+ if (opts.env)
48
+ Object.assign(childEnv, opts.env);
49
+ const payload = {
50
+ type: "spawn",
51
+ agentId,
52
+ task: opts.task,
53
+ cwd: opts.cwd ?? process.cwd(),
54
+ provider: opts.provider,
55
+ model: opts.model,
56
+ project: opts.project,
57
+ permissions: opts.permissions ?? "auto-confirm",
58
+ maxTurns: opts.maxTurns ?? 50,
59
+ budget: opts.budget ?? null,
60
+ plan: opts.plan ?? false,
61
+ verbose: opts.verbose ?? false,
62
+ env: childEnv,
63
+ };
64
+ const entry = {
65
+ id: agentId,
66
+ task: opts.task,
67
+ status: "starting",
68
+ startedAt: Date.now(),
69
+ };
70
+ this.agents.set(agentId, entry);
71
+ // Fork the child process
72
+ const child = fork(CHILD_ENTRY, [], {
73
+ stdio: ["pipe", "pipe", "pipe", "ipc"],
74
+ env: { ...childEnv, FORCE_COLOR: "0" },
75
+ cwd: opts.cwd ?? process.cwd(),
76
+ });
77
+ this.processes.set(agentId, child);
78
+ entry.pid = child.pid;
79
+ entry.status = "running";
80
+ // Send the spawn payload
81
+ child.send(payload);
82
+ // Handle IPC messages from child
83
+ child.on("message", (msg) => {
84
+ this.handleChildMessage(msg);
85
+ });
86
+ // Handle child exit
87
+ child.on("exit", (code) => {
88
+ const agent = this.agents.get(agentId);
89
+ if (agent && agent.status === "running") {
90
+ agent.status = code === 0 ? "done" : "error";
91
+ agent.finishedAt = Date.now();
92
+ if (code !== 0 && !agent.error) {
93
+ agent.error = `Process exited with code ${code}`;
94
+ }
95
+ }
96
+ this.processes.delete(agentId);
97
+ this.emit("exit", agentId, code);
98
+ });
99
+ // Capture stderr for error reporting
100
+ child.stderr?.on("data", (data) => {
101
+ const text = data.toString();
102
+ if (opts.verbose) {
103
+ this.emit("status", agentId, text);
104
+ }
105
+ });
106
+ return agentId;
107
+ }
108
+ handleChildMessage(msg) {
109
+ // DirectMessageEvent has from/to instead of agentId — handle before accessing msg.agentId
110
+ if (msg.type === "direct_message") {
111
+ this.routeDirectMessage(msg.from, msg.to, msg.content);
112
+ return;
113
+ }
114
+ const agent = this.agents.get(msg.agentId);
115
+ switch (msg.type) {
116
+ case "text_delta":
117
+ this.emit("text_delta", msg.agentId, msg.text);
118
+ break;
119
+ case "text_block":
120
+ this.emit("text_block", msg.agentId, msg.text);
121
+ break;
122
+ case "tool_start":
123
+ this.emit("tool_start", msg.agentId, msg.toolName, msg.input, msg.count);
124
+ break;
125
+ case "tool_end":
126
+ this.emit("tool_end", msg.agentId, msg.toolName, msg.input, msg.output, msg.isError, msg.durationMs);
127
+ break;
128
+ case "status":
129
+ this.emit("status", msg.agentId, msg.message);
130
+ break;
131
+ case "done":
132
+ if (agent) {
133
+ agent.status = "done";
134
+ agent.finishedAt = Date.now();
135
+ agent.result = msg.result;
136
+ }
137
+ this.emit("done", msg.agentId, msg.result);
138
+ break;
139
+ case "error":
140
+ if (agent) {
141
+ agent.status = "error";
142
+ agent.finishedAt = Date.now();
143
+ agent.error = msg.error;
144
+ }
145
+ this.emit("error", msg.agentId, msg.error);
146
+ break;
147
+ }
148
+ }
149
+ /** Route a direct message from one agent to another. */
150
+ routeDirectMessage(from, to, content) {
151
+ this.emit("message", from, to, content);
152
+ this.sendToAgent(to, content, from);
153
+ }
154
+ /** Cancel a running agent. */
155
+ cancel(agentId) {
156
+ const child = this.processes.get(agentId);
157
+ if (!child)
158
+ return false;
159
+ child.send({ type: "cancel", agentId, reason: "Cancelled by parent" });
160
+ // Give it a moment to clean up, then force kill
161
+ setTimeout(() => {
162
+ if (this.processes.has(agentId)) {
163
+ child.kill("SIGTERM");
164
+ }
165
+ }, 5000);
166
+ const agent = this.agents.get(agentId);
167
+ if (agent) {
168
+ agent.status = "cancelled";
169
+ agent.finishedAt = Date.now();
170
+ }
171
+ return true;
172
+ }
173
+ /** Send a direct message to a child agent via IPC. Returns true if delivered. */
174
+ sendToAgent(agentId, message, from) {
175
+ const child = this.processes.get(agentId);
176
+ if (!child)
177
+ return false;
178
+ child.send({ type: "deliver_message", from: from ?? "user", content: message });
179
+ return true;
180
+ }
181
+ /** Get the current state of an agent. */
182
+ getAgent(agentId) {
183
+ return this.agents.get(agentId);
184
+ }
185
+ /** List all agents. */
186
+ listAgents() {
187
+ return [...this.agents.values()];
188
+ }
189
+ /** Get agents by status. */
190
+ getAgentsByStatus(status) {
191
+ return [...this.agents.values()].filter((a) => a.status === status);
192
+ }
193
+ /** Shut down all running agents and wait for exit. */
194
+ async shutdown() {
195
+ const running = this.getAgentsByStatus("running");
196
+ for (const agent of running) {
197
+ this.cancel(agent.id);
198
+ }
199
+ // Wait for all processes to exit (max 10s)
200
+ if (this.processes.size > 0) {
201
+ await new Promise((resolve) => {
202
+ const check = () => {
203
+ if (this.processes.size === 0)
204
+ return resolve();
205
+ setTimeout(check, 100);
206
+ };
207
+ setTimeout(() => {
208
+ // Force kill remaining
209
+ for (const [id, child] of this.processes) {
210
+ child.kill("SIGKILL");
211
+ this.processes.delete(id);
212
+ }
213
+ resolve();
214
+ }, 10_000);
215
+ check();
216
+ });
217
+ }
218
+ }
219
+ }