@rethinkingstudio/agent-runner 0.1.0 → 0.1.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/package.json +3 -3
- package/src/cli.mjs +29 -1
- package/src/watch.mjs +35 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rethinkingstudio/agent-runner",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Starts your coding agent when a job lands on your Doable Agent Desk.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"repository": {
|
|
19
19
|
"type": "git",
|
|
20
|
-
"url": "git+https://github.com/
|
|
20
|
+
"url": "git+https://github.com/zangqilong198812/doable-agent-runner.git"
|
|
21
21
|
},
|
|
22
22
|
"keywords": [
|
|
23
23
|
"mcp",
|
|
@@ -30,6 +30,6 @@
|
|
|
30
30
|
},
|
|
31
31
|
"homepage": "https://doable.rethinking.art",
|
|
32
32
|
"bugs": {
|
|
33
|
-
"url": "https://github.com/
|
|
33
|
+
"url": "https://github.com/zangqilong198812/doable-agent-runner/issues"
|
|
34
34
|
}
|
|
35
35
|
}
|
package/src/cli.mjs
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import { spawnSync } from "node:child_process";
|
|
12
12
|
import { writeFileSync, unlinkSync, existsSync, mkdirSync, cpSync, rmSync } from "node:fs";
|
|
13
|
-
import { homedir, hostname, platform } from "node:os";
|
|
13
|
+
import { homedir, hostname, platform, userInfo } from "node:os";
|
|
14
14
|
import { join } from "node:path";
|
|
15
15
|
import { fileURLToPath } from "node:url";
|
|
16
16
|
import * as config from "./config.mjs";
|
|
@@ -63,6 +63,7 @@ function install() {
|
|
|
63
63
|
};
|
|
64
64
|
|
|
65
65
|
const path = config.save(settings);
|
|
66
|
+
checkAgentIsReachable(settings);
|
|
66
67
|
const runtime = installRuntime();
|
|
67
68
|
console.log(`Settings written to ${path}`);
|
|
68
69
|
console.log(`Machine name: ${settings.name}`);
|
|
@@ -76,6 +77,33 @@ function install() {
|
|
|
76
77
|
platform() === "darwin" ? installLaunchd(runtime) : installSystemd(runtime);
|
|
77
78
|
}
|
|
78
79
|
|
|
80
|
+
/// launchd and systemd hand a process a minimal PATH, while agent CLIs install
|
|
81
|
+
/// into ~/.local/bin or a homebrew or nvm prefix. The runner starts them under a
|
|
82
|
+
/// login shell for exactly that reason — but if the command still cannot be
|
|
83
|
+
/// found, it is far better to say so now than to fail at 1am with nobody
|
|
84
|
+
/// watching and nothing in the log but "command not found".
|
|
85
|
+
function checkAgentIsReachable(settings) {
|
|
86
|
+
const command = settings.exec || "claude";
|
|
87
|
+
const binary = command.trim().split(/\s+/)[0];
|
|
88
|
+
const shell = (() => {
|
|
89
|
+
try {
|
|
90
|
+
return userInfo().shell || "/bin/zsh";
|
|
91
|
+
} catch {
|
|
92
|
+
return "/bin/zsh";
|
|
93
|
+
}
|
|
94
|
+
})();
|
|
95
|
+
const found = spawnSync(shell, ["-lc", `command -v ${binary}`], { encoding: "utf8" });
|
|
96
|
+
if (found.status === 0 && found.stdout.trim()) {
|
|
97
|
+
console.log(`Will start: ${found.stdout.trim()}`);
|
|
98
|
+
} else {
|
|
99
|
+
console.log(
|
|
100
|
+
`\nWARNING: could not find \`${binary}\` in your login shell.\n` +
|
|
101
|
+
"The runner will install, but every session it starts will fail. Either install\n" +
|
|
102
|
+
"that command, or re-run with --exec pointing at the right one."
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
79
107
|
/// Copies the runner somewhere stable and returns the entry point to point the
|
|
80
108
|
/// service at. Without this the service would reference wherever the installer
|
|
81
109
|
/// happened to run from — under npx, a cache directory with no guarantees.
|
package/src/watch.mjs
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
// Settings come from ~/.doable/runner.json, written by `install`.
|
|
19
19
|
|
|
20
20
|
import { spawn } from "node:child_process";
|
|
21
|
+
import { userInfo } from "node:os";
|
|
21
22
|
import * as config from "./config.mjs";
|
|
22
23
|
|
|
23
24
|
const settings = config.load();
|
|
@@ -51,6 +52,17 @@ if (!token) {
|
|
|
51
52
|
|
|
52
53
|
const log = (...a) => console.log(new Date().toISOString(), ...a);
|
|
53
54
|
|
|
55
|
+
/// The user's own shell, so their profile — and the PATH it builds — applies.
|
|
56
|
+
/// Read from the password database rather than $SHELL, which launchd does not
|
|
57
|
+
/// set.
|
|
58
|
+
function loginShell() {
|
|
59
|
+
try {
|
|
60
|
+
return userInfo().shell || process.env.SHELL || "/bin/zsh";
|
|
61
|
+
} catch {
|
|
62
|
+
return process.env.SHELL || "/bin/zsh";
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
54
66
|
/// True while a session is running. The desk allows one job per machine anyway,
|
|
55
67
|
/// so starting a second session would only produce an agent that gets refused.
|
|
56
68
|
let busy = false;
|
|
@@ -59,10 +71,18 @@ function runSession() {
|
|
|
59
71
|
if (busy) return;
|
|
60
72
|
busy = true;
|
|
61
73
|
log("work on the desk — starting a session");
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
|
|
74
|
+
// A LOGIN shell, not a plain one. launchd and systemd hand a process a
|
|
75
|
+
// minimal PATH — /usr/bin:/bin and little else — while agent CLIs install
|
|
76
|
+
// into ~/.local/bin, a homebrew prefix, or an nvm directory. Running under
|
|
77
|
+
// the user's own shell profile is what makes `claude` resolvable at all;
|
|
78
|
+
// without it this fails with "command not found" long after anyone is
|
|
79
|
+
// watching.
|
|
80
|
+
//
|
|
81
|
+
// `--exec` stays a plain shell command so it can be whatever the user's
|
|
82
|
+
// agent needs, and the prompt is handed over twice — on stdin and in the
|
|
83
|
+
// environment — because CLIs disagree about which they prefer and neither
|
|
84
|
+
// way needs quoting.
|
|
85
|
+
const child = spawn(loginShell(), ["-lc", exec], {
|
|
66
86
|
cwd,
|
|
67
87
|
env: { ...process.env, DOABLE_PROMPT: PROMPT },
|
|
68
88
|
stdio: ["pipe", "inherit", "inherit"],
|
|
@@ -95,6 +115,10 @@ async function announce() {
|
|
|
95
115
|
}
|
|
96
116
|
|
|
97
117
|
let version = "";
|
|
118
|
+
/// Successful long polls are silent on purpose — one line every 25 seconds
|
|
119
|
+
/// would bury everything worth reading. But that makes an outage and a hang
|
|
120
|
+
/// look identical in the log, so recovery gets said out loud.
|
|
121
|
+
let failing = false;
|
|
98
122
|
|
|
99
123
|
async function watch() {
|
|
100
124
|
for (;;) {
|
|
@@ -113,6 +137,11 @@ async function watch() {
|
|
|
113
137
|
continue;
|
|
114
138
|
}
|
|
115
139
|
|
|
140
|
+
if (failing) {
|
|
141
|
+
log("reconnected");
|
|
142
|
+
failing = false;
|
|
143
|
+
}
|
|
144
|
+
|
|
116
145
|
const event = await res.json();
|
|
117
146
|
version = event.version;
|
|
118
147
|
|
|
@@ -124,7 +153,8 @@ async function watch() {
|
|
|
124
153
|
else log("desk changed but nothing is queued");
|
|
125
154
|
} catch (e) {
|
|
126
155
|
// Sleep, laptop lid, flaky wifi — none of these deserve a crash.
|
|
127
|
-
log("watch failed:", e.message, "— retrying
|
|
156
|
+
if (!failing) log("watch failed:", e.message, "— retrying every 15s until it comes back");
|
|
157
|
+
failing = true;
|
|
128
158
|
await sleep(15_000);
|
|
129
159
|
}
|
|
130
160
|
}
|