feat(cli): pick the app of the project the launcher belongs to
Discovery is per user, so every project's launcher sees every debug app running, and with two of them up every command refused to guess and asked for -i, though the agent in one project meant its own app. Two copies of the example, both named basic, could only be told apart by id. The launcher now exports CRAFTER_DEBUG_PROJECT, its project's directory (flatpak-spawn gets it spelled out), and the client counts an app as the project's when its executable lies under it, as `-r` runs it from <project>/bin/<variant>/. With other projects' apps running, commands pick this project's; two copies of it still need -i. Another project's app is never picked by default: with this project's app not up yet, or crashed, the agent would otherwise drive, or quit, someone else's. It is listed instead, for -i. `ls` lists this project's apps first, marked, `--json` says which are, and `--wait` waits for one of them rather than any app; the exit code says whether one runs. A client started without the launcher has no project and picks the one app running, as before, and the command the protocol check prints for an older app's own client now names it with -i. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
02f4564442
commit
2217037d72
5 changed files with 126 additions and 16 deletions
|
|
@ -6,7 +6,8 @@
|
|||
// while a thread plays the app's frame loop (Advance, Pump). Covers what an
|
||||
// agent sees: typed key=value calls and the usage error for a bad value,
|
||||
// suggestions for typos, get/set, pause/step/resume, waits, the log, traces,
|
||||
// screenshots written where asked, --json, instance selection, the protocol
|
||||
// screenshots written where asked, --json, instance selection (this
|
||||
// project's apps first, another project's only through -i), the protocol
|
||||
// and source checks against the discovery record, a server-side timeout,
|
||||
// and a client-side one that diagnoses an app no longer calling Pump.
|
||||
#include <stdio.h>
|
||||
|
|
@ -76,10 +77,12 @@ namespace {
|
|||
std::string err;
|
||||
};
|
||||
|
||||
Ran Run(std::vector<std::string_view> args, const std::vector<Instance>& instances) {
|
||||
// `project` as the launcher would pass it; empty, as without one.
|
||||
Ran Run(std::vector<std::string_view> args, const std::vector<Instance>& instances, const fs::path& project = {}) {
|
||||
std::ostringstream out;
|
||||
std::ostringstream err;
|
||||
Cli::Environment environment;
|
||||
environment.project = project;
|
||||
environment.connect = [](const InstanceRecord&) -> std::unique_ptr<Cli::Connection> { return std::make_unique<InProcess>(); };
|
||||
environment.list = [instances] { return instances; };
|
||||
environment.out = &out;
|
||||
|
|
@ -212,6 +215,44 @@ int main() {
|
|||
r = Run({ "guide" }, instances);
|
||||
Check(r.code == 0 && Has(r.out, "# crafter-debug"), "guide");
|
||||
|
||||
// Apps of two projects at once. `debugger` prints the pid of the record
|
||||
// it picked, which is how these tell which one that was.
|
||||
const fs::path here = "/projects/game";
|
||||
InstanceRecord mine = record;
|
||||
mine.instanceId = "aaa111";
|
||||
mine.pid = 11;
|
||||
mine.exe = (here / "bin" / "variant" / "game").string();
|
||||
InstanceRecord copy = mine;
|
||||
copy.instanceId = "aaa222";
|
||||
copy.pid = 12;
|
||||
InstanceRecord theirs = record;
|
||||
theirs.name = "tool";
|
||||
theirs.instanceId = "bbb111";
|
||||
theirs.pid = 21;
|
||||
theirs.exe = "/projects/game-tools/bin/variant/tool";
|
||||
r = Run({ "debugger" }, { { theirs, {} }, { mine, {} } }, here);
|
||||
Check(r.code == 0 && Has(r.out, "-p 11"), std::format("with another project's app running too, this project's is picked ({})", r.out + r.err));
|
||||
r = Run({ "debugger" }, { { theirs, {} } }, here);
|
||||
Check(r.code == 3 && Has(r.err, "nothing from this project") && Has(r.err, "bbb111"),
|
||||
std::format("another project's app alone is not picked, but listed for -i ({})", r.err));
|
||||
r = Run({ "-i", "tool", "debugger" }, { { theirs, {} }, { mine, {} } }, here);
|
||||
Check(r.code == 0 && Has(r.out, "-p 21"), "-i picks another project's app");
|
||||
r = Run({ "debugger" }, { { mine, {} }, { copy, {} } }, here);
|
||||
Check(r.code == 2 && Has(r.err, "several instances"), "two copies of this project's app still need -i");
|
||||
r = Run({ "-i", "aaa2", "debugger" }, { { mine, {} }, { copy, {} } }, here);
|
||||
Check(r.code == 0 && Has(r.out, "-p 12"), "a start of the id is enough");
|
||||
r = Run({ "debugger" }, { { theirs, {} } });
|
||||
Check(r.code == 0 && Has(r.out, "-p 21"), "without a launcher's project, the one app running is picked as before");
|
||||
r = Run({ "ls" }, { { theirs, {} }, { mine, {} } }, here);
|
||||
Check(r.code == 0 && r.out.starts_with("game id aaa111") && Has(r.out, "(this project)") && Has(r.out, "tool id bbb111"),
|
||||
std::format("ls lists this project's apps first, marked, then the others ({})", r.out));
|
||||
const auto waited = std::chrono::steady_clock::now();
|
||||
r = Run({ "ls", "--wait", "300ms" }, { { theirs, {} } }, here);
|
||||
Check(r.code == 3 && std::chrono::steady_clock::now() - waited >= 300ms && Has(r.out, "tool id bbb111") && Has(r.err, "none of these is from this project"),
|
||||
std::format("ls --wait waits for this project's app, not any ({})", r.err));
|
||||
r = Run({ "--json", "ls" }, { { theirs, {} }, { mine, {} } }, here);
|
||||
Check(r.code == 0 && Has(r.out, R"("thisProject":true)") && Has(r.out, R"("thisProject":false)"), "ls --json says which apps are this project's");
|
||||
|
||||
pumping = false;
|
||||
app.join();
|
||||
r = Run({ "--timeout", "100ms", "get", "hp" }, instances);
|
||||
|
|
|
|||
Loading…
Reference in a new issue