doer-agent 0.5.8 → 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.
@@ -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 pendingCodexDeviceAuth = null;
7
- function parseCodexDeviceAuthOutput(raw, stripAnsi) {
8
- const text = stripAnsi(raw);
9
- const urlMatch = text.match(/https?:\/\/[^\s]+/i);
10
- const codeMatch = text.match(/\b[A-Z0-9]{4,}(?:-[A-Z0-9]{4,})+\b/);
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 pendingCodexDeviceAuthMessage(state, stripAnsi) {
17
- const parsed = parseCodexDeviceAuthOutput(state.output, stripAnsi);
18
- if (parsed.verificationUri && parsed.userCode) {
19
- return `Waiting for approval. Enter code ${parsed.userCode} at ${parsed.verificationUri}`;
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
- return stripAnsi(state.output).trim() || "Waiting for approval";
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
- async function getLocalCodexLoginStatus(args) {
24
- const result = await args.runLocalCodexCli(["login", "status"], 5000);
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
- async function waitForCodexDeviceCode(state, timeoutMs, stripAnsi) {
32
- const startedAt = Date.now();
33
- while (Date.now() - startedAt < timeoutMs) {
34
- const parsed = parseCodexDeviceAuthOutput(state.output, stripAnsi);
35
- if (parsed.verificationUri && parsed.userCode) {
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
- async function startLocalCodexDeviceAuth(args) {
45
- if (pendingCodexDeviceAuth && pendingCodexDeviceAuth.child.exitCode === null) {
46
- const parsed = parseCodexDeviceAuthOutput(pendingCodexDeviceAuth.output, args.stripAnsi);
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: false,
49
- output: pendingCodexDeviceAuthMessage(pendingCodexDeviceAuth, args.stripAnsi),
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
- const child = spawn(args.buildLocalCodexCliCommand(["login", "--device-auth"]), {
55
- cwd: args.workspaceRoot,
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
- async function startLocalCodexLogin(args) {
92
- const child = spawn(args.buildLocalCodexCliCommand(["login"]), {
93
- cwd: args.workspaceRoot,
94
- shell: args.resolveShellPath(),
95
- detached: process.platform !== "win32",
96
- env: {
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: status?.loggedIn === true,
122
- output: status?.output || normalized || "Login started",
123
- verificationUri: parsed.verificationUri,
124
- userCode: parsed.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
- throw new Error(normalized || `Codex login failed with code ${result.code ?? "null"}`);
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 loginLocalCodexWithApiKey(args) {
130
- const result = await args.runLocalCodexCliWithInput(["login", "--with-api-key"], args.apiKey, 15000);
131
- const normalized = args.stripAnsi([result.stdout, result.stderr].filter(Boolean).join("\n")).trim();
132
- if ((result.code ?? 1) !== 0) {
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
- const status = await args.getLocalCodexLoginStatus().catch(() => null);
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 logoutLocalCodexAuth(args) {
144
- if (pendingCodexDeviceAuth && pendingCodexDeviceAuth.child.exitCode === null) {
145
- args.sendSignalToTaskProcess(pendingCodexDeviceAuth.child, "SIGTERM");
146
- setTimeout(() => {
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
- const result = await args.runLocalCodexCli(["logout"], 5000);
154
- let merged = args.stripAnsi([result.stdout, result.stderr].filter(Boolean).join("\n")).trim();
155
- const statusAfterLogout = await args.getLocalCodexLoginStatus().catch(() => null);
156
- if (statusAfterLogout?.loggedIn) {
157
- const authFile = path.join(args.resolveCodexHomePath(), "auth.json");
158
- await unlink(authFile).catch(() => undefined);
159
- const statusAfterDelete = await args.getLocalCodexLoginStatus().catch(() => null);
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
- return {
165
- loggedIn: false,
166
- output: merged || "Logged out",
91
+ pendingCodexAppLogin = {
92
+ loginId,
93
+ verificationUri: stringValue(result?.verificationUrl),
94
+ userCode: stringValue(result?.userCode),
167
95
  };
96
+ return formatPendingLogin();
168
97
  }
169
- function normalizeCodexAuthRpcRequest(args) {
170
- const requestId = typeof args.request.requestId === "string" ? args.request.requestId.trim() : "";
171
- const responseSubject = typeof args.request.responseSubject === "string" ? args.request.responseSubject.trim() : "";
172
- const requestAgentId = typeof args.request.agentId === "string" ? args.request.agentId.trim() : "";
173
- const actionRaw = typeof args.request.action === "string" ? args.request.action.trim() : "";
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 publishCodexAuthRpcResponse(args) {
185
- args.nc.publish(args.responseSubject, codexAuthRpcCodec.encode(JSON.stringify(args.payload)));
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
- const getStatus = () => getLocalCodexLoginStatus({
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 loginLocalCodexWithApiKey({
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
- const status = await getStatus();
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 logoutLocalCodexAuth({
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
- const status = await getStatus();
256
- if (status.loggedIn) {
257
- result = { loggedIn: true, output: status.output };
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
- workspaceRoot: args.workspaceRoot,
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
  },
@@ -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 === "oauth" ? "oauth" : row.authMode === "api_key" ? "api_key" : undefined;
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 buildAgentRunRpcSubject(userId, agentId) {
8
- return `doer.agent.run.rpc.${sanitizeUserId(userId)}.${agentId.trim()}`;
7
+ export function buildAgentCodexAppRpcSubject(userId, agentId) {
8
+ return `doer.agent.codex.app.rpc.${sanitizeUserId(userId)}.${agentId.trim()}`;
9
9
  }
10
- export function buildAgentRunEventsSubject(userId, agentId) {
11
- return `doer.agent.run.events.${sanitizeUserId(userId)}.${agentId.trim()}`;
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);