doer-agent 0.6.3 → 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,
@@ -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",
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,14 +24,14 @@
24
24
  "prepublishOnly": "npm run build"
25
25
  },
26
26
  "dependencies": {
27
- "@modelcontextprotocol/sdk": "^1.27.1",
27
+ "@modelcontextprotocol/sdk": "^1.29.0",
28
28
  "@openai/codex": "^0.129.0",
29
29
  "@openai/codex-sdk": "^0.129.0",
30
30
  "nats": "^2.29.3",
31
- "tar": "^7.5.13"
31
+ "tar": "^7.5.15"
32
32
  },
33
33
  "devDependencies": {
34
- "@types/node": "^20",
34
+ "@types/node": "^20.19.40",
35
35
  "tsx": "^4.21.0",
36
36
  "typescript": "^5"
37
37
  }