doer-agent 0.6.2 → 0.6.4
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,161 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
import { StringCodec } from "nats";
|
|
5
|
+
import { resolveAgentVersion } from "./agent-runtime-utils.js";
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
const maintenanceRpcCodec = StringCodec();
|
|
8
|
+
function normalizeAction(value) {
|
|
9
|
+
if (value === "info" || value === "update/check" || value === "update/restart") {
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
throw new Error("unsupported action");
|
|
13
|
+
}
|
|
14
|
+
function parseSemver(value) {
|
|
15
|
+
const match = value.trim().match(/^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/);
|
|
16
|
+
if (!match) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
20
|
+
}
|
|
21
|
+
function compareSemver(a, b) {
|
|
22
|
+
const left = parseSemver(a);
|
|
23
|
+
const right = parseSemver(b);
|
|
24
|
+
if (!left || !right) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
for (let index = 0; index < 3; index += 1) {
|
|
28
|
+
if (left[index] !== right[index]) {
|
|
29
|
+
return left[index] - right[index];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return 0;
|
|
33
|
+
}
|
|
34
|
+
async function resolveLatestPackageVersion() {
|
|
35
|
+
try {
|
|
36
|
+
const { stdout } = await execFileAsync("npm", ["view", "doer-agent", "version"], {
|
|
37
|
+
timeout: 15_000,
|
|
38
|
+
maxBuffer: 1024 * 64,
|
|
39
|
+
});
|
|
40
|
+
const version = stdout.trim().split(/\s+/)[0]?.trim() || "";
|
|
41
|
+
return version || null;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function resolveUpdateCommand(command) {
|
|
48
|
+
const rawCommand = command?.trim() || "";
|
|
49
|
+
if (!rawCommand) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
const replaced = rawCommand.replace(/(^|\s)npx(\s+-y)?\s+(?:doer-agent(?:@[^\s]+)?|\.)\b/, (_match, prefix, yesFlag) => `${prefix}npx${yesFlag ?? " -y"} doer-agent@latest`);
|
|
53
|
+
return replaced !== rawCommand ? replaced : null;
|
|
54
|
+
}
|
|
55
|
+
function shellQuote(value) {
|
|
56
|
+
if (/^[A-Za-z0-9_./:=@+-]+$/.test(value)) {
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
60
|
+
}
|
|
61
|
+
function buildFallbackUpdateCommand() {
|
|
62
|
+
const workspace = process.env.WORKSPACE?.trim() || process.cwd();
|
|
63
|
+
const args = process.argv.slice(2).map(shellQuote).join(" ");
|
|
64
|
+
return [`WORKSPACE=${shellQuote(workspace)}`, "npx -y doer-agent@latest", args].filter(Boolean).join(" ");
|
|
65
|
+
}
|
|
66
|
+
async function resolveMaintenanceInfo(agentPackageJsonPath) {
|
|
67
|
+
const currentVersion = await resolveAgentVersion(agentPackageJsonPath);
|
|
68
|
+
const latestVersion = await resolveLatestPackageVersion();
|
|
69
|
+
const updateComparison = latestVersion ? compareSemver(currentVersion, latestVersion) : null;
|
|
70
|
+
const launchCommand = process.env.DOER_AGENT_LAUNCH_COMMAND?.trim() || process.env.DOER_DAEMON_COMMAND?.trim() || "";
|
|
71
|
+
const updateCommand = resolveUpdateCommand(launchCommand) ?? buildFallbackUpdateCommand();
|
|
72
|
+
return {
|
|
73
|
+
currentVersion,
|
|
74
|
+
latestVersion,
|
|
75
|
+
updateAvailable: updateComparison === null ? null : updateComparison < 0,
|
|
76
|
+
packageName: "doer-agent",
|
|
77
|
+
nodeVersion: process.version,
|
|
78
|
+
command: launchCommand || updateCommand,
|
|
79
|
+
updateCommand,
|
|
80
|
+
cwd: process.env.DOER_AGENT_LAUNCH_CWD?.trim() || process.env.DOER_DAEMON_CWD?.trim() || process.cwd(),
|
|
81
|
+
startedAt: new Date(Number(process.env.DOER_AGENT_STARTED_AT_MS || Date.now())).toISOString(),
|
|
82
|
+
canRestartToLatest: Boolean(updateCommand),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function scheduleRestartToLatest(command, cwd) {
|
|
86
|
+
setTimeout(() => {
|
|
87
|
+
const env = { ...process.env };
|
|
88
|
+
delete env.DOER_DAEMON_ID;
|
|
89
|
+
delete env.DOER_DAEMON_COMMAND;
|
|
90
|
+
delete env.DOER_DAEMON_CWD;
|
|
91
|
+
delete env.DOER_DAEMON_STATE_PATH;
|
|
92
|
+
delete env.DOER_DAEMON_EVENTS_PATH;
|
|
93
|
+
delete env.DOER_DAEMON_SHELL_PATH;
|
|
94
|
+
delete env.DOER_AGENT_LAUNCH_COMMAND;
|
|
95
|
+
delete env.DOER_AGENT_LAUNCH_CWD;
|
|
96
|
+
delete env.DOER_AGENT_LAUNCH_SHELL;
|
|
97
|
+
const child = spawn(command, {
|
|
98
|
+
cwd,
|
|
99
|
+
env,
|
|
100
|
+
shell: process.env.DOER_AGENT_LAUNCH_SHELL || process.env.DOER_DAEMON_SHELL_PATH || process.env.SHELL || true,
|
|
101
|
+
detached: true,
|
|
102
|
+
stdio: "ignore",
|
|
103
|
+
});
|
|
104
|
+
child.unref();
|
|
105
|
+
setTimeout(() => {
|
|
106
|
+
process.exit(0);
|
|
107
|
+
}, 250);
|
|
108
|
+
}, 250);
|
|
109
|
+
}
|
|
110
|
+
function publicMaintenanceInfo(info) {
|
|
111
|
+
return {
|
|
112
|
+
...info,
|
|
113
|
+
command: null,
|
|
114
|
+
updateCommand: null,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
export async function handleMaintenanceRpcMessage(args) {
|
|
118
|
+
let requestId = "unknown";
|
|
119
|
+
try {
|
|
120
|
+
const request = JSON.parse(maintenanceRpcCodec.decode(args.msg.data));
|
|
121
|
+
requestId = typeof request.requestId === "string" ? request.requestId : "unknown";
|
|
122
|
+
const action = normalizeAction(request.action);
|
|
123
|
+
const info = await resolveMaintenanceInfo(args.agentPackageJsonPath);
|
|
124
|
+
if (action === "update/restart") {
|
|
125
|
+
if (!info.updateCommand) {
|
|
126
|
+
throw new Error("agent was not started with a supported npx doer-agent command");
|
|
127
|
+
}
|
|
128
|
+
scheduleRestartToLatest(info.updateCommand, info.cwd);
|
|
129
|
+
}
|
|
130
|
+
args.msg.respond(maintenanceRpcCodec.encode(JSON.stringify({
|
|
131
|
+
requestId,
|
|
132
|
+
ok: true,
|
|
133
|
+
action,
|
|
134
|
+
info: publicMaintenanceInfo(info),
|
|
135
|
+
restartScheduled: action === "update/restart",
|
|
136
|
+
})));
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
140
|
+
args.onError?.(`maintenance rpc failed requestId=${requestId} error=${message}`);
|
|
141
|
+
args.msg.respond(maintenanceRpcCodec.encode(JSON.stringify({ requestId, ok: false, error: message })));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
export function subscribeToMaintenanceRpc(args) {
|
|
145
|
+
args.nc.subscribe(args.subject, {
|
|
146
|
+
callback: (error, msg) => {
|
|
147
|
+
if (error) {
|
|
148
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
149
|
+
args.onError(`maintenance rpc subscription error: ${message}`);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
void handleMaintenanceRpcMessage({
|
|
153
|
+
msg,
|
|
154
|
+
nc: args.nc,
|
|
155
|
+
agentPackageJsonPath: args.agentPackageJsonPath,
|
|
156
|
+
onError: args.onError,
|
|
157
|
+
});
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
args.onInfo(`maintenance rpc subscribed subject=${args.subject}`);
|
|
161
|
+
}
|
|
@@ -25,6 +25,9 @@ export function buildAgentFsRpcSubject(userId, agentId) {
|
|
|
25
25
|
export function buildAgentDaemonRpcSubject(userId, agentId) {
|
|
26
26
|
return `doer.agent.daemon.rpc.${sanitizeUserId(userId)}.${agentId.trim()}`;
|
|
27
27
|
}
|
|
28
|
+
export function buildAgentMaintenanceRpcSubject(userId, agentId) {
|
|
29
|
+
return `doer.agent.maintenance.rpc.${sanitizeUserId(userId)}.${agentId.trim()}`;
|
|
30
|
+
}
|
|
28
31
|
export function parseBootstrapTaskConfig(value) {
|
|
29
32
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
30
33
|
return null;
|
package/dist/agent.js
CHANGED
|
@@ -12,8 +12,9 @@ import { createLocalCodexCliTools } from "./agent-codex-cli.js";
|
|
|
12
12
|
import { connectBootstrapWithRetry } from "./agent-jetstream.js";
|
|
13
13
|
import { runConnectedAgentSession } from "./agent-session-loop.js";
|
|
14
14
|
import { subscribeToSkillRpc } from "./agent-skill-rpc.js";
|
|
15
|
+
import { subscribeToMaintenanceRpc } from "./agent-maintenance-rpc.js";
|
|
15
16
|
import { sendSignalToTaskProcess } from "./agent-task-execution.js";
|
|
16
|
-
import { buildAgentCodexAppEventsSubject, buildAgentCodexAppRpcSubject, buildAgentDaemonRpcSubject, buildAgentFsRpcSubject, buildAgentGitRpcSubject, buildAgentSettingsRpcSubject, buildAgentSkillRpcSubject, formatLocalTimestamp, parseArgs, resolveAgentVersion, resolveArgOrEnv, resolveContainerReachableServerBaseUrl, sanitizeUserId, sleep, } from "./agent-runtime-utils.js";
|
|
17
|
+
import { buildAgentCodexAppEventsSubject, buildAgentCodexAppRpcSubject, buildAgentDaemonRpcSubject, buildAgentFsRpcSubject, buildAgentGitRpcSubject, buildAgentMaintenanceRpcSubject, buildAgentSettingsRpcSubject, buildAgentSkillRpcSubject, formatLocalTimestamp, parseArgs, resolveAgentVersion, resolveArgOrEnv, resolveContainerReachableServerBaseUrl, sanitizeUserId, sleep, } from "./agent-runtime-utils.js";
|
|
17
18
|
import { createRuntimeEnvHelpers } from "./agent-runtime-env.js";
|
|
18
19
|
import { createEventPersistenceHelpers, heartbeatAgentSession, postJson, } from "./agent-runtime-io.js";
|
|
19
20
|
import { handleSettingsRpcMessage } from "./agent-settings-rpc.js";
|
|
@@ -162,6 +163,7 @@ async function main() {
|
|
|
162
163
|
process.chdir(startupWorkspaceRoot);
|
|
163
164
|
process.env.WORKSPACE = startupWorkspaceRoot;
|
|
164
165
|
process.env.CODEX_HOME = path.join(startupWorkspaceRoot, ".codex");
|
|
166
|
+
process.env.DOER_AGENT_STARTED_AT_MS = String(Date.now());
|
|
165
167
|
await mkdir(process.env.CODEX_HOME, { recursive: true }).catch(() => undefined);
|
|
166
168
|
const serverBaseUrlRaw = resolveArgOrEnv(args, ["server", "url"], ["DOER_AGENT_SERVER"], DEFAULT_SERVER_BASE_URL);
|
|
167
169
|
const requestedServerBaseUrl = serverBaseUrlRaw.replace(/\/$/, "");
|
|
@@ -293,6 +295,13 @@ async function main() {
|
|
|
293
295
|
onInfo: writeAgentInfo,
|
|
294
296
|
onError: writeAgentError,
|
|
295
297
|
});
|
|
298
|
+
subscribeToMaintenanceRpc({
|
|
299
|
+
nc: jetstream.nc,
|
|
300
|
+
subject: buildAgentMaintenanceRpcSubject(userId, initialAgentId),
|
|
301
|
+
agentPackageJsonPath: AGENT_PACKAGE_JSON_PATH,
|
|
302
|
+
onInfo: writeAgentInfo,
|
|
303
|
+
onError: writeAgentError,
|
|
304
|
+
});
|
|
296
305
|
},
|
|
297
306
|
onInfraError: writeAgentInfraError,
|
|
298
307
|
sleep,
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import path from "node:path";
|
|
2
4
|
import { createInterface } from "node:readline";
|
|
5
|
+
const require = createRequire(import.meta.url);
|
|
6
|
+
function resolveCodexCliBinPath() {
|
|
7
|
+
const packageJsonPath = require.resolve("@openai/codex/package.json");
|
|
8
|
+
const packageJson = require(packageJsonPath);
|
|
9
|
+
const codexBin = packageJson.bin?.codex;
|
|
10
|
+
if (!codexBin) {
|
|
11
|
+
throw new Error("@openai/codex package does not expose a codex binary");
|
|
12
|
+
}
|
|
13
|
+
return path.resolve(path.dirname(packageJsonPath), codexBin);
|
|
14
|
+
}
|
|
3
15
|
export class CodexAppServerClient {
|
|
4
16
|
options;
|
|
5
17
|
child = null;
|
|
@@ -46,7 +58,7 @@ export class CodexAppServerClient {
|
|
|
46
58
|
}
|
|
47
59
|
}
|
|
48
60
|
async startInner() {
|
|
49
|
-
this.child = spawn(
|
|
61
|
+
this.child = spawn(process.execPath, [resolveCodexCliBinPath(), ...this.options.args], {
|
|
50
62
|
cwd: this.options.cwd,
|
|
51
63
|
env: this.options.env,
|
|
52
64
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -28,6 +28,7 @@ async function buildCodexAppServerArgs(args) {
|
|
|
28
28
|
agentProjectDir: args.agentProjectDir,
|
|
29
29
|
workspaceRoot: args.workspaceRoot,
|
|
30
30
|
}),
|
|
31
|
+
...buildFeatureArg(true, "goals"),
|
|
31
32
|
...buildFeatureArg(args.settings.codex.computerUseEnabled, "computer_use"),
|
|
32
33
|
...buildFeatureArg(args.settings.codex.browserUseEnabled, "browser_use"),
|
|
33
34
|
"--listen",
|
|
@@ -62,6 +62,9 @@ async function main() {
|
|
|
62
62
|
const cwd = readRequiredEnv("DOER_DAEMON_CWD");
|
|
63
63
|
const shellPath = readRequiredEnv("DOER_DAEMON_SHELL_PATH");
|
|
64
64
|
const childEnv = { ...process.env };
|
|
65
|
+
childEnv.DOER_AGENT_LAUNCH_COMMAND = command;
|
|
66
|
+
childEnv.DOER_AGENT_LAUNCH_CWD = cwd;
|
|
67
|
+
childEnv.DOER_AGENT_LAUNCH_SHELL = shellPath;
|
|
65
68
|
delete childEnv.DOER_DAEMON_STATE_PATH;
|
|
66
69
|
delete childEnv.DOER_DAEMON_EVENTS_PATH;
|
|
67
70
|
delete childEnv.DOER_DAEMON_COMMAND;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "doer-agent",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.4",
|
|
4
4
|
"description": "Reverse-polling agent runtime for doer",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/agent.js",
|
|
@@ -24,13 +24,14 @@
|
|
|
24
24
|
"prepublishOnly": "npm run build"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
28
|
-
"@openai/codex
|
|
27
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
28
|
+
"@openai/codex": "^0.129.0",
|
|
29
|
+
"@openai/codex-sdk": "^0.129.0",
|
|
29
30
|
"nats": "^2.29.3",
|
|
30
|
-
"tar": "^7.5.
|
|
31
|
+
"tar": "^7.5.15"
|
|
31
32
|
},
|
|
32
33
|
"devDependencies": {
|
|
33
|
-
"@types/node": "^20",
|
|
34
|
+
"@types/node": "^20.19.40",
|
|
34
35
|
"tsx": "^4.21.0",
|
|
35
36
|
"typescript": "^5"
|
|
36
37
|
}
|