copyrun 0.0.0-stage → 0.2.0

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.
@@ -0,0 +1,226 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/autopilot-worker.ts
4
+ import { spawnSync } from "child_process";
5
+ import { appendFileSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, renameSync, writeFileSync } from "fs";
6
+ import { homedir as homedir2 } from "os";
7
+ import { dirname, join as join3 } from "path";
8
+
9
+ // ../mcp/src/relevance.ts
10
+ var STOP = new Set(
11
+ "a an the to of for in on at by with without from into onto and or but not no is are was be been it its this that these those my our your their i me we you they he she them us do does did done can could should would will want need needs help please how what why when where which who add adding make making fix fixing build building set setting up get use using write writing create creating run running change update app apps api project code file files thing stuff some sometimes keeps keep way too very really just also new old one two all any every work works working doesn don isn won t s".split(" ")
12
+ );
13
+ var SYN_GROUPS = [
14
+ ["tracing", "trace", "opentelemetry", "otel", "span", "observability"],
15
+ ["secret", "key", "credential", "token", "leak", "leaked"],
16
+ ["memory", "heap", "oom", "leak"],
17
+ ["esm", "module", "commonjs", "cjs", "import", "require"],
18
+ ["big", "large", "huge", "size", "shrink", "slim", "smaller"],
19
+ ["downtime", "lock", "locking", "stall", "zero", "migration"],
20
+ ["column", "table", "schema", "alter", "migration", "migrate"],
21
+ ["slow", "performance", "speed", "fast", "latency"],
22
+ ["query", "queries", "n+1", "nplus1", "orm", "prefetch"],
23
+ ["flaky", "flake", "intermittent", "race", "nondeterministic"],
24
+ // frameworks aren't synonyms of each other: a Jest run isn't a Vitest run
25
+ ["test", "tests", "testing", "spec"],
26
+ ["upgrade", "migrate", "migration", "version"],
27
+ ["postgres", "postgresql", "pg", "sql", "database"],
28
+ ["docker", "dockerfile", "container", "image"],
29
+ ["webhook", "webhooks", "event", "events"],
30
+ ["dark", "theme"],
31
+ ["rate", "throttle", "limit", "limiting"],
32
+ ["xss", "sanitize", "sanitise", "injection"],
33
+ ["git", "history", "commit"],
34
+ ["next", "nextjs"]
35
+ ];
36
+ function stem(w) {
37
+ if (w.length > 5 && w.endsWith("ing")) return w.slice(0, -3);
38
+ if (w.length > 4 && w.endsWith("ed")) return w.slice(0, -2);
39
+ if (w.length > 4 && w.endsWith("ies")) return w.slice(0, -3) + "y";
40
+ if (w.length > 3 && w.endsWith("s") && !w.endsWith("ss")) return w.slice(0, -1);
41
+ return w;
42
+ }
43
+ var SYN = /* @__PURE__ */ new Map();
44
+ for (const g of SYN_GROUPS) for (const w of g.map(stem)) SYN.set(w, /* @__PURE__ */ new Set([...SYN.get(w) ?? [], ...g.map(stem)]));
45
+ var STACK = new Set(
46
+ "express react next nextjs vue svelte angular django flask fastapi rails ruby python node nodejs js javascript ts typescript go golang rust java kotlin swift php laravel spring dotnet csharp deno bun vite webpack tailwind".split(" ").map(stem)
47
+ );
48
+ var COMMON = new Set(
49
+ "event handler api server client data function method error bug test fail page request response route endpoint user config setting value type class service process script check safe simple issue problem feature support handle input output result status job task call".split(" ").map(stem)
50
+ );
51
+
52
+ // src/autopilot-logic.ts
53
+ var AUTOPILOT_STATE_FILE = "autopilot.json";
54
+ var AUTOPILOT_LOG_FILE = "autopilot.log";
55
+ var MAX_SESSIONS = 2e3;
56
+ var counts = (s) => s.counts ?? { consults: 0, matches: 0, filed: 0, failed: 0 };
57
+ function trim(s) {
58
+ const entries = Object.entries(s.sessions ?? {});
59
+ if (entries.length <= MAX_SESSIONS) return s;
60
+ const at = (m) => (m.filedAt || m.refused ? 1e15 : 0) + (m.filedAt ?? m.consultedAt ?? m.checkedMtime ?? 0);
61
+ const keep = entries.sort((a, b) => at(b[1]) - at(a[1])).slice(0, MAX_SESSIONS);
62
+ return { ...s, sessions: Object.fromEntries(keep) };
63
+ }
64
+ function shareArgs(transcriptPath) {
65
+ return ["share", transcriptPath, "--autopilot", "--yes", "--confidential", "remove", "--no-recipe", "--json"];
66
+ }
67
+ var FINAL_REFUSAL_CODES = /* @__PURE__ */ new Set(["sensitive_health_data", "autopilot_not_allowed", "autopilot_filing_off", "pro_required"]);
68
+ function finalRefusal(body) {
69
+ if (!body || body.ok === true) return void 0;
70
+ const code = typeof body.code === "string" ? body.code : void 0;
71
+ if (code && (code.startsWith("policy_") || FINAL_REFUSAL_CODES.has(code))) return code;
72
+ if (body.policyBlocked === true) return "policy_blocked";
73
+ return void 0;
74
+ }
75
+ function recordFiled(state, sessionId, runId, now, error, refused) {
76
+ const c = counts(state);
77
+ const prev = { ...state.sessions?.[sessionId] };
78
+ if (runId) {
79
+ delete prev.failures;
80
+ delete prev.lastError;
81
+ }
82
+ if (!runId && refused) {
83
+ return trim({
84
+ ...state,
85
+ sessions: { ...state.sessions, [sessionId]: { ...prev, refused, ...error ? { lastError: error.slice(0, 200) } : {} } },
86
+ counts: { ...c, refused: (c.refused ?? 0) + 1 }
87
+ });
88
+ }
89
+ const memo = runId ? { ...prev, filedAt: now, runId } : { ...prev, failures: (prev.failures ?? 0) + 1, ...error ? { lastError: error.slice(0, 200) } : {} };
90
+ return trim({
91
+ ...state,
92
+ sessions: { ...state.sessions, [sessionId]: memo },
93
+ counts: { ...c, filed: c.filed + (runId ? 1 : 0), failed: c.failed + (runId ? 0 : 1) }
94
+ });
95
+ }
96
+ var SWEEP_QUIET_MS = 10 * 6e4;
97
+ var SWEEP_LOCK_STALE_MS = 30 * 6e4;
98
+ function sweepArgs(agent, hints) {
99
+ const named = hints.path ?? hints.id;
100
+ return ["autopilot", "sweep", "--agent", agent, ...named ? ["--session", named] : [], "--json"];
101
+ }
102
+
103
+ // src/config.ts
104
+ import { randomUUID } from "crypto";
105
+ import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
106
+ import { homedir } from "os";
107
+ import { join as join2 } from "path";
108
+
109
+ // src/managed.ts
110
+ import { execFileSync } from "child_process";
111
+ import { existsSync, readFileSync } from "fs";
112
+ import { join } from "path";
113
+
114
+ // src/config.ts
115
+ var VERSION = "0.2.0";
116
+ var DIR = process.env.COPYRUN_HOME ?? join2(homedir(), ".copyrun");
117
+
118
+ // src/autopilot-worker.ts
119
+ var HOME = process.env.COPYRUN_HOME ?? join3(homedir2(), ".copyrun");
120
+ function log(line) {
121
+ try {
122
+ mkdirSync(HOME, { recursive: true, mode: 448 });
123
+ appendFileSync(join3(HOME, AUTOPILOT_LOG_FILE), `${(/* @__PURE__ */ new Date()).toISOString()} ${line}
124
+ `, { mode: 384 });
125
+ } catch {
126
+ }
127
+ }
128
+ function recordedCli() {
129
+ try {
130
+ const cfg = JSON.parse(readFileSync2(join3(HOME, "config.json"), "utf8"));
131
+ return [cfg.autopilot?.cli, cfg.memory?.cli].find((p) => typeof p === "string" && p.endsWith(".js") && existsSync2(p));
132
+ } catch {
133
+ return void 0;
134
+ }
135
+ }
136
+ function cli(args) {
137
+ const local = process.env.COPYRUN_CLI ?? recordedCli();
138
+ const [cmd, argv] = local ? [process.execPath, [local, ...args]] : [process.platform === "win32" ? "npx.cmd" : "npx", ["-y", `copyrun@${VERSION}`, ...args]];
139
+ const r = spawnSync(cmd, argv, { encoding: "utf8", timeout: 20 * 6e4, env: { ...process.env, COPYRUN_HOME: HOME, COPYRUN_AUTOPILOT: "1" }, stdio: ["ignore", "pipe", "pipe"] });
140
+ return { status: r.status, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
141
+ }
142
+ var lastJson = (out) => {
143
+ for (const line of out.trim().split("\n").reverse()) {
144
+ try {
145
+ const v = JSON.parse(line);
146
+ if (v && typeof v === "object") return v;
147
+ } catch {
148
+ }
149
+ }
150
+ return void 0;
151
+ };
152
+ var why = (body, r) => String(body?.message ?? body?.error ?? r.stderr.replace(/\x1b\[[0-9;]*m/g, "").trim().split("\n").filter(Boolean).pop()?.slice(0, 200) ?? `exit ${r.status}`);
153
+ function sweep(agent, path, id) {
154
+ const r = cli(sweepArgs(agent, { path: path || void 0, id: id || void 0 }));
155
+ const body = lastJson(r.stdout);
156
+ if (r.status !== 0 && !body) log(`sweep ${agent}: failed (${why(body, r)})`);
157
+ }
158
+ function filingOff() {
159
+ try {
160
+ const path = join3(HOME, "config.json");
161
+ const cfg = JSON.parse(readFileSync2(path, "utf8"));
162
+ const ap = cfg.autopilot && typeof cfg.autopilot === "object" ? cfg.autopilot : {};
163
+ const tmp = `${path}.${process.pid}.tmp`;
164
+ writeFileSync(tmp, JSON.stringify({ ...cfg, autopilot: { ...ap, file: false, needsPro: true } }, null, 2), { mode: 384 });
165
+ renameSync(tmp, path);
166
+ } catch {
167
+ }
168
+ }
169
+ function fileOne(sessionId, transcript) {
170
+ const statePath = join3(HOME, AUTOPILOT_STATE_FILE);
171
+ const share = cli(shareArgs(transcript));
172
+ const body = lastJson(share.stdout);
173
+ const runId = body?.ok === true && typeof body.id === "string" ? body.id : void 0;
174
+ let state = {};
175
+ try {
176
+ state = JSON.parse(readFileSync2(statePath, "utf8"));
177
+ } catch {
178
+ }
179
+ const refused = runId ? void 0 : finalRefusal(body);
180
+ state = recordFiled(state, sessionId, runId, Date.now(), runId ? void 0 : why(body, share), refused);
181
+ try {
182
+ const tmp = `${statePath}.${process.pid}.tmp`;
183
+ writeFileSync(tmp, JSON.stringify(state), { mode: 384 });
184
+ renameSync(tmp, statePath);
185
+ } catch {
186
+ }
187
+ if (!runId && body?.code === "pro_required") {
188
+ filingOff();
189
+ log(`file ${sessionId}: not filed: filing sessions into private memory is part of Pro. Filing is off; lookups stay on (npx copyrun autopilot status)`);
190
+ return;
191
+ }
192
+ if (refused) {
193
+ log(`file ${sessionId}: refused (${refused}: ${why(body, share)}); final, not retried`);
194
+ return;
195
+ }
196
+ if (!runId) {
197
+ log(`file ${sessionId}: not filed (${why(body, share)}); the next sweep tries again`);
198
+ return;
199
+ }
200
+ log(`file ${sessionId}: filed ${typeof body?.visibility === "string" ? `(${body.visibility}${typeof body.org === "string" ? ` to ${body.org}` : ""}) ` : ""}as ${runId} ${typeof body?.url === "string" ? body.url : ""}`);
201
+ const out = join3(HOME, "attestations", `${runId}.copyrun-run.intoto.json`);
202
+ try {
203
+ mkdirSync(dirname(out), { recursive: true, mode: 448 });
204
+ } catch {
205
+ }
206
+ const att = cli(["attest", runId, "--no-commit", "--out", out, "--json"]);
207
+ log(att.status === 0 ? `attest ${runId}: ${out}` : `attest ${runId}: failed (exit ${att.status})`);
208
+ }
209
+ function main() {
210
+ const args = process.argv.slice(2);
211
+ if (args[0] === "--index") {
212
+ const r = cli(["memory", "index", "--quiet", "--json"]);
213
+ const body = lastJson(r.stdout);
214
+ if (body && typeof body.total === "number") {
215
+ if (body.added || body.updated || body.dropped) log(`memory index: ${body.total} sessions (${Number(body.added) || 0} new, ${Number(body.updated) || 0} updated${body.dropped ? `, ${Number(body.dropped)} dropped by the size cap` : ""}); on this machine only`);
216
+ } else if (r.status !== 0) log(`memory index: failed (${why(body, r)})`);
217
+ return;
218
+ }
219
+ if (args[0] === "--sweep") {
220
+ if (args[1] === "codex" || args[1] === "cursor") sweep(args[1], args[2], args[3]);
221
+ return;
222
+ }
223
+ const [sessionId, transcript] = args;
224
+ if (sessionId && transcript) fileOne(sessionId, transcript);
225
+ }
226
+ main();