@evo-hq/pi-evo 0.4.2-alpha.2

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 ADDED
@@ -0,0 +1,38 @@
1
+ # @evo-hq/pi-evo
2
+
3
+ Evo plugin for [pi-coding-agent](https://pi.dev) — adds the `/discover`,
4
+ `/optimize`, `/subagent`, and `/infra-setup` skills, plus a `before_provider_request`
5
+ extension that consumes `evo direct` mid-run inject messages.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pi install npm:@evo-hq/pi-evo
11
+ ```
12
+
13
+ Pi auto-registers the extension under `~/.pi/agent/extensions/` and discovers
14
+ the skills under `~/.pi/agent/skills/`. No additional setup steps.
15
+
16
+ A parallel-subagent provider ([`pi-subagents`](https://pi.dev/packages/pi-subagents))
17
+ is bundled — pi's default toolkit has no fanout primitive, and evo's `optimize`
18
+ skill needs one to run multiple experiments per round.
19
+
20
+ ## What ships in this package
21
+
22
+ | Path | Purpose |
23
+ |---|---|
24
+ | `extensions/evo/index.js` | Bundled JS that hooks `before_provider_request`, drains the workspace inject queue, appends `[evo direct]` directives to the outgoing LLM payload |
25
+ | `skills/discover/` | First-run setup: explore repo, propose optimization dimensions, build benchmark, run first experiment |
26
+ | `skills/optimize/` | The search loop: parallel subagents form hypotheses, edit, get scored, frontier picks next branch |
27
+ | `skills/subagent/` | Per-experiment brief contract for the optimize round's fanout |
28
+ | `skills/infra-setup/` | Provider matrix for remote-sandbox backends (Modal, E2B, Daytona, etc.) |
29
+
30
+ ## Versioning
31
+
32
+ This package is versioned in lockstep with the [evo-hq/evo](https://github.com/evo-hq/evo)
33
+ release line. The skills + bundled extension are sync'd from `plugins/evo/` in
34
+ the source repo at publish time (see `scripts/sync-from-source.sh`).
35
+
36
+ ## Source
37
+
38
+ [github.com/evo-hq/evo](https://github.com/evo-hq/evo) (Apache-2.0)
@@ -0,0 +1,244 @@
1
+ // ../opencode_plugin/drain.ts
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ var QUEUE_SCHEMA_VERSION = 1;
5
+ function injectRoot(runDir) {
6
+ return path.join(runDir, "inject");
7
+ }
8
+ function sessionFile(runDir, sid) {
9
+ return path.join(injectRoot(runDir), "sessions", `${sid}.json`);
10
+ }
11
+ function workspaceEventsPath(runDir) {
12
+ return path.join(injectRoot(runDir), "events", "workspace.jsonl");
13
+ }
14
+ function expEventsPath(runDir, expId) {
15
+ return path.join(injectRoot(runDir), "events", `${expId}.jsonl`);
16
+ }
17
+ function offsetFile(runDir, sid) {
18
+ return path.join(injectRoot(runDir), "offsets", `${sid}.json`);
19
+ }
20
+ function markerFile(runDir, sid) {
21
+ return path.join(injectRoot(runDir), "markers", `${sid}.flag`);
22
+ }
23
+ function readJsonOrNull(p) {
24
+ try {
25
+ return JSON.parse(fs.readFileSync(p, "utf8"));
26
+ } catch {
27
+ return null;
28
+ }
29
+ }
30
+ function atomicWriteJson(p, data) {
31
+ fs.mkdirSync(path.dirname(p), { recursive: true });
32
+ const tmp = `${p}.tmp.${process.pid}`;
33
+ fs.writeFileSync(tmp, JSON.stringify(data));
34
+ fs.renameSync(tmp, p);
35
+ }
36
+ function unlinkIfExists(p) {
37
+ try {
38
+ fs.unlinkSync(p);
39
+ } catch {}
40
+ }
41
+ function readEventsAfter(queuePath, afterId) {
42
+ if (!fs.existsSync(queuePath))
43
+ return [];
44
+ let text;
45
+ try {
46
+ text = fs.readFileSync(queuePath, "utf8");
47
+ } catch {
48
+ return [];
49
+ }
50
+ const out = [];
51
+ for (const line of text.split(`
52
+ `)) {
53
+ const trimmed = line.trim();
54
+ if (!trimmed)
55
+ continue;
56
+ let rec;
57
+ try {
58
+ rec = JSON.parse(trimmed);
59
+ } catch {
60
+ continue;
61
+ }
62
+ const recId = rec?.id;
63
+ if (typeof recId !== "string")
64
+ continue;
65
+ if (afterId === null || recId > afterId) {
66
+ out.push(rec);
67
+ }
68
+ }
69
+ return out;
70
+ }
71
+ function readOffset(runDir, sid, queue) {
72
+ const data = readJsonOrNull(offsetFile(runDir, sid));
73
+ if (!data)
74
+ return null;
75
+ if (queue === "workspace")
76
+ return data.last_workspace_event_id ?? null;
77
+ if (queue === "exp")
78
+ return data.last_exp_event_id ?? null;
79
+ return null;
80
+ }
81
+ function nowIso() {
82
+ return new Date().toISOString().replace(/\.\d{3}Z$/, "+00:00");
83
+ }
84
+ function writeOffset(runDir, sid, opts) {
85
+ const p = offsetFile(runDir, sid);
86
+ let data = readJsonOrNull(p) ?? {};
87
+ data.schema_version = QUEUE_SCHEMA_VERSION;
88
+ data.session_id = sid;
89
+ if (opts.workspaceId !== undefined && opts.workspaceId !== null) {
90
+ data.last_workspace_event_id = opts.workspaceId;
91
+ }
92
+ if (opts.expId !== undefined && opts.expId !== null) {
93
+ data.last_exp_event_id = opts.expId;
94
+ }
95
+ data.updated_at = nowIso();
96
+ atomicWriteJson(p, data);
97
+ }
98
+ function formatDirectiveText(events) {
99
+ const lines = [];
100
+ for (const ev of events) {
101
+ if (ev.text)
102
+ lines.push(`[evo direct] ${ev.text}`);
103
+ }
104
+ return lines.join(`
105
+ `);
106
+ }
107
+ function getSession(runDir, sid) {
108
+ return readJsonOrNull(sessionFile(runDir, sid));
109
+ }
110
+ function isRegistered(runDir, sid) {
111
+ return fs.existsSync(sessionFile(runDir, sid));
112
+ }
113
+ var REGISTRY_SCHEMA_VERSION = 1;
114
+ function registerSession(runDir, sid, host, expId = null) {
115
+ const p = sessionFile(runDir, sid);
116
+ const now = nowIso();
117
+ const existing = readJsonOrNull(p);
118
+ if (existing) {
119
+ existing.last_seen_at = now;
120
+ atomicWriteJson(p, existing);
121
+ return;
122
+ }
123
+ const rec = {
124
+ schema_version: REGISTRY_SCHEMA_VERSION,
125
+ session_id: sid,
126
+ host,
127
+ pid: process.pid,
128
+ registered_at: now,
129
+ last_seen_at: now,
130
+ exp_id: expId,
131
+ parent_session_id: null
132
+ };
133
+ atomicWriteJson(p, rec);
134
+ }
135
+ function findEvoRunDir(cwd) {
136
+ const envRunDir = process.env.EVO_RUN_DIR;
137
+ if (envRunDir)
138
+ return envRunDir;
139
+ let dir = cwd || process.cwd();
140
+ while (dir !== "/" && dir !== "") {
141
+ const evoDir = path.join(dir, ".evo");
142
+ if (fs.existsSync(evoDir)) {
143
+ try {
144
+ const runs = fs.readdirSync(evoDir).filter((n) => n.startsWith("run_")).sort();
145
+ if (runs.length === 0)
146
+ return null;
147
+ return path.join(evoDir, runs[runs.length - 1]);
148
+ } catch {
149
+ return null;
150
+ }
151
+ }
152
+ const parent = path.dirname(dir);
153
+ if (parent === dir)
154
+ break;
155
+ dir = parent;
156
+ }
157
+ return null;
158
+ }
159
+ function drainSession(runDir, sessionId) {
160
+ const sess = getSession(runDir, sessionId);
161
+ if (!sess) {
162
+ unlinkIfExists(markerFile(runDir, sessionId));
163
+ return { text: null, newWorkspaceOffset: null, newExpOffset: null };
164
+ }
165
+ const expId = sess.exp_id;
166
+ let events = [];
167
+ let newWorkspaceOffset = null;
168
+ let newExpOffset = null;
169
+ if (expId) {
170
+ const lastId = readOffset(runDir, sessionId, "exp");
171
+ const newEvents = readEventsAfter(expEventsPath(runDir, expId), lastId);
172
+ events = newEvents;
173
+ if (newEvents.length > 0)
174
+ newExpOffset = newEvents[newEvents.length - 1].id;
175
+ } else {
176
+ const lastId = readOffset(runDir, sessionId, "workspace");
177
+ const newEvents = readEventsAfter(workspaceEventsPath(runDir), lastId);
178
+ events = newEvents;
179
+ if (newEvents.length > 0)
180
+ newWorkspaceOffset = newEvents[newEvents.length - 1].id;
181
+ }
182
+ const text = events.length > 0 ? formatDirectiveText(events) : null;
183
+ if (newWorkspaceOffset || newExpOffset) {
184
+ writeOffset(runDir, sessionId, {
185
+ workspaceId: newWorkspaceOffset,
186
+ expId: newExpOffset
187
+ });
188
+ }
189
+ unlinkIfExists(markerFile(runDir, sessionId));
190
+ return { text, newWorkspaceOffset, newExpOffset };
191
+ }
192
+
193
+ // index.ts
194
+ import * as crypto from "crypto";
195
+ function deriveSessionId() {
196
+ const hash = crypto.createHash("sha256").update(process.cwd()).digest("hex").slice(0, 12);
197
+ return `openclaw-${hash}`;
198
+ }
199
+ function register(api) {
200
+ const drainedTexts = [];
201
+ const ensureRegistered = () => {
202
+ const runDir = findEvoRunDir();
203
+ if (!runDir)
204
+ return null;
205
+ const sid = deriveSessionId();
206
+ if (!isRegistered(runDir, sid)) {
207
+ registerSession(runDir, sid, "openclaw");
208
+ }
209
+ return { sid, runDir };
210
+ };
211
+ const appendToPayload = (event, text) => {
212
+ if (Array.isArray(event.payload?.input)) {
213
+ event.payload.input.push({
214
+ role: "user",
215
+ content: [{ type: "input_text", text }]
216
+ });
217
+ } else if (Array.isArray(event.payload?.messages)) {
218
+ event.payload.messages.push({
219
+ role: "user",
220
+ content: [{ type: "text", text }]
221
+ });
222
+ }
223
+ };
224
+ api.on("session_start", () => {
225
+ ensureRegistered();
226
+ });
227
+ api.on("before_provider_request", (event, _ctx) => {
228
+ const ctx = ensureRegistered();
229
+ if (!ctx)
230
+ return;
231
+ const result = drainSession(ctx.runDir, ctx.sid);
232
+ if (result.text)
233
+ drainedTexts.push(result.text);
234
+ if (drainedTexts.length === 0)
235
+ return;
236
+ const combined = drainedTexts.join(`
237
+ `);
238
+ appendToPayload(event, combined);
239
+ return event.payload;
240
+ });
241
+ }
242
+ export {
243
+ register as default
244
+ };
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@evo-hq/pi-evo",
3
+ "version": "0.4.2-alpha.2",
4
+ "description": "Evo plugin for pi-coding-agent: optimize/discover/subagent skills + mid-run inject extension.",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "keywords": [
9
+ "pi-package",
10
+ "pi",
11
+ "pi-coding-agent",
12
+ "evo",
13
+ "evo-hq",
14
+ "coding-agent",
15
+ "optimization"
16
+ ],
17
+ "pi": {
18
+ "extensions": ["./extensions"],
19
+ "skills": ["./skills"]
20
+ },
21
+ "dependencies": {
22
+ "pi-subagents": "*"
23
+ },
24
+ "bundledDependencies": ["pi-subagents"],
25
+ "peerDependencies": {
26
+ "@earendil-works/pi-ai": "*",
27
+ "pi-agent-core": "*",
28
+ "pi-coding-agent": "*",
29
+ "pi-tui": "*",
30
+ "typebox": "*"
31
+ },
32
+ "files": ["extensions", "skills", "README.md", "LICENSE"],
33
+ "license": "Apache-2.0",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/evo-hq/evo.git",
37
+ "directory": "plugins/evo/npm"
38
+ },
39
+ "homepage": "https://github.com/evo-hq/evo#readme",
40
+ "bugs": "https://github.com/evo-hq/evo/issues"
41
+ }