doer-agent 0.9.1 → 0.9.3
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-connectivity.test.js +42 -0
- package/dist/agent-jetstream.js +17 -1
- package/dist/agent-runtime-io.js +31 -8
- package/dist/agent.js +3 -1
- package/package.json +3 -3
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { buildNatsConnectionOptions } from "./agent-jetstream.js";
|
|
4
|
+
import { heartbeatAgentSession } from "./agent-runtime-io.js";
|
|
5
|
+
test("NATS connection detects stale networks quickly and keeps reconnecting", () => {
|
|
6
|
+
const options = buildNatsConnectionOptions({
|
|
7
|
+
servers: ["tls://nats.example.com:4222"],
|
|
8
|
+
token: "secret",
|
|
9
|
+
});
|
|
10
|
+
assert.equal(options.pingInterval, 2_000);
|
|
11
|
+
assert.equal(options.maxPingOut, 2);
|
|
12
|
+
assert.equal(options.maxReconnectAttempts, -1);
|
|
13
|
+
assert.equal(options.reconnectTimeWait, 1_000);
|
|
14
|
+
assert.equal(options.token, "secret");
|
|
15
|
+
});
|
|
16
|
+
test("heartbeat stops waiting when a NATS flush stalls", async () => {
|
|
17
|
+
const startedAt = Date.now();
|
|
18
|
+
await assert.rejects(heartbeatAgentSession({
|
|
19
|
+
nc: { flush: () => new Promise(() => undefined) },
|
|
20
|
+
serverBaseUrl: "https://doer.example.com",
|
|
21
|
+
userId: "user-1",
|
|
22
|
+
agentToken: "token-1",
|
|
23
|
+
timeoutMs: 20,
|
|
24
|
+
postJson: async () => ({ ok: true }),
|
|
25
|
+
}), /nats flush timed out after 20ms/);
|
|
26
|
+
assert.ok(Date.now() - startedAt < 500);
|
|
27
|
+
});
|
|
28
|
+
test("heartbeat forwards its timeout to the HTTP probe", async () => {
|
|
29
|
+
let observedTimeout;
|
|
30
|
+
await heartbeatAgentSession({
|
|
31
|
+
nc: { flush: async () => undefined },
|
|
32
|
+
serverBaseUrl: "https://doer.example.com",
|
|
33
|
+
userId: "user-1",
|
|
34
|
+
agentToken: "token-1",
|
|
35
|
+
timeoutMs: 3_000,
|
|
36
|
+
postJson: async (_url, _body, options) => {
|
|
37
|
+
observedTimeout = options?.timeoutMs;
|
|
38
|
+
return { ok: true };
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
assert.equal(observedTimeout, 3_000);
|
|
42
|
+
});
|
package/dist/agent-jetstream.js
CHANGED
|
@@ -1,4 +1,20 @@
|
|
|
1
1
|
import { AckPolicy, connect, DeliverPolicy, JSONCodec, RetentionPolicy, StorageType, } from "nats";
|
|
2
|
+
const NATS_PING_INTERVAL_MS = 2_000;
|
|
3
|
+
const NATS_MAX_PING_OUT = 2;
|
|
4
|
+
const NATS_RECONNECT_TIME_WAIT_MS = 1_000;
|
|
5
|
+
const NATS_RECONNECT_JITTER_MS = 250;
|
|
6
|
+
export function buildNatsConnectionOptions(args) {
|
|
7
|
+
return {
|
|
8
|
+
servers: args.servers,
|
|
9
|
+
...(args.token ? { token: args.token } : {}),
|
|
10
|
+
pingInterval: NATS_PING_INTERVAL_MS,
|
|
11
|
+
maxPingOut: NATS_MAX_PING_OUT,
|
|
12
|
+
maxReconnectAttempts: -1,
|
|
13
|
+
reconnectTimeWait: NATS_RECONNECT_TIME_WAIT_MS,
|
|
14
|
+
reconnectJitter: NATS_RECONNECT_JITTER_MS,
|
|
15
|
+
reconnectJitterTLS: NATS_RECONNECT_JITTER_MS,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
2
18
|
export function normalizeNatsServers(value) {
|
|
3
19
|
if (!Array.isArray(value)) {
|
|
4
20
|
return [];
|
|
@@ -55,7 +71,7 @@ async function initJetStreamContext(args) {
|
|
|
55
71
|
const stream = `DOER_AGENT_EVENTS_${sanitized}`;
|
|
56
72
|
const subject = `doer.agent.events.${sanitized}`;
|
|
57
73
|
const durable = `doer-agent-uploader-${sanitized}`;
|
|
58
|
-
const nc = await connect(
|
|
74
|
+
const nc = await connect(buildNatsConnectionOptions({ servers: args.servers, token: args.token }));
|
|
59
75
|
const jsm = await nc.jetstreamManager();
|
|
60
76
|
await ensureJetStreamInfra({ jsm, stream, subject, durable });
|
|
61
77
|
void (async () => {
|
package/dist/agent-runtime-io.js
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
|
-
export async function postJson(url, body) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
export async function postJson(url, body, options = {}) {
|
|
2
|
+
let res;
|
|
3
|
+
try {
|
|
4
|
+
res = await fetch(url, {
|
|
5
|
+
method: "POST",
|
|
6
|
+
headers: { "Content-Type": "application/json" },
|
|
7
|
+
body: JSON.stringify(body),
|
|
8
|
+
...(options.timeoutMs ? { signal: AbortSignal.timeout(options.timeoutMs) } : {}),
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
if (options.timeoutMs && error instanceof Error && error.name === "TimeoutError") {
|
|
13
|
+
throw new Error(`request timed out after ${options.timeoutMs}ms`);
|
|
14
|
+
}
|
|
15
|
+
throw error;
|
|
16
|
+
}
|
|
7
17
|
const text = await res.text();
|
|
8
18
|
let data = {};
|
|
9
19
|
if (text) {
|
|
@@ -104,9 +114,22 @@ export function createEventPersistenceHelpers(args) {
|
|
|
104
114
|
};
|
|
105
115
|
}
|
|
106
116
|
export async function heartbeatAgentSession(args) {
|
|
107
|
-
|
|
117
|
+
let timeout;
|
|
118
|
+
try {
|
|
119
|
+
await Promise.race([
|
|
120
|
+
args.nc.flush(),
|
|
121
|
+
new Promise((_, reject) => {
|
|
122
|
+
timeout = setTimeout(() => reject(new Error(`nats flush timed out after ${args.timeoutMs}ms`)), args.timeoutMs);
|
|
123
|
+
}),
|
|
124
|
+
]);
|
|
125
|
+
}
|
|
126
|
+
finally {
|
|
127
|
+
if (timeout) {
|
|
128
|
+
clearTimeout(timeout);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
108
131
|
await args.postJson(`${args.serverBaseUrl}/api/agent/heartbeat`, {
|
|
109
132
|
userId: args.userId,
|
|
110
133
|
agentToken: args.agentToken,
|
|
111
|
-
});
|
|
134
|
+
}, { timeoutMs: args.timeoutMs });
|
|
112
135
|
}
|
package/dist/agent.js
CHANGED
|
@@ -28,7 +28,8 @@ const AGENT_PROJECT_DIR = path.join(AGENT_MODULE_DIR, "..");
|
|
|
28
28
|
const AGENT_PACKAGE_JSON_PATH = path.join(AGENT_PROJECT_DIR, "package.json");
|
|
29
29
|
const BUNDLED_SKILLS_ROOT = path.join(AGENT_PROJECT_DIR, "runtime", "skills");
|
|
30
30
|
const HEARTBEAT_INTERVAL_MS = 5_000;
|
|
31
|
-
const HEARTBEAT_FAILURE_THRESHOLD =
|
|
31
|
+
const HEARTBEAT_FAILURE_THRESHOLD = 2;
|
|
32
|
+
const HEARTBEAT_REQUEST_TIMEOUT_MS = 3_000;
|
|
32
33
|
const codexAppEventCodec = StringCodec();
|
|
33
34
|
let activeTaskLogContext = null;
|
|
34
35
|
let workspaceRootOverride = null;
|
|
@@ -187,6 +188,7 @@ const eventPersistenceHelpers = createEventPersistenceHelpers({
|
|
|
187
188
|
const heartbeatSession = async (args) => {
|
|
188
189
|
await heartbeatAgentSession({
|
|
189
190
|
...args,
|
|
191
|
+
timeoutMs: HEARTBEAT_REQUEST_TIMEOUT_MS,
|
|
190
192
|
postJson,
|
|
191
193
|
});
|
|
192
194
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "doer-agent",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.3",
|
|
4
4
|
"description": "Reverse-polling agent runtime for doer",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/agent.js",
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
29
|
-
"@openai/codex": "^0.
|
|
30
|
-
"@openai/codex-sdk": "^0.
|
|
29
|
+
"@openai/codex": "^0.145.0",
|
|
30
|
+
"@openai/codex-sdk": "^0.145.0",
|
|
31
31
|
"diff": "^9.0.0",
|
|
32
32
|
"nats": "^2.29.3",
|
|
33
33
|
"tar": "^7.5.15"
|