@prestyj/cli 5.3.0 → 5.3.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/dist/app-sidecar.js +581 -55
- package/dist/app-sidecar.js.map +1 -1
- package/dist/core/agent-session-compaction.test.js +89 -0
- package/dist/core/agent-session-compaction.test.js.map +1 -1
- package/dist/core/agent-session.d.ts +71 -3
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +261 -47
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/autopilot-cycle.d.ts +69 -17
- package/dist/core/autopilot-cycle.d.ts.map +1 -1
- package/dist/core/autopilot-cycle.js +82 -13
- package/dist/core/autopilot-cycle.js.map +1 -1
- package/dist/core/autopilot-cycle.test.js +206 -10
- package/dist/core/autopilot-cycle.test.js.map +1 -1
- package/dist/core/autopilot-gate.d.ts +48 -22
- package/dist/core/autopilot-gate.d.ts.map +1 -1
- package/dist/core/autopilot-gate.js +110 -1
- package/dist/core/autopilot-gate.js.map +1 -1
- package/dist/core/autopilot-gate.test.js +139 -3
- package/dist/core/autopilot-gate.test.js.map +1 -1
- package/dist/core/autopilot-verdict.d.ts +6 -0
- package/dist/core/autopilot-verdict.d.ts.map +1 -1
- package/dist/core/autopilot-verdict.js +75 -1
- package/dist/core/autopilot-verdict.js.map +1 -1
- package/dist/core/autopilot-verdict.test.js +63 -0
- package/dist/core/autopilot-verdict.test.js.map +1 -1
- package/dist/core/bash-spawn-benchmark.d.ts +2 -0
- package/dist/core/bash-spawn-benchmark.d.ts.map +1 -0
- package/dist/core/bash-spawn-benchmark.js +139 -0
- package/dist/core/bash-spawn-benchmark.js.map +1 -0
- package/dist/core/hashline-edit-benchmark.d.ts +33 -1
- package/dist/core/hashline-edit-benchmark.d.ts.map +1 -1
- package/dist/core/hashline-edit-benchmark.js +193 -21
- package/dist/core/hashline-edit-benchmark.js.map +1 -1
- package/dist/core/hashline-edit-benchmark.test.js +51 -2
- package/dist/core/hashline-edit-benchmark.test.js.map +1 -1
- package/dist/core/mcp/deferred-catalog.d.ts +28 -0
- package/dist/core/mcp/deferred-catalog.d.ts.map +1 -0
- package/dist/core/mcp/deferred-catalog.js +72 -0
- package/dist/core/mcp/deferred-catalog.js.map +1 -0
- package/dist/core/mcp/resolve-stdio.d.ts +19 -3
- package/dist/core/mcp/resolve-stdio.d.ts.map +1 -1
- package/dist/core/mcp/resolve-stdio.js +136 -5
- package/dist/core/mcp/resolve-stdio.js.map +1 -1
- package/dist/core/mcp/resolve-stdio.test.js +82 -1
- package/dist/core/mcp/resolve-stdio.test.js.map +1 -1
- package/dist/core/nolan-context.d.ts +26 -2
- package/dist/core/nolan-context.d.ts.map +1 -1
- package/dist/core/nolan-context.js +46 -4
- package/dist/core/nolan-context.js.map +1 -1
- package/dist/core/nolan-context.test.js +53 -12
- package/dist/core/nolan-context.test.js.map +1 -1
- package/dist/core/nolan-prompt.d.ts +2 -15
- package/dist/core/nolan-prompt.d.ts.map +1 -1
- package/dist/core/nolan-prompt.js +96 -15
- package/dist/core/nolan-prompt.js.map +1 -1
- package/dist/core/nolan-prompt.test.js +102 -7
- package/dist/core/nolan-prompt.test.js.map +1 -1
- package/dist/core/persistent-shell.d.ts +27 -0
- package/dist/core/persistent-shell.d.ts.map +1 -0
- package/dist/core/persistent-shell.js +150 -0
- package/dist/core/persistent-shell.js.map +1 -0
- package/dist/core/persistent-shell.test.d.ts +2 -0
- package/dist/core/persistent-shell.test.d.ts.map +1 -0
- package/dist/core/persistent-shell.test.js +84 -0
- package/dist/core/persistent-shell.test.js.map +1 -0
- package/dist/core/process-manager-dev-server-repro.test.js +34 -6
- package/dist/core/process-manager-dev-server-repro.test.js.map +1 -1
- package/dist/core/progress/engine.d.ts +23 -0
- package/dist/core/progress/engine.d.ts.map +1 -0
- package/dist/core/progress/engine.js +131 -0
- package/dist/core/progress/engine.js.map +1 -0
- package/dist/core/progress/engine.test.d.ts +2 -0
- package/dist/core/progress/engine.test.d.ts.map +1 -0
- package/dist/core/progress/engine.test.js +136 -0
- package/dist/core/progress/engine.test.js.map +1 -0
- package/dist/core/progress/git-xp.d.ts +16 -0
- package/dist/core/progress/git-xp.d.ts.map +1 -0
- package/dist/core/progress/git-xp.js +106 -0
- package/dist/core/progress/git-xp.js.map +1 -0
- package/dist/core/progress/git-xp.test.d.ts +2 -0
- package/dist/core/progress/git-xp.test.d.ts.map +1 -0
- package/dist/core/progress/git-xp.test.js +88 -0
- package/dist/core/progress/git-xp.test.js.map +1 -0
- package/dist/core/progress/ranks.d.ts +21 -0
- package/dist/core/progress/ranks.d.ts.map +1 -0
- package/dist/core/progress/ranks.js +141 -0
- package/dist/core/progress/ranks.js.map +1 -0
- package/dist/core/progress/ranks.test.d.ts +2 -0
- package/dist/core/progress/ranks.test.d.ts.map +1 -0
- package/dist/core/progress/ranks.test.js +59 -0
- package/dist/core/progress/ranks.test.js.map +1 -0
- package/dist/core/progress/rebuild.d.ts +7 -0
- package/dist/core/progress/rebuild.d.ts.map +1 -0
- package/dist/core/progress/rebuild.js +106 -0
- package/dist/core/progress/rebuild.js.map +1 -0
- package/dist/core/progress/rebuild.test.d.ts +2 -0
- package/dist/core/progress/rebuild.test.d.ts.map +1 -0
- package/dist/core/progress/rebuild.test.js +72 -0
- package/dist/core/progress/rebuild.test.js.map +1 -0
- package/dist/core/progress/store.d.ts +35 -0
- package/dist/core/progress/store.d.ts.map +1 -0
- package/dist/core/progress/store.js +200 -0
- package/dist/core/progress/store.js.map +1 -0
- package/dist/core/progress/store.test.d.ts +2 -0
- package/dist/core/progress/store.test.d.ts.map +1 -0
- package/dist/core/progress/store.test.js +108 -0
- package/dist/core/progress/store.test.js.map +1 -0
- package/dist/core/progress/types.d.ts +108 -0
- package/dist/core/progress/types.d.ts.map +1 -0
- package/dist/core/progress/types.js +3 -0
- package/dist/core/progress/types.js.map +1 -0
- package/dist/core/project-discovery.d.ts.map +1 -1
- package/dist/core/project-discovery.js +40 -9
- package/dist/core/project-discovery.js.map +1 -1
- package/dist/core/project-discovery.test.d.ts +2 -0
- package/dist/core/project-discovery.test.d.ts.map +1 -0
- package/dist/core/project-discovery.test.js +104 -0
- package/dist/core/project-discovery.test.js.map +1 -0
- package/dist/core/session-history.d.ts +51 -0
- package/dist/core/session-history.d.ts.map +1 -0
- package/dist/core/session-history.js +156 -0
- package/dist/core/session-history.js.map +1 -0
- package/dist/core/session-history.test.d.ts +2 -0
- package/dist/core/session-history.test.d.ts.map +1 -0
- package/dist/core/session-history.test.js +101 -0
- package/dist/core/session-history.test.js.map +1 -0
- package/dist/core/session-manager.d.ts +34 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +67 -0
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/session-manager.test.js +140 -1
- package/dist/core/session-manager.test.js.map +1 -1
- package/dist/core/settings-manager.d.ts +1 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +4 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/system-prompt.d.ts.map +1 -1
- package/dist/system-prompt.js +12 -8
- package/dist/system-prompt.js.map +1 -1
- package/dist/system-prompt.test.js +15 -20
- package/dist/system-prompt.test.js.map +1 -1
- package/dist/tools/bash.d.ts +1 -0
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +27 -2
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/edit.d.ts +9 -2
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +91 -10
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/edit.test.js +97 -0
- package/dist/tools/edit.test.js.map +1 -1
- package/dist/tools/plan-mode.test.js +25 -0
- package/dist/tools/plan-mode.test.js.map +1 -1
- package/dist/tools/prompt-hints.d.ts.map +1 -1
- package/dist/tools/prompt-hints.js +3 -0
- package/dist/tools/prompt-hints.js.map +1 -1
- package/dist/tools/subagent.d.ts.map +1 -1
- package/dist/tools/subagent.js +24 -1
- package/dist/tools/subagent.js.map +1 -1
- package/dist/tools/tool-search.d.ts +15 -0
- package/dist/tools/tool-search.d.ts.map +1 -0
- package/dist/tools/tool-search.js +35 -0
- package/dist/tools/tool-search.js.map +1 -0
- package/dist/ui/hooks/useAgentLoop.d.ts.map +1 -1
- package/dist/ui/hooks/useAgentLoop.js +22 -9
- package/dist/ui/hooks/useAgentLoop.js.map +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistent bash session for the bash tool's opt-in `persist` mode.
|
|
3
|
+
*
|
|
4
|
+
* One long-lived bash per instance; commands are written to stdin and
|
|
5
|
+
* delimited with a sentinel that carries the exit code. Benchmarked at ~0.3ms
|
|
6
|
+
* per call vs ~6.4ms for spawn-per-call (see bash-spawn-benchmark.ts), and —
|
|
7
|
+
* the real win — cd/env/shell state survive across calls.
|
|
8
|
+
*
|
|
9
|
+
* POSIX-only (needs a real bash). Callers must fall back to spawn-per-call
|
|
10
|
+
* when bash is unavailable (Windows cmd.exe fallback path).
|
|
11
|
+
*/
|
|
12
|
+
import { spawn } from "node:child_process";
|
|
13
|
+
import { randomUUID } from "node:crypto";
|
|
14
|
+
import { killProcessTree } from "../utils/process.js";
|
|
15
|
+
export class PersistentShell {
|
|
16
|
+
cwd;
|
|
17
|
+
env;
|
|
18
|
+
maxOutputBytes;
|
|
19
|
+
child = null;
|
|
20
|
+
buffer = "";
|
|
21
|
+
busy = false;
|
|
22
|
+
constructor(cwd, env, maxOutputBytes) {
|
|
23
|
+
this.cwd = cwd;
|
|
24
|
+
this.env = env;
|
|
25
|
+
this.maxOutputBytes = maxOutputBytes;
|
|
26
|
+
}
|
|
27
|
+
/** True while a previous persistent command is still running. */
|
|
28
|
+
get isBusy() {
|
|
29
|
+
return this.busy;
|
|
30
|
+
}
|
|
31
|
+
ensureChild() {
|
|
32
|
+
if (this.child && this.child.exitCode === null && !this.child.killed) {
|
|
33
|
+
return this.child;
|
|
34
|
+
}
|
|
35
|
+
// Fresh session: no rc files so startup is fast and deterministic.
|
|
36
|
+
const child = spawn("bash", ["--norc", "--noprofile"], {
|
|
37
|
+
cwd: this.cwd,
|
|
38
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
39
|
+
env: this.env,
|
|
40
|
+
detached: true,
|
|
41
|
+
});
|
|
42
|
+
// Don't let a lingering session shell keep the parent process alive.
|
|
43
|
+
child.unref();
|
|
44
|
+
this.child = child;
|
|
45
|
+
this.buffer = "";
|
|
46
|
+
return child;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Run one command in the session shell. Serialized by the tool's sequential
|
|
50
|
+
* execution mode; a concurrent call while busy is rejected defensively.
|
|
51
|
+
* On timeout or abort the whole session is killed (state is gone — the next
|
|
52
|
+
* call starts a fresh shell) because a wedged command cannot be safely
|
|
53
|
+
* skipped within the same shell.
|
|
54
|
+
*/
|
|
55
|
+
run(command, timeoutMs, signal, onChunk) {
|
|
56
|
+
if (this.busy) {
|
|
57
|
+
return Promise.resolve({
|
|
58
|
+
exitCode: 1,
|
|
59
|
+
output: "persistent shell is busy with a previous command",
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
this.busy = true;
|
|
63
|
+
const child = this.ensureChild();
|
|
64
|
+
const sentinel = `__GG_PSH_${randomUUID()}__`;
|
|
65
|
+
// `</dev/null` keeps stdin-reading commands (cat, read) from eating the
|
|
66
|
+
// next sentinel line instead of hanging the session.
|
|
67
|
+
const wrapped = `{ ${command}\n} </dev/null; echo "${sentinel}$?"\n`;
|
|
68
|
+
return new Promise((resolve) => {
|
|
69
|
+
let out = "";
|
|
70
|
+
let capped = false;
|
|
71
|
+
let done = false;
|
|
72
|
+
const finish = (result) => {
|
|
73
|
+
if (done)
|
|
74
|
+
return;
|
|
75
|
+
done = true;
|
|
76
|
+
child.stdout?.off("data", onData);
|
|
77
|
+
child.stderr?.off("data", onData);
|
|
78
|
+
child.off("exit", onExit);
|
|
79
|
+
child.off("error", onError);
|
|
80
|
+
clearTimeout(timer);
|
|
81
|
+
signal.removeEventListener("abort", onAbort);
|
|
82
|
+
this.busy = false;
|
|
83
|
+
resolve(result);
|
|
84
|
+
};
|
|
85
|
+
// When output is capped we stop growing `out` but MUST keep scanning for
|
|
86
|
+
// the sentinel — otherwise an over-cap command hangs until timeout and
|
|
87
|
+
// needlessly destroys the session. `scanTail` keeps a small rolling
|
|
88
|
+
// window across chunk boundaries so a split sentinel is still found.
|
|
89
|
+
let scanTail = "";
|
|
90
|
+
const checkSentinel = (scan, fromCapped) => {
|
|
91
|
+
const idx = scan.indexOf(sentinel);
|
|
92
|
+
if (idx === -1)
|
|
93
|
+
return;
|
|
94
|
+
const code = parseInt(scan.slice(idx + sentinel.length), 10);
|
|
95
|
+
const body = fromCapped ? out : scan.slice(0, idx);
|
|
96
|
+
finish({
|
|
97
|
+
exitCode: Number.isNaN(code) ? 1 : code,
|
|
98
|
+
output: body.replace(/\n$/, "") +
|
|
99
|
+
(fromCapped ? `\n[Output capped at ${this.maxOutputBytes} bytes]` : ""),
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
const onData = (d) => {
|
|
103
|
+
const text = d.toString("utf-8");
|
|
104
|
+
if (capped) {
|
|
105
|
+
scanTail = (scanTail + text).slice(-(sentinel.length + 16));
|
|
106
|
+
checkSentinel(scanTail, true);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
out += text;
|
|
110
|
+
if (out.length > this.maxOutputBytes) {
|
|
111
|
+
capped = true;
|
|
112
|
+
scanTail = out.slice(-(sentinel.length + 16));
|
|
113
|
+
out = out.slice(0, this.maxOutputBytes);
|
|
114
|
+
}
|
|
115
|
+
onChunk?.(text);
|
|
116
|
+
checkSentinel(capped ? scanTail : out, capped);
|
|
117
|
+
};
|
|
118
|
+
const timer = setTimeout(() => {
|
|
119
|
+
this.kill();
|
|
120
|
+
finish({ exitCode: "TIMEOUT", output: out });
|
|
121
|
+
}, timeoutMs);
|
|
122
|
+
const onAbort = () => {
|
|
123
|
+
this.kill();
|
|
124
|
+
finish({ exitCode: 1, output: out });
|
|
125
|
+
};
|
|
126
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
127
|
+
// `exit N` (or a crash) ends the session shell itself — the sentinel
|
|
128
|
+
// never prints, so settle from the shell's own exit code. The next run()
|
|
129
|
+
// starts a fresh session.
|
|
130
|
+
const onExit = (code) => {
|
|
131
|
+
this.child = null;
|
|
132
|
+
finish({ exitCode: code ?? 1, output: out.replace(/\n$/, "") });
|
|
133
|
+
};
|
|
134
|
+
child.on("exit", onExit);
|
|
135
|
+
const onError = () => finish({ exitCode: 1, output: "failed to spawn session bash" });
|
|
136
|
+
child.on("error", onError);
|
|
137
|
+
child.stdout?.on("data", onData);
|
|
138
|
+
child.stderr?.on("data", onData);
|
|
139
|
+
child.stdin?.write(wrapped);
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
/** Kill the session shell; the next run() starts a fresh one. */
|
|
143
|
+
kill() {
|
|
144
|
+
if (this.child?.pid)
|
|
145
|
+
killProcessTree(this.child.pid);
|
|
146
|
+
this.child = null;
|
|
147
|
+
this.busy = false;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=persistent-shell.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persistent-shell.js","sourceRoot":"","sources":["../../src/core/persistent-shell.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persistent-shell.test.d.ts","sourceRoot":"","sources":["../../src/core/persistent-shell.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach } from "vitest";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import { PersistentShell } from "./persistent-shell.js";
|
|
4
|
+
const isWindows = os.platform() === "win32";
|
|
5
|
+
const d = describe.skipIf(isWindows);
|
|
6
|
+
d("PersistentShell", () => {
|
|
7
|
+
let shell = null;
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
shell?.kill();
|
|
10
|
+
shell = null;
|
|
11
|
+
});
|
|
12
|
+
function make() {
|
|
13
|
+
shell = new PersistentShell(os.tmpdir(), { ...process.env, TERM: "dumb" }, 1024 * 1024);
|
|
14
|
+
return shell;
|
|
15
|
+
}
|
|
16
|
+
const signal = () => new AbortController().signal;
|
|
17
|
+
it("runs a command and returns output + exit code", async () => {
|
|
18
|
+
const sh = make();
|
|
19
|
+
const res = await sh.run("echo hello", 10_000, signal());
|
|
20
|
+
expect(res.exitCode).toBe(0);
|
|
21
|
+
expect(res.output).toBe("hello");
|
|
22
|
+
});
|
|
23
|
+
it("propagates non-zero exit codes", async () => {
|
|
24
|
+
const sh = make();
|
|
25
|
+
const res = await sh.run("exit 3", 10_000, signal());
|
|
26
|
+
expect(res.exitCode).toBe(3);
|
|
27
|
+
});
|
|
28
|
+
it("persists cd and env vars across calls — the point of the feature", async () => {
|
|
29
|
+
const sh = make();
|
|
30
|
+
await sh.run("cd / && export GG_PSH_TEST=alive", 10_000, signal());
|
|
31
|
+
const res = await sh.run('pwd; echo "$GG_PSH_TEST"', 10_000, signal());
|
|
32
|
+
expect(res.output).toBe("/\nalive");
|
|
33
|
+
});
|
|
34
|
+
it("stdin-reading commands don't eat the sentinel or hang", async () => {
|
|
35
|
+
const sh = make();
|
|
36
|
+
const res = await sh.run("cat", 5_000, signal());
|
|
37
|
+
expect(res.exitCode).toBe(0);
|
|
38
|
+
// Session still healthy afterwards.
|
|
39
|
+
const next = await sh.run("echo ok", 5_000, signal());
|
|
40
|
+
expect(next.output).toBe("ok");
|
|
41
|
+
});
|
|
42
|
+
it("timeout kills the session; the next call gets a fresh shell", async () => {
|
|
43
|
+
const sh = make();
|
|
44
|
+
await sh.run("export GG_PSH_STATE=set", 10_000, signal());
|
|
45
|
+
const timedOut = await sh.run("sleep 30", 300, signal());
|
|
46
|
+
expect(timedOut.exitCode).toBe("TIMEOUT");
|
|
47
|
+
// Fresh shell: the exported var from before the timeout is gone.
|
|
48
|
+
const res = await sh.run('echo "[$GG_PSH_STATE]"', 10_000, signal());
|
|
49
|
+
expect(res.output).toBe("[]");
|
|
50
|
+
});
|
|
51
|
+
it("over-cap output still finds the sentinel — no hang, session survives", async () => {
|
|
52
|
+
shell = new PersistentShell(os.tmpdir(), { ...process.env, TERM: "dumb" }, 512);
|
|
53
|
+
const sh = shell;
|
|
54
|
+
await sh.run("export GG_PSH_CAP=kept", 10_000, signal());
|
|
55
|
+
// ~40KB of output, way past the 512-byte cap.
|
|
56
|
+
const res = await sh.run('yes x | head -n 20000; echo "end"', 10_000, signal());
|
|
57
|
+
expect(res.exitCode).toBe(0);
|
|
58
|
+
expect(res.output).toContain("[Output capped at 512 bytes]");
|
|
59
|
+
// Session state survived — the command completed instead of timing out.
|
|
60
|
+
const after = await sh.run('echo "$GG_PSH_CAP"', 10_000, signal());
|
|
61
|
+
expect(after.output).toBe("kept");
|
|
62
|
+
});
|
|
63
|
+
it("does not leak error listeners across many runs", async () => {
|
|
64
|
+
const sh = make();
|
|
65
|
+
for (let i = 0; i < 15; i++) {
|
|
66
|
+
await sh.run("true", 10_000, signal());
|
|
67
|
+
}
|
|
68
|
+
// Access the internal child to count listeners — the leak showed up as
|
|
69
|
+
// MaxListenersExceededWarning after ~10 calls before the fix.
|
|
70
|
+
const child = sh.child;
|
|
71
|
+
expect(child.listenerCount("error")).toBeLessThanOrEqual(1);
|
|
72
|
+
expect(child.listenerCount("exit")).toBeLessThanOrEqual(1);
|
|
73
|
+
});
|
|
74
|
+
it("rejects a concurrent call while busy instead of interleaving", async () => {
|
|
75
|
+
const sh = make();
|
|
76
|
+
const slow = sh.run("sleep 0.5; echo done", 10_000, signal());
|
|
77
|
+
const busy = await sh.run("echo nope", 10_000, signal());
|
|
78
|
+
expect(busy.exitCode).toBe(1);
|
|
79
|
+
expect(busy.output).toContain("busy");
|
|
80
|
+
const done = await slow;
|
|
81
|
+
expect(done.output).toBe("done");
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
//# sourceMappingURL=persistent-shell.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persistent-shell.test.js","sourceRoot":"","sources":["../../src/core/persistent-shell.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -22,6 +22,25 @@ async function waitForProcessExit(pid) {
|
|
|
22
22
|
}
|
|
23
23
|
throw new Error(`Process ${pid} was still alive after shutdown.`);
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Terminate a real spawned process group with the *unmocked* process.kill.
|
|
27
|
+
* Used by tests that stub the manager's kill machinery and would otherwise
|
|
28
|
+
* leak the OS process they spawned. POSIX kills the detached group (-pid);
|
|
29
|
+
* Windows falls back to the single pid.
|
|
30
|
+
*/
|
|
31
|
+
function killRealProcessTree(pid) {
|
|
32
|
+
try {
|
|
33
|
+
process.kill(process.platform === "win32" ? pid : -pid, "SIGKILL");
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
try {
|
|
37
|
+
process.kill(pid, "SIGKILL");
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// Already gone.
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
25
44
|
function isProcessAlive(pid) {
|
|
26
45
|
try {
|
|
27
46
|
process.kill(pid, 0);
|
|
@@ -71,12 +90,21 @@ describe("ProcessManager dev-server lifecycle repro", () => {
|
|
|
71
90
|
});
|
|
72
91
|
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "gg-win-taskkill-"));
|
|
73
92
|
const started = await manager.start(`${JSON.stringify(process.execPath)} -e "setInterval(()=>{},1000)"`, tmpDir);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
93
|
+
try {
|
|
94
|
+
const stopped = await manager.stop(started.id);
|
|
95
|
+
expect(stopped).toBe(`Process ${started.id} already exited`);
|
|
96
|
+
manager.shutdownAll();
|
|
97
|
+
expect(taskkill).toHaveBeenCalledWith("taskkill", ["/pid", String(started.pid), "/T", "/F"], {
|
|
98
|
+
stdio: "ignore",
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
// This test deliberately mocks `kill` and `spawnSync`, so neither the
|
|
103
|
+
// simulated stop() nor shutdownAll() actually signals the real child
|
|
104
|
+
// spawned by start(). Reap it for real here — otherwise every run of
|
|
105
|
+
// this suite orphans a live `node -e setInterval` process forever.
|
|
106
|
+
killRealProcessTree(started.pid);
|
|
107
|
+
}
|
|
80
108
|
});
|
|
81
109
|
it("starts, reads, and stops a long-running Node HTTP server through the worker background path", async () => {
|
|
82
110
|
manager = new ProcessManager();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-manager-dev-server-repro.test.js","sourceRoot":"","sources":["../../src/core/process-manager-dev-server-repro.test.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"process-manager-dev-server-repro.test.js","sourceRoot":"","sources":["../../src/core/process-manager-dev-server-repro.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { LevelUpEvent, ProgressFile, ScoredCommit } from "./types.js";
|
|
2
|
+
export interface AwardResult {
|
|
3
|
+
file: ProgressFile;
|
|
4
|
+
gained: number;
|
|
5
|
+
levelUp: LevelUpEvent | null;
|
|
6
|
+
}
|
|
7
|
+
/** Streak multiplier for the current streak length (in days). */
|
|
8
|
+
export declare function streakMultiplier(days: number): number;
|
|
9
|
+
/** Raw XP for a commit of a given size (before multipliers/caps). */
|
|
10
|
+
export declare function commitXp(linesChanged: number): number;
|
|
11
|
+
/** Update streak for an XP event happening now. Returns the active multiplier. */
|
|
12
|
+
export declare function applyStreak(file: ProgressFile, now: number): number;
|
|
13
|
+
/**
|
|
14
|
+
* Award XP for one completed prompt (caller ensures the run finished, not canceled, ≥1 turn).
|
|
15
|
+
* Diminishing returns: after 15 prompts in a rolling hour, +2 instead of +10.
|
|
16
|
+
*/
|
|
17
|
+
export declare function awardPrompt(file: ProgressFile, now: number, cwd?: string): AwardResult;
|
|
18
|
+
/**
|
|
19
|
+
* Award XP for new commits. Dedupes by patch-id (reverts/recommits score 0) and caps
|
|
20
|
+
* XP-earning commits at 6 per rolling hour (extra commits still count in totals).
|
|
21
|
+
*/
|
|
22
|
+
export declare function awardCommits(file: ProgressFile, commits: ScoredCommit[], now: number, cwd?: string): AwardResult;
|
|
23
|
+
//# sourceMappingURL=engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../src/core/progress/engine.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAY3E,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,YAAY,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,YAAY,GAAG,IAAI,CAAC;CAC9B;AAED,iEAAiE;AACjE,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMrD;AAED,qEAAqE;AACrE,wBAAgB,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAErD;AAYD,kFAAkF;AAClF,wBAAgB,WAAW,CAAC,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAUnE;AAkDD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,WAAW,CAYtF;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,YAAY,EAClB,OAAO,EAAE,YAAY,EAAE,EACvB,GAAG,EAAE,MAAM,EACX,GAAG,CAAC,EAAE,MAAM,GACX,WAAW,CAmBb"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// Pure XP engine — all functions take a ProgressFile and a `now`, mutate a copy, and
|
|
2
|
+
// return the gain. No I/O here; the store handles persistence.
|
|
3
|
+
import crypto from "node:crypto";
|
|
4
|
+
import { levelForXp, rankForLevel } from "./ranks.js";
|
|
5
|
+
import { dayKey } from "./store.js";
|
|
6
|
+
const HOUR_MS = 60 * 60 * 1000;
|
|
7
|
+
const PROMPT_XP = 10;
|
|
8
|
+
const PROMPT_XP_DIMINISHED = 2;
|
|
9
|
+
const PROMPTS_PER_HOUR_FULL = 15;
|
|
10
|
+
const COMMIT_BASE_XP = 20;
|
|
11
|
+
const COMMIT_SIZE_XP_CAP = 80;
|
|
12
|
+
const COMMITS_PER_HOUR = 6;
|
|
13
|
+
const DAILY_SOFT_CAP = 500;
|
|
14
|
+
const DAILY_OVERCAP_FACTOR = 0.25;
|
|
15
|
+
/** Streak multiplier for the current streak length (in days). */
|
|
16
|
+
export function streakMultiplier(days) {
|
|
17
|
+
if (days >= 30)
|
|
18
|
+
return 1.5;
|
|
19
|
+
if (days >= 14)
|
|
20
|
+
return 1.4;
|
|
21
|
+
if (days >= 7)
|
|
22
|
+
return 1.25;
|
|
23
|
+
if (days >= 3)
|
|
24
|
+
return 1.1;
|
|
25
|
+
return 1;
|
|
26
|
+
}
|
|
27
|
+
/** Raw XP for a commit of a given size (before multipliers/caps). */
|
|
28
|
+
export function commitXp(linesChanged) {
|
|
29
|
+
return COMMIT_BASE_XP + Math.min(COMMIT_SIZE_XP_CAP, Math.floor(Math.sqrt(linesChanged) * 4));
|
|
30
|
+
}
|
|
31
|
+
function pruneRolling(file, now) {
|
|
32
|
+
file.rolling.promptTimes = file.rolling.promptTimes.filter((t) => now - t < HOUR_MS);
|
|
33
|
+
file.rolling.commitTimes = file.rolling.commitTimes.filter((t) => now - t < HOUR_MS);
|
|
34
|
+
const today = dayKey(new Date(now));
|
|
35
|
+
if (file.rolling.dayKey !== today) {
|
|
36
|
+
file.rolling.dayKey = today;
|
|
37
|
+
file.rolling.dayXp = 0;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** Update streak for an XP event happening now. Returns the active multiplier. */
|
|
41
|
+
export function applyStreak(file, now) {
|
|
42
|
+
const today = dayKey(new Date(now));
|
|
43
|
+
const last = file.streak.lastActiveDay;
|
|
44
|
+
if (last !== today) {
|
|
45
|
+
const yesterday = dayKey(new Date(now - 24 * HOUR_MS));
|
|
46
|
+
file.streak.current = last === yesterday ? file.streak.current + 1 : 1;
|
|
47
|
+
file.streak.lastActiveDay = today;
|
|
48
|
+
if (file.streak.current > file.streak.best)
|
|
49
|
+
file.streak.best = file.streak.current;
|
|
50
|
+
}
|
|
51
|
+
return streakMultiplier(file.streak.current);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Apply streak multiplier + daily soft cap to a base amount and credit it.
|
|
55
|
+
* Splits the streak bonus out into its own xpBySource bucket.
|
|
56
|
+
*/
|
|
57
|
+
function creditXp(file, base, source, now) {
|
|
58
|
+
const multiplier = applyStreak(file, now);
|
|
59
|
+
const withStreak = base * multiplier;
|
|
60
|
+
// Daily soft cap: XP beyond 500/day earns at 25%.
|
|
61
|
+
const room = Math.max(0, DAILY_SOFT_CAP - file.rolling.dayXp);
|
|
62
|
+
const full = Math.min(withStreak, room);
|
|
63
|
+
const overflow = withStreak - full;
|
|
64
|
+
const gained = Math.round(full + overflow * DAILY_OVERCAP_FACTOR);
|
|
65
|
+
if (gained <= 0)
|
|
66
|
+
return 0;
|
|
67
|
+
// Attribute the capped gain proportionally between base and streak bonus.
|
|
68
|
+
const capRatio = withStreak > 0 ? gained / withStreak : 0;
|
|
69
|
+
const baseShare = Math.round(base * capRatio);
|
|
70
|
+
const bonusShare = gained - baseShare;
|
|
71
|
+
file.xp += gained;
|
|
72
|
+
file.rolling.dayXp += gained;
|
|
73
|
+
file.xpBySource[source] += baseShare;
|
|
74
|
+
file.xpBySource.streakBonus += bonusShare;
|
|
75
|
+
return gained;
|
|
76
|
+
}
|
|
77
|
+
function markProject(file, cwd) {
|
|
78
|
+
if (!cwd)
|
|
79
|
+
return;
|
|
80
|
+
const hash = crypto.createHash("sha256").update(cwd).digest("hex").slice(0, 16);
|
|
81
|
+
if (!file.totals.projects.includes(hash))
|
|
82
|
+
file.totals.projects.push(hash);
|
|
83
|
+
}
|
|
84
|
+
function finishAward(file, levelBefore, gained) {
|
|
85
|
+
const levelAfter = levelForXp(file.xp);
|
|
86
|
+
let levelUp = null;
|
|
87
|
+
if (levelAfter > levelBefore) {
|
|
88
|
+
levelUp = { from: levelBefore, to: levelAfter, rankName: rankForLevel(levelAfter).name };
|
|
89
|
+
}
|
|
90
|
+
file.lastEvent = { nonce: crypto.randomUUID(), levelUp };
|
|
91
|
+
return { file, gained, levelUp };
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Award XP for one completed prompt (caller ensures the run finished, not canceled, ≥1 turn).
|
|
95
|
+
* Diminishing returns: after 15 prompts in a rolling hour, +2 instead of +10.
|
|
96
|
+
*/
|
|
97
|
+
export function awardPrompt(file, now, cwd) {
|
|
98
|
+
const levelBefore = levelForXp(file.xp);
|
|
99
|
+
pruneRolling(file, now);
|
|
100
|
+
const base = file.rolling.promptTimes.length >= PROMPTS_PER_HOUR_FULL ? PROMPT_XP_DIMINISHED : PROMPT_XP;
|
|
101
|
+
file.rolling.promptTimes.push(now);
|
|
102
|
+
file.totals.prompts += 1;
|
|
103
|
+
markProject(file, cwd);
|
|
104
|
+
const gained = creditXp(file, base, "prompts", now);
|
|
105
|
+
return finishAward(file, levelBefore, gained);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Award XP for new commits. Dedupes by patch-id (reverts/recommits score 0) and caps
|
|
109
|
+
* XP-earning commits at 6 per rolling hour (extra commits still count in totals).
|
|
110
|
+
*/
|
|
111
|
+
export function awardCommits(file, commits, now, cwd) {
|
|
112
|
+
const levelBefore = levelForXp(file.xp);
|
|
113
|
+
pruneRolling(file, now);
|
|
114
|
+
let gained = 0;
|
|
115
|
+
for (const commit of commits) {
|
|
116
|
+
if (commit.patchId && file.patchIds.includes(commit.patchId))
|
|
117
|
+
continue;
|
|
118
|
+
if (commit.patchId)
|
|
119
|
+
file.patchIds.push(commit.patchId);
|
|
120
|
+
file.totals.commits += 1;
|
|
121
|
+
file.totals.linesShipped += commit.linesChanged;
|
|
122
|
+
if (file.rolling.commitTimes.length >= COMMITS_PER_HOUR)
|
|
123
|
+
continue;
|
|
124
|
+
file.rolling.commitTimes.push(now);
|
|
125
|
+
gained += creditXp(file, commitXp(commit.linesChanged), "commits", now);
|
|
126
|
+
}
|
|
127
|
+
if (commits.length > 0)
|
|
128
|
+
markProject(file, cwd);
|
|
129
|
+
return finishAward(file, levelBefore, gained);
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../../../src/core/progress/engine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.test.d.ts","sourceRoot":"","sources":["../../../src/core/progress/engine.test.ts"],"names":[],"mappings":""}
|