doer-agent 0.4.2 → 0.4.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.
- package/dist/agent-codex-auth-rpc.js +322 -0
- package/dist/agent-codex-cli.js +210 -0
- package/dist/agent-fs-rpc.js +405 -0
- package/dist/agent-git-rpc.js +299 -0
- package/dist/agent-jetstream.js +120 -0
- package/dist/agent-run-execution.js +39 -0
- package/dist/agent-run-lifecycle.js +67 -0
- package/dist/agent-run-rpc.js +93 -0
- package/dist/agent-run-state.js +229 -0
- package/dist/agent-runtime-env.js +147 -0
- package/dist/agent-runtime-io.js +112 -0
- package/dist/agent-runtime-utils.js +253 -0
- package/dist/agent-session-loop.js +53 -0
- package/dist/agent-session-rpc.js +867 -0
- package/dist/agent-settings-rpc.js +75 -0
- package/dist/agent-settings.js +397 -0
- package/dist/agent-skill-rpc.js +164 -0
- package/dist/agent-task-execution.js +275 -0
- package/dist/agent.js +376 -4275
- package/package.json +1 -1
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
export function sanitizeUserId(userId) {
|
|
3
|
+
const normalized = userId.trim().replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
4
|
+
return normalized.length > 0 ? normalized : "anonymous";
|
|
5
|
+
}
|
|
6
|
+
export function buildAgentRunRpcSubject(userId, agentId) {
|
|
7
|
+
return `doer.agent.run.rpc.${sanitizeUserId(userId)}.${agentId.trim()}`;
|
|
8
|
+
}
|
|
9
|
+
export function buildAgentRunEventsSubject(userId, agentId) {
|
|
10
|
+
return `doer.agent.run.events.${sanitizeUserId(userId)}.${agentId.trim()}`;
|
|
11
|
+
}
|
|
12
|
+
export function buildAgentSessionRpcSubject(userId, agentId) {
|
|
13
|
+
return `doer.agent.session.rpc.${sanitizeUserId(userId)}.${agentId.trim()}`;
|
|
14
|
+
}
|
|
15
|
+
export function buildAgentCodexAuthRpcSubject(userId, agentId) {
|
|
16
|
+
return `doer.agent.codex.auth.rpc.${sanitizeUserId(userId)}.${agentId.trim()}`;
|
|
17
|
+
}
|
|
18
|
+
export function buildAgentSettingsRpcSubject(userId, agentId) {
|
|
19
|
+
return `doer.agent.settings.rpc.${sanitizeUserId(userId)}.${agentId.trim()}`;
|
|
20
|
+
}
|
|
21
|
+
export function buildAgentGitRpcSubject(userId, agentId) {
|
|
22
|
+
return `doer.agent.git.rpc.${sanitizeUserId(userId)}.${agentId.trim()}`;
|
|
23
|
+
}
|
|
24
|
+
export function buildAgentSkillRpcSubject(userId, agentId) {
|
|
25
|
+
return `doer.agent.skill.rpc.${sanitizeUserId(userId)}.${agentId.trim()}`;
|
|
26
|
+
}
|
|
27
|
+
export function buildAgentFsRpcSubject(userId, agentId) {
|
|
28
|
+
return `doer.agent.fs.rpc.${sanitizeUserId(userId)}.${agentId.trim()}`;
|
|
29
|
+
}
|
|
30
|
+
export function parseBootstrapTaskConfig(value) {
|
|
31
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const task = value;
|
|
35
|
+
const stream = typeof task.stream === "string" ? task.stream.trim() : "";
|
|
36
|
+
const subject = typeof task.subject === "string" ? task.subject.trim() : "";
|
|
37
|
+
const durable = typeof task.durable === "string" ? task.durable.trim() : "";
|
|
38
|
+
if (!stream || !subject || !durable) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
return { stream, subject, durable };
|
|
42
|
+
}
|
|
43
|
+
export function normalizeTaskIds(value) {
|
|
44
|
+
if (!Array.isArray(value)) {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
const out = [];
|
|
48
|
+
for (const item of value) {
|
|
49
|
+
if (typeof item !== "string") {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const id = item.trim();
|
|
53
|
+
if (!id) {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
out.push(id);
|
|
57
|
+
}
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
export function normalizeEnvPatch(value) {
|
|
61
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
62
|
+
return {};
|
|
63
|
+
}
|
|
64
|
+
const out = {};
|
|
65
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
66
|
+
if (typeof raw !== "string") {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const normalizedKey = key.trim();
|
|
70
|
+
if (!normalizedKey) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
out[normalizedKey] = raw;
|
|
74
|
+
}
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
export function normalizeRunImagePaths(value) {
|
|
78
|
+
if (!Array.isArray(value)) {
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
const seen = new Set();
|
|
82
|
+
const out = [];
|
|
83
|
+
for (const item of value) {
|
|
84
|
+
if (typeof item !== "string") {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const normalized = item.trim();
|
|
88
|
+
if (!normalized || seen.has(normalized)) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
seen.add(normalized);
|
|
92
|
+
out.push(normalized);
|
|
93
|
+
}
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
96
|
+
export function fatalExit(message, error, writeAgentError) {
|
|
97
|
+
const detail = error instanceof Error ? error.message : typeof error === "string" ? error : error ? String(error) : "";
|
|
98
|
+
const full = detail ? `${message}: ${detail}` : message;
|
|
99
|
+
writeAgentError(`fatal: ${full}`);
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
export function sleep(ms) {
|
|
103
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
104
|
+
}
|
|
105
|
+
export function writeTaskStream(taskId, stream, chunk) {
|
|
106
|
+
const target = stream === "stdout" ? process.stdout : process.stderr;
|
|
107
|
+
const lines = chunk.replace(/\r/g, "\n").split("\n");
|
|
108
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
109
|
+
const line = lines[i];
|
|
110
|
+
if (line.length === 0 && i === lines.length - 1) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
target.write(`[doer-agent][task=${taskId}][${stream}] ${line}\n`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
export function writeTaskUpload(taskId, message) {
|
|
117
|
+
process.stdout.write(`[doer-agent][task=${taskId}][upload] ${message}\n`);
|
|
118
|
+
}
|
|
119
|
+
export function writeRpcStream(requestId, stream, chunk) {
|
|
120
|
+
const target = stream === "stdout" ? process.stdout : process.stderr;
|
|
121
|
+
const lines = chunk.replace(/\r/g, "\n").split("\n");
|
|
122
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
123
|
+
const line = lines[i];
|
|
124
|
+
if (line.length === 0 && i === lines.length - 1) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
target.write(`[doer-agent][rpc=${requestId}][${stream}] ${line}\n`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
export function writeRpcStatus(requestId, message) {
|
|
131
|
+
process.stdout.write(`[doer-agent][rpc=${requestId}][status] ${message}\n`);
|
|
132
|
+
}
|
|
133
|
+
export function writeRunStatus(runId, message) {
|
|
134
|
+
process.stdout.write(`[doer-agent][run=${runId}][status] ${message}\n`);
|
|
135
|
+
}
|
|
136
|
+
export function writeRunStream(runId, stream, chunk) {
|
|
137
|
+
const target = stream === "stdout" ? process.stdout : process.stderr;
|
|
138
|
+
const lines = chunk.split(/\r?\n/);
|
|
139
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
140
|
+
const line = lines[index];
|
|
141
|
+
if (!line && index === lines.length - 1) {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
target.write(`[doer-agent][run=${runId}][${stream}] ${line}\n`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function resolveLogTimeZone() {
|
|
148
|
+
const configured = process.env.DOER_AGENT_LOG_TIMEZONE?.trim() || process.env.TZ?.trim();
|
|
149
|
+
return configured && configured.length > 0 ? configured : "Asia/Seoul";
|
|
150
|
+
}
|
|
151
|
+
function resolveTimeZoneOffsetString(date, timeZone) {
|
|
152
|
+
try {
|
|
153
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
154
|
+
timeZone,
|
|
155
|
+
timeZoneName: "shortOffset",
|
|
156
|
+
hour: "2-digit",
|
|
157
|
+
minute: "2-digit",
|
|
158
|
+
hour12: false,
|
|
159
|
+
}).formatToParts(date);
|
|
160
|
+
const token = parts.find((part) => part.type === "timeZoneName")?.value || "GMT+0";
|
|
161
|
+
const matched = token.match(/GMT([+-]\d{1,2})(?::?(\d{2}))?/i);
|
|
162
|
+
if (!matched) {
|
|
163
|
+
return "+00:00";
|
|
164
|
+
}
|
|
165
|
+
const hourRaw = matched[1] || "+0";
|
|
166
|
+
const minuteRaw = matched[2] || "00";
|
|
167
|
+
const sign = hourRaw.startsWith("-") ? "-" : "+";
|
|
168
|
+
const absHour = String(Math.abs(Number.parseInt(hourRaw, 10))).padStart(2, "0");
|
|
169
|
+
const absMinute = String(Math.abs(Number.parseInt(minuteRaw, 10))).padStart(2, "0");
|
|
170
|
+
return `${sign}${absHour}:${absMinute}`;
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
return "+00:00";
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
export function formatLocalTimestamp(date = new Date()) {
|
|
177
|
+
const timeZone = resolveLogTimeZone();
|
|
178
|
+
try {
|
|
179
|
+
const parts = new Intl.DateTimeFormat("en-CA", {
|
|
180
|
+
timeZone,
|
|
181
|
+
year: "numeric",
|
|
182
|
+
month: "2-digit",
|
|
183
|
+
day: "2-digit",
|
|
184
|
+
hour: "2-digit",
|
|
185
|
+
minute: "2-digit",
|
|
186
|
+
second: "2-digit",
|
|
187
|
+
hour12: false,
|
|
188
|
+
}).formatToParts(date);
|
|
189
|
+
const pick = (type) => {
|
|
190
|
+
return parts.find((part) => part.type === type)?.value || "00";
|
|
191
|
+
};
|
|
192
|
+
const year = pick("year");
|
|
193
|
+
const month = pick("month");
|
|
194
|
+
const day = pick("day");
|
|
195
|
+
const hours = pick("hour");
|
|
196
|
+
const minutes = pick("minute");
|
|
197
|
+
const seconds = pick("second");
|
|
198
|
+
const ms = String(date.getMilliseconds()).padStart(3, "0");
|
|
199
|
+
const offset = resolveTimeZoneOffsetString(date, timeZone);
|
|
200
|
+
return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}.${ms}${offset}`;
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
return date.toISOString();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
export function parseArgs(argv) {
|
|
207
|
+
const out = {};
|
|
208
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
209
|
+
const key = argv[i];
|
|
210
|
+
if (!key.startsWith("--")) {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
const value = argv[i + 1];
|
|
214
|
+
if (typeof value === "string" && !value.startsWith("--")) {
|
|
215
|
+
out[key.slice(2)] = value;
|
|
216
|
+
i += 1;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
out[key.slice(2)] = "true";
|
|
220
|
+
}
|
|
221
|
+
return out;
|
|
222
|
+
}
|
|
223
|
+
export function resolveArgOrEnv(args, argKeys, envKeys, fallback = "") {
|
|
224
|
+
for (const key of argKeys) {
|
|
225
|
+
const value = args[key]?.trim();
|
|
226
|
+
if (value) {
|
|
227
|
+
return value;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
for (const key of envKeys) {
|
|
231
|
+
const value = process.env[key]?.trim();
|
|
232
|
+
if (value) {
|
|
233
|
+
return value;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return fallback;
|
|
237
|
+
}
|
|
238
|
+
export function resolveContainerReachableServerBaseUrl(serverBaseUrl) {
|
|
239
|
+
return serverBaseUrl;
|
|
240
|
+
}
|
|
241
|
+
export async function resolveAgentVersion(packageJsonPath) {
|
|
242
|
+
const raw = await readFile(packageJsonPath, "utf8").catch(() => "");
|
|
243
|
+
if (!raw) {
|
|
244
|
+
return "unknown";
|
|
245
|
+
}
|
|
246
|
+
try {
|
|
247
|
+
const parsed = JSON.parse(raw);
|
|
248
|
+
return typeof parsed.version === "string" && parsed.version.trim() ? parsed.version.trim() : "unknown";
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
return "unknown";
|
|
252
|
+
}
|
|
253
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export async function runConnectedAgentSession(args) {
|
|
2
|
+
let heartbeatFailures = 0;
|
|
3
|
+
let heartbeatInFlight = false;
|
|
4
|
+
let sessionInvalidated = false;
|
|
5
|
+
const invalidateAgentSession = (reason) => {
|
|
6
|
+
if (sessionInvalidated) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
sessionInvalidated = true;
|
|
10
|
+
args.onInfraError(`closing nats session: ${reason}`);
|
|
11
|
+
void args.jetstream.nc.close().catch((error) => {
|
|
12
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
13
|
+
args.onInfraError(`failed to close nats session: ${message}`);
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
const heartbeatTimer = setInterval(() => {
|
|
17
|
+
if (heartbeatInFlight || sessionInvalidated) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
heartbeatInFlight = true;
|
|
21
|
+
void args.heartbeatAgentSession({
|
|
22
|
+
nc: args.jetstream.nc,
|
|
23
|
+
serverBaseUrl: args.serverBaseUrl,
|
|
24
|
+
userId: args.userId,
|
|
25
|
+
agentToken: args.agentToken,
|
|
26
|
+
})
|
|
27
|
+
.then(() => {
|
|
28
|
+
heartbeatInFlight = false;
|
|
29
|
+
if (heartbeatFailures > 0) {
|
|
30
|
+
args.onInfraError(`heartbeat reconnected at=${args.formatTimestamp()}`);
|
|
31
|
+
}
|
|
32
|
+
heartbeatFailures = 0;
|
|
33
|
+
})
|
|
34
|
+
.catch((error) => {
|
|
35
|
+
heartbeatInFlight = false;
|
|
36
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
37
|
+
heartbeatFailures += 1;
|
|
38
|
+
if (heartbeatFailures > 1) {
|
|
39
|
+
args.onInfraError(`heartbeat failed: ${message} (count=${heartbeatFailures}/${args.heartbeatFailureThreshold})`);
|
|
40
|
+
}
|
|
41
|
+
if (heartbeatFailures >= args.heartbeatFailureThreshold) {
|
|
42
|
+
invalidateAgentSession(`heartbeat failure threshold reached at=${args.formatTimestamp()}`);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}, args.heartbeatIntervalMs);
|
|
46
|
+
args.subscribeAll();
|
|
47
|
+
const closeError = await args.jetstream.nc.closed();
|
|
48
|
+
clearInterval(heartbeatTimer);
|
|
49
|
+
args.stopAllSessionWatchers();
|
|
50
|
+
const detail = closeError instanceof Error ? closeError.message : "clean close";
|
|
51
|
+
args.onInfraError(`nats session ended: ${detail}; reconnecting`);
|
|
52
|
+
await args.sleep(1000);
|
|
53
|
+
}
|