doer-agent 0.5.9 → 0.6.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-bundled-skills.js +79 -0
- package/dist/agent-codex-app-rpc.js +63 -0
- package/dist/agent-codex-auth-rpc.js +96 -238
- package/dist/agent-codex-cli.js +1 -11
- package/dist/agent-runtime-utils.js +4 -65
- package/dist/agent-session-loop.js +0 -1
- package/dist/agent-settings-rpc.js +10 -82
- package/dist/agent-settings.js +1 -115
- package/dist/agent.js +47 -311
- package/dist/codex-app-server-client.js +148 -0
- package/dist/codex-app-server-manager.js +108 -0
- package/package.json +1 -4
- package/dist/agent-run-execution.js +0 -39
- package/dist/agent-run-lifecycle.js +0 -67
- package/dist/agent-run-rpc.js +0 -93
- package/dist/agent-run-state.js +0 -287
- package/dist/agent-runtime-utils.test.js +0 -38
- package/dist/agent-session-rpc.js +0 -1033
- package/dist/db-mcp-server.js +0 -377
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { mkdir, readdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
3
|
+
const MANAGED_MARKER = "<!-- managed-by: doer-agent bundled-skill -->";
|
|
4
|
+
const LEGACY_MANAGED_SKILLS_DIRS = [".doer", "doer-managed"];
|
|
5
|
+
async function pathExists(target) {
|
|
6
|
+
try {
|
|
7
|
+
await stat(target);
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
async function shouldWriteManagedSkill(targetFile) {
|
|
15
|
+
if (!(await pathExists(targetFile))) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
const current = await readFile(targetFile, "utf8").catch(() => "");
|
|
19
|
+
return current.includes(MANAGED_MARKER);
|
|
20
|
+
}
|
|
21
|
+
function withManagedMarker(source) {
|
|
22
|
+
const normalizedSource = source.trimEnd();
|
|
23
|
+
if (normalizedSource.includes(MANAGED_MARKER)) {
|
|
24
|
+
return `${normalizedSource}\n`;
|
|
25
|
+
}
|
|
26
|
+
const frontmatterMatch = normalizedSource.match(/^(---\n[\s\S]*?\n---)(\n?)([\s\S]*)$/);
|
|
27
|
+
if (frontmatterMatch) {
|
|
28
|
+
const [, frontmatter, , body] = frontmatterMatch;
|
|
29
|
+
return `${frontmatter}\n${MANAGED_MARKER}\n${body.replace(/^\n/, "")}\n`;
|
|
30
|
+
}
|
|
31
|
+
return `${MANAGED_MARKER}\n${normalizedSource}\n`;
|
|
32
|
+
}
|
|
33
|
+
async function removeLegacyManagedSkill(args) {
|
|
34
|
+
for (const legacyRoot of LEGACY_MANAGED_SKILLS_DIRS) {
|
|
35
|
+
const legacySkillDir = path.join(args.codexHome, "skills", legacyRoot, args.skillName);
|
|
36
|
+
const legacySkillFile = path.join(legacySkillDir, "SKILL.md");
|
|
37
|
+
if (!(await pathExists(legacySkillFile))) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const current = await readFile(legacySkillFile, "utf8").catch(() => "");
|
|
41
|
+
if (!current.includes(MANAGED_MARKER)) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
await rm(legacySkillDir, { recursive: true, force: true });
|
|
45
|
+
args.onInfo?.(`legacy bundled skill removed name=${args.skillName} path=.codex/skills/${legacyRoot}/${args.skillName}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export async function ensureBundledDoerSkills(args) {
|
|
49
|
+
const sourceRootExists = await pathExists(args.bundledSkillsRoot);
|
|
50
|
+
if (!sourceRootExists) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const entries = await readdir(args.bundledSkillsRoot, { withFileTypes: true });
|
|
54
|
+
for (const entry of entries) {
|
|
55
|
+
if (!entry.isDirectory() || entry.name.startsWith(".")) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const sourceSkillDir = path.join(args.bundledSkillsRoot, entry.name);
|
|
59
|
+
const sourceSkillFile = path.join(sourceSkillDir, "SKILL.md");
|
|
60
|
+
const targetSkillDir = path.join(args.codexHome, "skills", entry.name);
|
|
61
|
+
const targetSkillFile = path.join(targetSkillDir, "SKILL.md");
|
|
62
|
+
try {
|
|
63
|
+
const source = await readFile(sourceSkillFile, "utf8");
|
|
64
|
+
if (!(await shouldWriteManagedSkill(targetSkillFile))) {
|
|
65
|
+
args.onInfo?.(`bundled skill skipped name=${entry.name} reason=user-managed-target`);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const managedSource = withManagedMarker(source);
|
|
69
|
+
await mkdir(targetSkillDir, { recursive: true });
|
|
70
|
+
await writeFile(targetSkillFile, managedSource, "utf8");
|
|
71
|
+
await removeLegacyManagedSkill({ codexHome: args.codexHome, skillName: entry.name, onInfo: args.onInfo });
|
|
72
|
+
args.onInfo?.(`bundled skill synced name=${entry.name} path=.codex/skills/${entry.name}/SKILL.md`);
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
const message = error instanceof Error ? error.message : "unknown error";
|
|
76
|
+
args.onError?.(`bundled skill sync failed name=${entry.name} error=${message}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { StringCodec } from "nats";
|
|
2
|
+
const codexAppRpcCodec = StringCodec();
|
|
3
|
+
function normalizeCodexAppRpcRequest(args) {
|
|
4
|
+
const requestId = typeof args.request.requestId === "string" ? args.request.requestId.trim() : "";
|
|
5
|
+
const requestAgentId = typeof args.request.agentId === "string" ? args.request.agentId.trim() : "";
|
|
6
|
+
const actionRaw = typeof args.request.action === "string" ? args.request.action.trim() : "";
|
|
7
|
+
const method = typeof args.request.method === "string" ? args.request.method.trim() : "";
|
|
8
|
+
if (!requestId || !requestAgentId || requestAgentId !== args.agentId || actionRaw !== "request" || !method) {
|
|
9
|
+
throw new Error("invalid codex app rpc request");
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
requestId,
|
|
13
|
+
action: "request",
|
|
14
|
+
method,
|
|
15
|
+
params: args.request.params,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
async function handleCodexAppRpcMessage(args) {
|
|
19
|
+
let requestId = "unknown";
|
|
20
|
+
try {
|
|
21
|
+
const payload = JSON.parse(codexAppRpcCodec.decode(args.msg.data));
|
|
22
|
+
const request = normalizeCodexAppRpcRequest({ request: payload, agentId: args.agentId });
|
|
23
|
+
requestId = request.requestId;
|
|
24
|
+
const result = await args.manager.request(request.method, request.params);
|
|
25
|
+
args.msg.respond(codexAppRpcCodec.encode(JSON.stringify({
|
|
26
|
+
requestId,
|
|
27
|
+
ok: true,
|
|
28
|
+
result,
|
|
29
|
+
})));
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
33
|
+
args.onError(`codex app rpc failed requestId=${requestId} error=${message}`);
|
|
34
|
+
args.msg.respond(codexAppRpcCodec.encode(JSON.stringify({
|
|
35
|
+
requestId,
|
|
36
|
+
ok: false,
|
|
37
|
+
error: message,
|
|
38
|
+
})));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export function subscribeToCodexAppRpc(args) {
|
|
42
|
+
args.nc.closed().finally(() => {
|
|
43
|
+
void args.manager.stop().catch(() => undefined);
|
|
44
|
+
});
|
|
45
|
+
args.nc.subscribe(args.subject, {
|
|
46
|
+
callback: (error, msg) => {
|
|
47
|
+
if (error) {
|
|
48
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
49
|
+
args.onError(`codex app rpc subscription error: ${message}`);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
void handleCodexAppRpcMessage({
|
|
53
|
+
msg,
|
|
54
|
+
nc: args.nc,
|
|
55
|
+
agentId: args.agentId,
|
|
56
|
+
manager: args.manager,
|
|
57
|
+
onInfo: args.onInfo,
|
|
58
|
+
onError: args.onError,
|
|
59
|
+
});
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
args.onInfo(`codex app rpc subscribed subject=${args.subject} eventsSubject=${args.eventsSubject}`);
|
|
63
|
+
}
|
|
@@ -1,188 +1,114 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
|
-
import { unlink } from "node:fs/promises";
|
|
3
|
-
import path from "node:path";
|
|
4
1
|
import { StringCodec } from "nats";
|
|
5
2
|
const codexAuthRpcCodec = StringCodec();
|
|
6
|
-
let
|
|
7
|
-
function
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
return
|
|
12
|
-
verificationUri: urlMatch?.[0] ?? null,
|
|
13
|
-
userCode: codeMatch?.[0] ?? null,
|
|
14
|
-
};
|
|
3
|
+
let pendingCodexAppLogin = null;
|
|
4
|
+
function recordValue(value) {
|
|
5
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
6
|
+
}
|
|
7
|
+
function stringValue(value) {
|
|
8
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
15
9
|
}
|
|
16
|
-
function
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
10
|
+
function normalizeCodexAuthRpcRequest(args) {
|
|
11
|
+
const requestId = stringValue(args.request.requestId) ?? "";
|
|
12
|
+
const responseSubject = stringValue(args.request.responseSubject) ?? "";
|
|
13
|
+
const requestAgentId = stringValue(args.request.agentId) ?? "";
|
|
14
|
+
const actionRaw = stringValue(args.request.action) ?? "";
|
|
15
|
+
const action = actionRaw === "start" || actionRaw === "logout" || actionRaw === "login_api_key" ? actionRaw : "status";
|
|
16
|
+
const apiKey = stringValue(args.request.apiKey);
|
|
17
|
+
if (!requestId || !responseSubject || !requestAgentId || requestAgentId !== args.agentId) {
|
|
18
|
+
throw new Error("invalid codex auth rpc request");
|
|
20
19
|
}
|
|
21
|
-
|
|
20
|
+
if (action === "login_api_key" && !apiKey) {
|
|
21
|
+
throw new Error("api key is required");
|
|
22
|
+
}
|
|
23
|
+
return { requestId, responseSubject, action, apiKey };
|
|
22
24
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const merged = args.stripAnsi([result.stdout, result.stderr].filter(Boolean).join("\n")).trim();
|
|
26
|
-
return {
|
|
27
|
-
loggedIn: (result.code ?? 1) === 0,
|
|
28
|
-
output: merged || ((result.code ?? 1) === 0 ? "Logged in" : "Not logged in"),
|
|
29
|
-
};
|
|
25
|
+
function publishCodexAuthRpcResponse(args) {
|
|
26
|
+
args.nc.publish(args.responseSubject, codexAuthRpcCodec.encode(JSON.stringify(args.payload)));
|
|
30
27
|
}
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
if (state.child.exitCode !== null) {
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
28
|
+
function formatAccountStatus(result) {
|
|
29
|
+
const response = recordValue(result);
|
|
30
|
+
const account = recordValue(response?.account);
|
|
31
|
+
if (!account) {
|
|
32
|
+
return { loggedIn: false, output: "Not logged in" };
|
|
42
33
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const
|
|
34
|
+
const type = stringValue(account.type);
|
|
35
|
+
if (type === "chatgpt") {
|
|
36
|
+
const email = stringValue(account.email);
|
|
37
|
+
const planType = stringValue(account.planType);
|
|
47
38
|
return {
|
|
48
|
-
loggedIn:
|
|
49
|
-
output:
|
|
50
|
-
verificationUri: parsed.verificationUri,
|
|
51
|
-
userCode: parsed.userCode,
|
|
39
|
+
loggedIn: true,
|
|
40
|
+
output: `Logged in${email ? ` as ${email}` : ""}${planType ? ` (${planType})` : ""}`,
|
|
52
41
|
};
|
|
53
42
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
shell: args.resolveShellPath(),
|
|
57
|
-
detached: process.platform !== "win32",
|
|
58
|
-
env: {
|
|
59
|
-
...process.env,
|
|
60
|
-
WORKSPACE: args.workspaceRoot,
|
|
61
|
-
CODEX_HOME: args.resolveCodexHomePath(),
|
|
62
|
-
},
|
|
63
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
64
|
-
});
|
|
65
|
-
child.stdout?.setEncoding("utf8");
|
|
66
|
-
child.stderr?.setEncoding("utf8");
|
|
67
|
-
const state = { child, output: "" };
|
|
68
|
-
pendingCodexDeviceAuth = state;
|
|
69
|
-
const appendOutput = (chunk) => {
|
|
70
|
-
state.output += chunk;
|
|
71
|
-
};
|
|
72
|
-
child.stdout?.on("data", appendOutput);
|
|
73
|
-
child.stderr?.on("data", appendOutput);
|
|
74
|
-
child.once("exit", () => {
|
|
75
|
-
if (pendingCodexDeviceAuth === state) {
|
|
76
|
-
pendingCodexDeviceAuth = null;
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
await waitForCodexDeviceCode(state, 8000, args.stripAnsi);
|
|
80
|
-
const parsed = parseCodexDeviceAuthOutput(state.output, args.stripAnsi);
|
|
81
|
-
if ((!parsed.verificationUri || !parsed.userCode) && state.child.exitCode !== null) {
|
|
82
|
-
throw new Error("Failed to read device code from Codex CLI");
|
|
43
|
+
if (type === "apiKey") {
|
|
44
|
+
return { loggedIn: true, output: "Logged in with API key" };
|
|
83
45
|
}
|
|
84
|
-
return {
|
|
85
|
-
loggedIn: false,
|
|
86
|
-
output: pendingCodexDeviceAuthMessage(state, args.stripAnsi),
|
|
87
|
-
verificationUri: parsed.verificationUri,
|
|
88
|
-
userCode: parsed.userCode,
|
|
89
|
-
};
|
|
46
|
+
return { loggedIn: true, output: "Logged in" };
|
|
90
47
|
}
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
...process.env,
|
|
98
|
-
WORKSPACE: args.workspaceRoot,
|
|
99
|
-
CODEX_HOME: args.resolveCodexHomePath(),
|
|
100
|
-
},
|
|
101
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
102
|
-
});
|
|
103
|
-
let output = "";
|
|
104
|
-
child.stdout?.setEncoding("utf8");
|
|
105
|
-
child.stderr?.setEncoding("utf8");
|
|
106
|
-
child.stdout?.on("data", (chunk) => {
|
|
107
|
-
output += chunk;
|
|
108
|
-
});
|
|
109
|
-
child.stderr?.on("data", (chunk) => {
|
|
110
|
-
output += chunk;
|
|
111
|
-
});
|
|
112
|
-
const result = await new Promise((resolve, reject) => {
|
|
113
|
-
child.once("error", reject);
|
|
114
|
-
child.once("exit", (code) => resolve({ code, output }));
|
|
115
|
-
});
|
|
116
|
-
const normalized = args.stripAnsi(result.output).trim();
|
|
117
|
-
const parsed = parseCodexDeviceAuthOutput(result.output, args.stripAnsi);
|
|
118
|
-
if ((result.code ?? 1) === 0) {
|
|
119
|
-
const status = await args.getLocalCodexLoginStatus().catch(() => null);
|
|
48
|
+
function formatPendingLogin() {
|
|
49
|
+
const pending = pendingCodexAppLogin;
|
|
50
|
+
if (!pending) {
|
|
51
|
+
return { loggedIn: false, output: "Not logged in" };
|
|
52
|
+
}
|
|
53
|
+
if (pending.verificationUri && pending.userCode) {
|
|
120
54
|
return {
|
|
121
|
-
loggedIn:
|
|
122
|
-
output:
|
|
123
|
-
verificationUri:
|
|
124
|
-
userCode:
|
|
55
|
+
loggedIn: false,
|
|
56
|
+
output: `Waiting for approval. Enter code ${pending.userCode} at ${pending.verificationUri}`,
|
|
57
|
+
verificationUri: pending.verificationUri,
|
|
58
|
+
userCode: pending.userCode,
|
|
125
59
|
};
|
|
126
60
|
}
|
|
127
|
-
|
|
61
|
+
if (pending.verificationUri) {
|
|
62
|
+
return {
|
|
63
|
+
loggedIn: false,
|
|
64
|
+
output: `Waiting for approval at ${pending.verificationUri}`,
|
|
65
|
+
verificationUri: pending.verificationUri,
|
|
66
|
+
userCode: null,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return { loggedIn: false, output: "Waiting for approval", verificationUri: null, userCode: null };
|
|
128
70
|
}
|
|
129
|
-
async function
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
throw new Error(normalized || `Codex API key login failed with code ${result.code ?? "null"}`);
|
|
71
|
+
async function readCodexAccount(manager) {
|
|
72
|
+
const status = formatAccountStatus(await manager.request("account/read", { refreshToken: false }));
|
|
73
|
+
if (status.loggedIn) {
|
|
74
|
+
pendingCodexAppLogin = null;
|
|
134
75
|
}
|
|
135
|
-
|
|
136
|
-
return {
|
|
137
|
-
loggedIn: status?.loggedIn === true,
|
|
138
|
-
output: status?.output || normalized || "Logged in",
|
|
139
|
-
verificationUri: null,
|
|
140
|
-
userCode: null,
|
|
141
|
-
};
|
|
76
|
+
return status;
|
|
142
77
|
}
|
|
143
|
-
async function
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if (pendingCodexDeviceAuth?.child.exitCode === null) {
|
|
148
|
-
args.sendSignalToTaskProcess(pendingCodexDeviceAuth.child, "SIGKILL");
|
|
149
|
-
}
|
|
150
|
-
}, 1000);
|
|
151
|
-
pendingCodexDeviceAuth = null;
|
|
78
|
+
async function startCodexDeviceLogin(manager) {
|
|
79
|
+
const status = await readCodexAccount(manager);
|
|
80
|
+
if (status.loggedIn) {
|
|
81
|
+
return status;
|
|
152
82
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
if (statusAfterDelete?.output) {
|
|
161
|
-
merged = [merged, statusAfterDelete.output].filter(Boolean).join("\n");
|
|
162
|
-
}
|
|
83
|
+
if (pendingCodexAppLogin) {
|
|
84
|
+
return formatPendingLogin();
|
|
85
|
+
}
|
|
86
|
+
const result = recordValue(await manager.request("account/login/start", { type: "chatgptDeviceCode" }));
|
|
87
|
+
const loginId = stringValue(result?.loginId);
|
|
88
|
+
if (!loginId) {
|
|
89
|
+
throw new Error("Codex app-server did not return a login id");
|
|
163
90
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
91
|
+
pendingCodexAppLogin = {
|
|
92
|
+
loginId,
|
|
93
|
+
verificationUri: stringValue(result?.verificationUrl),
|
|
94
|
+
userCode: stringValue(result?.userCode),
|
|
167
95
|
};
|
|
96
|
+
return formatPendingLogin();
|
|
168
97
|
}
|
|
169
|
-
function
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
const action = actionRaw === "start" || actionRaw === "logout" || actionRaw === "login_api_key" ? actionRaw : "status";
|
|
175
|
-
const apiKey = typeof args.request.apiKey === "string" && args.request.apiKey.trim() ? args.request.apiKey.trim() : null;
|
|
176
|
-
if (!requestId || !responseSubject || !requestAgentId || requestAgentId !== args.agentId) {
|
|
177
|
-
throw new Error("invalid codex auth rpc request");
|
|
178
|
-
}
|
|
179
|
-
if (action === "login_api_key" && !apiKey) {
|
|
180
|
-
throw new Error("api key is required");
|
|
181
|
-
}
|
|
182
|
-
return { requestId, responseSubject, action, apiKey };
|
|
98
|
+
async function loginCodexWithApiKey(args) {
|
|
99
|
+
await args.manager.request("account/login/start", { type: "apiKey", apiKey: args.apiKey });
|
|
100
|
+
pendingCodexAppLogin = null;
|
|
101
|
+
const status = await readCodexAccount(args.manager);
|
|
102
|
+
return status.loggedIn ? status : { loggedIn: true, output: "Logged in with API key" };
|
|
183
103
|
}
|
|
184
|
-
function
|
|
185
|
-
|
|
104
|
+
async function logoutCodexAccount(manager) {
|
|
105
|
+
const pending = pendingCodexAppLogin;
|
|
106
|
+
pendingCodexAppLogin = null;
|
|
107
|
+
if (pending) {
|
|
108
|
+
await manager.request("account/login/cancel", { loginId: pending.loginId }).catch(() => undefined);
|
|
109
|
+
}
|
|
110
|
+
await manager.request("account/logout", undefined);
|
|
111
|
+
return { loggedIn: false, output: "Logged out" };
|
|
186
112
|
}
|
|
187
113
|
async function handleCodexAuthRpcMessage(args) {
|
|
188
114
|
let requestId = "unknown";
|
|
@@ -192,81 +118,20 @@ async function handleCodexAuthRpcMessage(args) {
|
|
|
192
118
|
const request = normalizeCodexAuthRpcRequest({ request: payload, agentId: args.agentId });
|
|
193
119
|
requestId = request.requestId;
|
|
194
120
|
responseSubject = request.responseSubject;
|
|
195
|
-
|
|
196
|
-
runLocalCodexCli: args.runLocalCodexCli,
|
|
197
|
-
stripAnsi: args.stripAnsi,
|
|
198
|
-
});
|
|
199
|
-
let result = null;
|
|
121
|
+
let result;
|
|
200
122
|
if (request.action === "login_api_key") {
|
|
201
|
-
result = await
|
|
202
|
-
apiKey: request.apiKey ?? "",
|
|
203
|
-
runLocalCodexCliWithInput: args.runLocalCodexCliWithInput,
|
|
204
|
-
stripAnsi: args.stripAnsi,
|
|
205
|
-
getLocalCodexLoginStatus: getStatus,
|
|
206
|
-
});
|
|
123
|
+
result = await loginCodexWithApiKey({ manager: args.manager, apiKey: request.apiKey ?? "" });
|
|
207
124
|
}
|
|
208
125
|
else if (request.action === "start") {
|
|
209
|
-
|
|
210
|
-
if (status.loggedIn) {
|
|
211
|
-
result = { loggedIn: true, output: status.output };
|
|
212
|
-
}
|
|
213
|
-
else {
|
|
214
|
-
try {
|
|
215
|
-
result = await startLocalCodexDeviceAuth({
|
|
216
|
-
workspaceRoot: args.workspaceRoot,
|
|
217
|
-
buildLocalCodexCliCommand: args.buildLocalCodexCliCommand,
|
|
218
|
-
resolveShellPath: args.resolveShellPath,
|
|
219
|
-
resolveCodexHomePath: args.resolveCodexHomePath,
|
|
220
|
-
stripAnsi: args.stripAnsi,
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
catch (error) {
|
|
224
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
225
|
-
const normalized = message.toLowerCase();
|
|
226
|
-
if (normalized.includes("operation not permitted") ||
|
|
227
|
-
normalized.includes("failed to read device code") ||
|
|
228
|
-
normalized.includes("panic") ||
|
|
229
|
-
normalized.includes("null object")) {
|
|
230
|
-
result = await startLocalCodexLogin({
|
|
231
|
-
workspaceRoot: args.workspaceRoot,
|
|
232
|
-
buildLocalCodexCliCommand: args.buildLocalCodexCliCommand,
|
|
233
|
-
resolveShellPath: args.resolveShellPath,
|
|
234
|
-
resolveCodexHomePath: args.resolveCodexHomePath,
|
|
235
|
-
stripAnsi: args.stripAnsi,
|
|
236
|
-
getLocalCodexLoginStatus: getStatus,
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
else {
|
|
240
|
-
throw error;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
126
|
+
result = await startCodexDeviceLogin(args.manager);
|
|
244
127
|
}
|
|
245
128
|
else if (request.action === "logout") {
|
|
246
|
-
result = await
|
|
247
|
-
sendSignalToTaskProcess: args.sendSignalToTaskProcess,
|
|
248
|
-
runLocalCodexCli: args.runLocalCodexCli,
|
|
249
|
-
stripAnsi: args.stripAnsi,
|
|
250
|
-
resolveCodexHomePath: args.resolveCodexHomePath,
|
|
251
|
-
getLocalCodexLoginStatus: getStatus,
|
|
252
|
-
});
|
|
129
|
+
result = await logoutCodexAccount(args.manager);
|
|
253
130
|
}
|
|
254
131
|
else {
|
|
255
|
-
|
|
256
|
-
if (
|
|
257
|
-
result =
|
|
258
|
-
}
|
|
259
|
-
else if (pendingCodexDeviceAuth && pendingCodexDeviceAuth.child.exitCode === null) {
|
|
260
|
-
const parsed = parseCodexDeviceAuthOutput(pendingCodexDeviceAuth.output, args.stripAnsi);
|
|
261
|
-
result = {
|
|
262
|
-
loggedIn: false,
|
|
263
|
-
output: pendingCodexDeviceAuthMessage(pendingCodexDeviceAuth, args.stripAnsi),
|
|
264
|
-
verificationUri: parsed.verificationUri,
|
|
265
|
-
userCode: parsed.userCode,
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
else {
|
|
269
|
-
result = { loggedIn: false, output: status.output || "Not logged in" };
|
|
132
|
+
result = await readCodexAccount(args.manager);
|
|
133
|
+
if (!result.loggedIn && pendingCodexAppLogin) {
|
|
134
|
+
result = formatPendingLogin();
|
|
270
135
|
}
|
|
271
136
|
}
|
|
272
137
|
publishCodexAuthRpcResponse({
|
|
@@ -306,14 +171,7 @@ export function subscribeToCodexAuthRpc(args) {
|
|
|
306
171
|
msg,
|
|
307
172
|
nc: args.nc,
|
|
308
173
|
agentId: args.agentId,
|
|
309
|
-
|
|
310
|
-
buildLocalCodexCliCommand: args.buildLocalCodexCliCommand,
|
|
311
|
-
resolveShellPath: args.resolveShellPath,
|
|
312
|
-
resolveCodexHomePath: args.resolveCodexHomePath,
|
|
313
|
-
runLocalCodexCli: args.runLocalCodexCli,
|
|
314
|
-
runLocalCodexCliWithInput: args.runLocalCodexCliWithInput,
|
|
315
|
-
sendSignalToTaskProcess: args.sendSignalToTaskProcess,
|
|
316
|
-
stripAnsi: args.stripAnsi,
|
|
174
|
+
manager: args.manager,
|
|
317
175
|
onError: args.onError,
|
|
318
176
|
});
|
|
319
177
|
},
|
package/dist/agent-codex-cli.js
CHANGED
|
@@ -55,16 +55,6 @@ export function buildDaemonMcpConfigArgs(args) {
|
|
|
55
55
|
workspaceRootEnvName: "DOER_DAEMON_WORKSPACE_ROOT",
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
|
-
export function buildDatabaseMcpConfigArgs(args) {
|
|
59
|
-
return buildWorkspaceMcpConfigArgs({
|
|
60
|
-
agentProjectDir: args.agentProjectDir,
|
|
61
|
-
workspaceRoot: args.workspaceRoot,
|
|
62
|
-
serverName: args.serverName?.trim() || "doer_database",
|
|
63
|
-
distEntryRelativePath: path.join("dist", "db-mcp-server.js"),
|
|
64
|
-
srcEntryRelativePath: path.join("src", "db-mcp-server.ts"),
|
|
65
|
-
workspaceRootEnvName: "DOER_DB_WORKSPACE_ROOT",
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
58
|
function buildWorkspaceMcpConfigArgs(args) {
|
|
69
59
|
const serverName = args.serverName.trim();
|
|
70
60
|
const distEntry = path.join(args.agentProjectDir, args.distEntryRelativePath);
|
|
@@ -240,7 +230,7 @@ export function normalizeShellRpcCodexAuthBundle(value) {
|
|
|
240
230
|
}
|
|
241
231
|
const row = value;
|
|
242
232
|
const authJson = typeof row.authJson === "string" ? row.authJson : null;
|
|
243
|
-
const authMode = row.authMode === "
|
|
233
|
+
const authMode = row.authMode === "chatgpt" ? "chatgpt" : row.authMode === "api_key" ? "api_key" : undefined;
|
|
244
234
|
const apiKey = typeof row.apiKey === "string" || row.apiKey === null ? row.apiKey : undefined;
|
|
245
235
|
if (!authJson && authMode !== "api_key" && apiKey === undefined) {
|
|
246
236
|
return null;
|
|
@@ -4,17 +4,11 @@ export function sanitizeUserId(userId) {
|
|
|
4
4
|
const normalized = userId.trim().replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
5
5
|
return normalized.length > 0 ? normalized : "anonymous";
|
|
6
6
|
}
|
|
7
|
-
export function
|
|
8
|
-
return `doer.agent.
|
|
7
|
+
export function buildAgentCodexAppRpcSubject(userId, agentId) {
|
|
8
|
+
return `doer.agent.codex.app.rpc.${sanitizeUserId(userId)}.${agentId.trim()}`;
|
|
9
9
|
}
|
|
10
|
-
export function
|
|
11
|
-
return `doer.agent.
|
|
12
|
-
}
|
|
13
|
-
export function buildAgentSessionRpcSubject(userId, agentId) {
|
|
14
|
-
return `doer.agent.session.rpc.${sanitizeUserId(userId)}.${agentId.trim()}`;
|
|
15
|
-
}
|
|
16
|
-
export function buildAgentCodexAuthRpcSubject(userId, agentId) {
|
|
17
|
-
return `doer.agent.codex.auth.rpc.${sanitizeUserId(userId)}.${agentId.trim()}`;
|
|
10
|
+
export function buildAgentCodexAppEventsSubject(userId, agentId) {
|
|
11
|
+
return `doer.agent.codex.app.events.${sanitizeUserId(userId)}.${agentId.trim()}`;
|
|
18
12
|
}
|
|
19
13
|
export function buildAgentSettingsRpcSubject(userId, agentId) {
|
|
20
14
|
return `doer.agent.settings.rpc.${sanitizeUserId(userId)}.${agentId.trim()}`;
|
|
@@ -78,25 +72,6 @@ export function normalizeEnvPatch(value) {
|
|
|
78
72
|
}
|
|
79
73
|
return out;
|
|
80
74
|
}
|
|
81
|
-
export function normalizeRunImagePaths(value) {
|
|
82
|
-
if (!Array.isArray(value)) {
|
|
83
|
-
return [];
|
|
84
|
-
}
|
|
85
|
-
const seen = new Set();
|
|
86
|
-
const out = [];
|
|
87
|
-
for (const item of value) {
|
|
88
|
-
if (typeof item !== "string") {
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
const normalized = item.trim();
|
|
92
|
-
if (!normalized || seen.has(normalized)) {
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
seen.add(normalized);
|
|
96
|
-
out.push(normalized);
|
|
97
|
-
}
|
|
98
|
-
return out;
|
|
99
|
-
}
|
|
100
75
|
const PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
|
|
101
76
|
const crc32Table = (() => {
|
|
102
77
|
const table = new Uint32Array(256);
|
|
@@ -162,28 +137,6 @@ export function validateImageBytes(filePath, bytes) {
|
|
|
162
137
|
}
|
|
163
138
|
return null;
|
|
164
139
|
}
|
|
165
|
-
export async function filterValidRunImagePaths(args) {
|
|
166
|
-
const valid = [];
|
|
167
|
-
for (const imagePath of args.imagePaths) {
|
|
168
|
-
const absPath = path.isAbsolute(imagePath) ? imagePath : path.resolve(args.workspaceRoot, imagePath);
|
|
169
|
-
let bytes;
|
|
170
|
-
try {
|
|
171
|
-
bytes = await readFile(absPath);
|
|
172
|
-
}
|
|
173
|
-
catch (error) {
|
|
174
|
-
const reason = error instanceof Error ? error.message : "failed to read image";
|
|
175
|
-
args.onInvalidImage?.(imagePath, reason);
|
|
176
|
-
continue;
|
|
177
|
-
}
|
|
178
|
-
const validationError = validateImageBytes(absPath, bytes);
|
|
179
|
-
if (validationError) {
|
|
180
|
-
args.onInvalidImage?.(imagePath, validationError);
|
|
181
|
-
continue;
|
|
182
|
-
}
|
|
183
|
-
valid.push(imagePath);
|
|
184
|
-
}
|
|
185
|
-
return valid;
|
|
186
|
-
}
|
|
187
140
|
export function fatalExit(message, error, writeAgentError) {
|
|
188
141
|
const detail = error instanceof Error ? error.message : typeof error === "string" ? error : error ? String(error) : "";
|
|
189
142
|
const full = detail ? `${message}: ${detail}` : message;
|
|
@@ -207,20 +160,6 @@ export function writeRpcStream(requestId, stream, chunk) {
|
|
|
207
160
|
target.write(`[doer-agent][rpc=${requestId}][${stream}] ${line}\n`);
|
|
208
161
|
}
|
|
209
162
|
}
|
|
210
|
-
export function writeRunStatus(runId, message) {
|
|
211
|
-
process.stdout.write(`[doer-agent][run=${runId}][status] ${message}\n`);
|
|
212
|
-
}
|
|
213
|
-
export function writeRunStream(runId, stream, chunk) {
|
|
214
|
-
const target = stream === "stdout" ? process.stdout : process.stderr;
|
|
215
|
-
const lines = chunk.split(/\r?\n/);
|
|
216
|
-
for (let index = 0; index < lines.length; index += 1) {
|
|
217
|
-
const line = lines[index];
|
|
218
|
-
if (!line && index === lines.length - 1) {
|
|
219
|
-
continue;
|
|
220
|
-
}
|
|
221
|
-
target.write(`[doer-agent][run=${runId}][${stream}] ${line}\n`);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
163
|
function resolveLogTimeZone() {
|
|
225
164
|
const configured = process.env.DOER_AGENT_LOG_TIMEZONE?.trim() || process.env.TZ?.trim();
|
|
226
165
|
return configured && configured.length > 0 ? configured : "Asia/Seoul";
|
|
@@ -46,7 +46,6 @@ export async function runConnectedAgentSession(args) {
|
|
|
46
46
|
args.subscribeAll();
|
|
47
47
|
const closeError = await args.jetstream.nc.closed();
|
|
48
48
|
clearInterval(heartbeatTimer);
|
|
49
|
-
args.stopAllSessionWatchers();
|
|
50
49
|
const detail = closeError instanceof Error ? closeError.message : "clean close";
|
|
51
50
|
args.onInfraError(`nats session ended: ${detail}; reconnecting`);
|
|
52
51
|
await args.sleep(1000);
|