doer-agent 0.3.6 → 0.4.0
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.js +84 -15
- package/package.json +1 -1
package/dist/agent.js
CHANGED
|
@@ -9,6 +9,8 @@ const DEFAULT_SERVER_BASE_URL = "https://doer.cranix.net";
|
|
|
9
9
|
const AGENT_MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
10
10
|
const AGENT_PROJECT_DIR = path.join(AGENT_MODULE_DIR, "..");
|
|
11
11
|
const AGENT_PACKAGE_JSON_PATH = path.join(AGENT_PROJECT_DIR, "package.json");
|
|
12
|
+
const HEARTBEAT_INTERVAL_MS = 5_000;
|
|
13
|
+
const HEARTBEAT_FAILURE_THRESHOLD = 3;
|
|
12
14
|
let activeTaskLogContext = null;
|
|
13
15
|
let workspaceRootOverride = null;
|
|
14
16
|
const fsRpcCodec = StringCodec();
|
|
@@ -605,10 +607,13 @@ function resolveAgentSettingsDir() {
|
|
|
605
607
|
function resolveAgentSettingsFilePath() {
|
|
606
608
|
return path.join(resolveAgentSettingsDir(), "config.json");
|
|
607
609
|
}
|
|
610
|
+
function resolveAgentModelInstructionsFilePath() {
|
|
611
|
+
return path.join(resolveAgentSettingsDir(), "model-instructions.md");
|
|
612
|
+
}
|
|
608
613
|
function createDefaultAgentSettingsConfig() {
|
|
609
614
|
return {
|
|
610
615
|
general: {
|
|
611
|
-
|
|
616
|
+
personality: "pragmatic",
|
|
612
617
|
},
|
|
613
618
|
codex: {
|
|
614
619
|
model: "gpt-5.4",
|
|
@@ -671,6 +676,9 @@ function normalizeNullableString(value) {
|
|
|
671
676
|
const trimmed = value.trim();
|
|
672
677
|
return trimmed ? trimmed : null;
|
|
673
678
|
}
|
|
679
|
+
function normalizeCodexPersonality(value, fallback) {
|
|
680
|
+
return value === "friendly" || value === "pragmatic" ? value : fallback;
|
|
681
|
+
}
|
|
674
682
|
function normalizeAgentSettingsConfig(value, fallback) {
|
|
675
683
|
const base = fallback ?? createDefaultAgentSettingsConfig();
|
|
676
684
|
const raw = value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
@@ -685,7 +693,7 @@ function normalizeAgentSettingsConfig(value, fallback) {
|
|
|
685
693
|
const figma = raw.figma && typeof raw.figma === "object" ? raw.figma : {};
|
|
686
694
|
return {
|
|
687
695
|
general: {
|
|
688
|
-
|
|
696
|
+
personality: normalizeCodexPersonality(general.personality, base.general.personality),
|
|
689
697
|
},
|
|
690
698
|
codex: {
|
|
691
699
|
model: typeof codex.model === "string" && codex.model.trim() ? codex.model.trim() : base.codex.model,
|
|
@@ -757,6 +765,20 @@ async function writeAgentSettingsConfig(config) {
|
|
|
757
765
|
await mkdir(dir, { recursive: true });
|
|
758
766
|
await writeFile(resolveAgentSettingsFilePath(), `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
759
767
|
}
|
|
768
|
+
async function readAgentModelInstructions() {
|
|
769
|
+
const raw = await readFile(resolveAgentModelInstructionsFilePath(), "utf8").catch(() => "");
|
|
770
|
+
return raw.trim() ? raw : null;
|
|
771
|
+
}
|
|
772
|
+
async function writeAgentModelInstructions(value) {
|
|
773
|
+
const filePath = resolveAgentModelInstructionsFilePath();
|
|
774
|
+
const nextValue = typeof value === "string" ? value.trim() : "";
|
|
775
|
+
if (!nextValue) {
|
|
776
|
+
await unlink(filePath).catch(() => undefined);
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
await mkdir(resolveAgentSettingsDir(), { recursive: true });
|
|
780
|
+
await writeFile(filePath, value ?? "", "utf8");
|
|
781
|
+
}
|
|
760
782
|
function maskSecretPreview(secret) {
|
|
761
783
|
if (secret.length <= 6) {
|
|
762
784
|
return `${secret.slice(0, 1)}***${secret.slice(-1)}`;
|
|
@@ -769,7 +791,7 @@ function toMaskedSecret(value) {
|
|
|
769
791
|
}
|
|
770
792
|
return { has: true, masked: maskSecretPreview(value), length: value.length };
|
|
771
793
|
}
|
|
772
|
-
function toAgentSettingsPublic(config) {
|
|
794
|
+
async function toAgentSettingsPublic(config) {
|
|
773
795
|
const realtimeKey = toMaskedSecret(config.realtime.apiKey);
|
|
774
796
|
const gitOauth = toMaskedSecret(config.git.oauthToken);
|
|
775
797
|
const awsSecret = toMaskedSecret(config.aws.secretAccessKey);
|
|
@@ -778,9 +800,11 @@ function toAgentSettingsPublic(config) {
|
|
|
778
800
|
const notionToken = toMaskedSecret(config.notion.apiToken);
|
|
779
801
|
const slackToken = toMaskedSecret(config.slack.botToken);
|
|
780
802
|
const figmaToken = toMaskedSecret(config.figma.apiToken);
|
|
803
|
+
const customInstructions = await readAgentModelInstructions();
|
|
781
804
|
return {
|
|
782
805
|
general: {
|
|
783
|
-
|
|
806
|
+
personality: config.general.personality,
|
|
807
|
+
customInstructions,
|
|
784
808
|
},
|
|
785
809
|
codex: {
|
|
786
810
|
model: config.codex.model,
|
|
@@ -872,7 +896,7 @@ function normalizeAgentSettingsPatch(value) {
|
|
|
872
896
|
assignNested(section, key, raw[flatKey]);
|
|
873
897
|
delete patch[flatKey];
|
|
874
898
|
};
|
|
875
|
-
move("
|
|
899
|
+
move("personality", "general", "personality");
|
|
876
900
|
move("codexModel", "codex", "model");
|
|
877
901
|
move("codexAuthMode", "codex", "authMode");
|
|
878
902
|
move("realtimeModel", "realtime", "model");
|
|
@@ -1341,12 +1365,22 @@ function normalizeCodexModel(value) {
|
|
|
1341
1365
|
const normalized = typeof value === "string" ? value.trim() : "";
|
|
1342
1366
|
return normalized || "gpt-5.4";
|
|
1343
1367
|
}
|
|
1368
|
+
function toTomlStringLiteral(value) {
|
|
1369
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
1370
|
+
}
|
|
1344
1371
|
function buildManagedCodexArgs(args) {
|
|
1345
1372
|
const promptArgs = ["--", args.prompt];
|
|
1346
1373
|
const fixedArgs = ["--dangerously-bypass-approvals-and-sandbox"];
|
|
1374
|
+
const configArgs = [
|
|
1375
|
+
...(args.personality ? ["--config", `personality=${toTomlStringLiteral(args.personality)}`] : []),
|
|
1376
|
+
...(args.modelInstructionsFile
|
|
1377
|
+
? ["--config", `model_instructions_file=${toTomlStringLiteral(args.modelInstructionsFile)}`]
|
|
1378
|
+
: []),
|
|
1379
|
+
];
|
|
1347
1380
|
const imageArgs = args.imagePaths.flatMap((imagePath) => ["--image", imagePath]);
|
|
1348
1381
|
return [
|
|
1349
1382
|
...fixedArgs,
|
|
1383
|
+
...configArgs,
|
|
1350
1384
|
"--model",
|
|
1351
1385
|
args.model,
|
|
1352
1386
|
...(args.sessionId
|
|
@@ -1717,6 +1751,14 @@ async function handleSettingsRpcMessage(args) {
|
|
|
1717
1751
|
const next = request.action === "update" ? normalizeAgentSettingsConfig(request.patch, existing) : existing;
|
|
1718
1752
|
if (request.action === "update") {
|
|
1719
1753
|
await writeAgentSettingsConfig(next);
|
|
1754
|
+
const customInstructions = typeof request.patch.customInstructions === "string"
|
|
1755
|
+
? request.patch.customInstructions
|
|
1756
|
+
: request.patch.customInstructions === null
|
|
1757
|
+
? null
|
|
1758
|
+
: undefined;
|
|
1759
|
+
if (customInstructions !== undefined) {
|
|
1760
|
+
await writeAgentModelInstructions(customInstructions);
|
|
1761
|
+
}
|
|
1720
1762
|
}
|
|
1721
1763
|
else if (request.defaults) {
|
|
1722
1764
|
const filePath = resolveAgentSettingsFilePath();
|
|
@@ -1731,7 +1773,7 @@ async function handleSettingsRpcMessage(args) {
|
|
|
1731
1773
|
payload: {
|
|
1732
1774
|
requestId,
|
|
1733
1775
|
ok: true,
|
|
1734
|
-
settings: toAgentSettingsPublic(next),
|
|
1776
|
+
settings: await toAgentSettingsPublic(next),
|
|
1735
1777
|
},
|
|
1736
1778
|
});
|
|
1737
1779
|
}
|
|
@@ -2337,6 +2379,8 @@ async function handleRunRpcMessage(args) {
|
|
|
2337
2379
|
const runId = request.runId ?? requestId;
|
|
2338
2380
|
await claimRunStartSlot({ runId, sessionId: request.sessionId });
|
|
2339
2381
|
try {
|
|
2382
|
+
const localAgentSettings = await readAgentSettingsConfig(null);
|
|
2383
|
+
const customInstructions = await readAgentModelInstructions();
|
|
2340
2384
|
const task = await startManagedRun({
|
|
2341
2385
|
requestId,
|
|
2342
2386
|
runId,
|
|
@@ -2350,6 +2394,8 @@ async function handleRunRpcMessage(args) {
|
|
|
2350
2394
|
imagePaths: request.imagePaths,
|
|
2351
2395
|
sessionId: request.sessionId,
|
|
2352
2396
|
model: request.model,
|
|
2397
|
+
personality: localAgentSettings.general.personality,
|
|
2398
|
+
modelInstructionsFile: customInstructions ? resolveAgentModelInstructionsFilePath() : null,
|
|
2353
2399
|
}),
|
|
2354
2400
|
cwd: request.cwd,
|
|
2355
2401
|
runtimeEnvPatch: request.runtimeEnvPatch,
|
|
@@ -3890,7 +3936,8 @@ function persistEventOrFatal(args) {
|
|
|
3890
3936
|
}
|
|
3891
3937
|
})();
|
|
3892
3938
|
}
|
|
3893
|
-
async function
|
|
3939
|
+
async function heartbeatAgentSession(args) {
|
|
3940
|
+
await args.nc.flush();
|
|
3894
3941
|
await postJson(`${args.serverBaseUrl}/api/agent/heartbeat`, {
|
|
3895
3942
|
userId: args.userId,
|
|
3896
3943
|
agentToken: args.agentToken,
|
|
@@ -4330,23 +4377,45 @@ async function main() {
|
|
|
4330
4377
|
else {
|
|
4331
4378
|
writeAgentInfraError(`nats session restored agentId=${initialAgentId} servers=${jetstream.servers.join(",")} at=${formatLocalTimestamp()}`);
|
|
4332
4379
|
}
|
|
4333
|
-
let
|
|
4380
|
+
let heartbeatFailures = 0;
|
|
4381
|
+
let heartbeatInFlight = false;
|
|
4382
|
+
let sessionInvalidated = false;
|
|
4383
|
+
const invalidateAgentSession = (reason) => {
|
|
4384
|
+
if (sessionInvalidated) {
|
|
4385
|
+
return;
|
|
4386
|
+
}
|
|
4387
|
+
sessionInvalidated = true;
|
|
4388
|
+
writeAgentInfraError(`closing nats session: ${reason}`);
|
|
4389
|
+
void jetstream.nc.close().catch((error) => {
|
|
4390
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4391
|
+
writeAgentInfraError(`failed to close nats session: ${message}`);
|
|
4392
|
+
});
|
|
4393
|
+
};
|
|
4334
4394
|
const heartbeatTimer = setInterval(() => {
|
|
4335
|
-
|
|
4395
|
+
if (heartbeatInFlight || sessionInvalidated) {
|
|
4396
|
+
return;
|
|
4397
|
+
}
|
|
4398
|
+
heartbeatInFlight = true;
|
|
4399
|
+
void heartbeatAgentSession({ nc: jetstream.nc, serverBaseUrl, userId, agentToken })
|
|
4336
4400
|
.then(() => {
|
|
4337
|
-
|
|
4401
|
+
heartbeatInFlight = false;
|
|
4402
|
+
if (heartbeatFailures > 0) {
|
|
4338
4403
|
writeAgentInfraError(`heartbeat reconnected at=${formatLocalTimestamp()}`);
|
|
4339
4404
|
}
|
|
4340
|
-
|
|
4405
|
+
heartbeatFailures = 0;
|
|
4341
4406
|
})
|
|
4342
4407
|
.catch((error) => {
|
|
4408
|
+
heartbeatInFlight = false;
|
|
4343
4409
|
const message = error instanceof Error ? error.message : String(error);
|
|
4344
|
-
|
|
4345
|
-
|
|
4410
|
+
heartbeatFailures += 1;
|
|
4411
|
+
if (heartbeatFailures > 1) {
|
|
4412
|
+
writeAgentInfraError(`heartbeat failed: ${message} (count=${heartbeatFailures}/${HEARTBEAT_FAILURE_THRESHOLD})`);
|
|
4413
|
+
}
|
|
4414
|
+
if (heartbeatFailures >= HEARTBEAT_FAILURE_THRESHOLD) {
|
|
4415
|
+
invalidateAgentSession(`heartbeat failure threshold reached at=${formatLocalTimestamp()}`);
|
|
4346
4416
|
}
|
|
4347
|
-
heartbeatHealthy = false;
|
|
4348
4417
|
});
|
|
4349
|
-
},
|
|
4418
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
4350
4419
|
subscribeToFsRpc({
|
|
4351
4420
|
jetstream,
|
|
4352
4421
|
serverBaseUrl,
|