doer-agent 0.6.5 → 0.6.7

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.
@@ -55,6 +55,22 @@ export function buildDaemonMcpConfigArgs(args) {
55
55
  workspaceRootEnvName: "DOER_DAEMON_WORKSPACE_ROOT",
56
56
  });
57
57
  }
58
+ export function buildMobileMcpConfigArgs(args) {
59
+ return buildWorkspaceMcpConfigArgs({
60
+ agentProjectDir: args.agentProjectDir,
61
+ workspaceRoot: args.workspaceRoot,
62
+ serverName: args.serverName?.trim() || "doer_mobile",
63
+ distEntryRelativePath: path.join("dist", "mobile-mcp-server.js"),
64
+ srcEntryRelativePath: path.join("src", "mobile-mcp-server.ts"),
65
+ workspaceRootEnvName: "DOER_MOBILE_WORKSPACE_ROOT",
66
+ env: {
67
+ DOER_MOBILE_SERVER_BASE_URL: args.serverBaseUrl,
68
+ DOER_MOBILE_USER_ID: args.userId,
69
+ DOER_MOBILE_AGENT_ID: args.agentId,
70
+ DOER_AGENT_TOKEN: args.agentToken,
71
+ },
72
+ });
73
+ }
58
74
  function buildWorkspaceMcpConfigArgs(args) {
59
75
  const serverName = args.serverName.trim();
60
76
  const distEntry = path.join(args.agentProjectDir, args.distEntryRelativePath);
@@ -64,7 +80,7 @@ function buildWorkspaceMcpConfigArgs(args) {
64
80
  const commandArgs = existsSync(distEntry)
65
81
  ? [distEntry, "--workspace-root", args.workspaceRoot]
66
82
  : ["--import", tsxLoaderPath, srcEntry, "--workspace-root", args.workspaceRoot];
67
- return [
83
+ const configArgs = [
68
84
  "--config",
69
85
  `mcp_servers.${serverName}.command=${toTomlStringLiteral(command)}`,
70
86
  "--config",
@@ -74,6 +90,10 @@ function buildWorkspaceMcpConfigArgs(args) {
74
90
  "--config",
75
91
  `mcp_servers.${serverName}.enabled=true`,
76
92
  ];
93
+ for (const [key, value] of Object.entries(args.env ?? {})) {
94
+ configArgs.push("--config", `mcp_servers.${serverName}.env.${key}=${toTomlStringLiteral(value)}`);
95
+ }
96
+ return configArgs;
77
97
  }
78
98
  export function buildLocalCodexCliCommand(args) {
79
99
  const quotedArgs = args.map(shellSingleQuote).join(" ");
@@ -17,6 +17,7 @@ export function createDefaultAgentSettingsConfig() {
17
17
  codex: {
18
18
  model: "gpt-5.5",
19
19
  reasoningEffort: "medium",
20
+ serviceTier: null,
20
21
  authMode: "api_key",
21
22
  computerUseEnabled: false,
22
23
  browserUseEnabled: false,
@@ -52,6 +53,13 @@ function normalizeNullableString(value) {
52
53
  const trimmed = value.trim();
53
54
  return trimmed ? trimmed : null;
54
55
  }
56
+ function normalizeServiceTier(value) {
57
+ const normalized = normalizeNullableString(value);
58
+ if (normalized === "priority") {
59
+ return "fast";
60
+ }
61
+ return normalized;
62
+ }
55
63
  function normalizeCodexPersonality(value, fallback) {
56
64
  return value === "friendly" || value === "pragmatic" ? value : fallback;
57
65
  }
@@ -122,6 +130,7 @@ export function normalizeAgentSettingsConfig(value, fallback) {
122
130
  codex: {
123
131
  model: typeof codex.model === "string" && codex.model.trim() ? codex.model.trim() : base.codex.model,
124
132
  reasoningEffort: normalizeReasoningEffort(codex.reasoningEffort, base.codex.reasoningEffort),
133
+ serviceTier: codex.serviceTier === null ? null : normalizeServiceTier(codex.serviceTier) ?? base.codex.serviceTier,
125
134
  authMode: codex.authMode === "chatgpt" ? "chatgpt" : codex.authMode === "api_key" ? "api_key" : base.codex.authMode,
126
135
  computerUseEnabled: typeof codex.computerUseEnabled === "boolean" ? codex.computerUseEnabled : base.codex.computerUseEnabled,
127
136
  browserUseEnabled: typeof codex.browserUseEnabled === "boolean" ? codex.browserUseEnabled : base.codex.browserUseEnabled,
@@ -202,6 +211,7 @@ export async function toAgentSettingsPublic(args) {
202
211
  codex: {
203
212
  model: args.config.codex.model,
204
213
  reasoningEffort: args.config.codex.reasoningEffort,
214
+ serviceTier: args.config.codex.serviceTier,
205
215
  authMode: args.config.codex.authMode,
206
216
  computerUseEnabled: args.config.codex.computerUseEnabled,
207
217
  browserUseEnabled: args.config.codex.browserUseEnabled,
@@ -260,6 +270,7 @@ export function normalizeAgentSettingsPatch(value) {
260
270
  move("personality", "general", "personality");
261
271
  move("codexModel", "codex", "model");
262
272
  move("codexReasoningEffort", "codex", "reasoningEffort");
273
+ move("codexServiceTier", "codex", "serviceTier");
263
274
  move("codexAuthMode", "codex", "authMode");
264
275
  move("computerUseEnabled", "codex", "computerUseEnabled");
265
276
  move("browserUseEnabled", "codex", "browserUseEnabled");
package/dist/agent.js CHANGED
@@ -224,10 +224,14 @@ async function main() {
224
224
  heartbeatAgentSession: heartbeatSession,
225
225
  subscribeAll: () => {
226
226
  const codexAppServerManager = createCodexAppServerManager({
227
+ agentId: initialAgentId,
228
+ agentToken,
227
229
  workspaceRoot: resolveWorkspaceRoot(),
228
230
  agentProjectDir: AGENT_PROJECT_DIR,
231
+ serverBaseUrl,
229
232
  resolveCodexHomePath: runtimeEnvHelpers.resolveCodexHomePath,
230
233
  readAgentSettingsConfig,
234
+ userId,
231
235
  onLog: writeAgentInfo,
232
236
  onNotification: (method, params) => {
233
237
  if (!shouldForwardCodexAppNotification(method)) {
@@ -1,5 +1,5 @@
1
1
  import { buildAgentSettingsEnvPatch, readAgentModelInstructions, resolveAgentModelInstructionsFilePath, } from "./agent-settings.js";
2
- import { buildDaemonMcpConfigArgs } from "./agent-codex-cli.js";
2
+ import { buildDaemonMcpConfigArgs, buildMobileMcpConfigArgs } from "./agent-codex-cli.js";
3
3
  import { CodexAppServerClient } from "./codex-app-server-client.js";
4
4
  function toTomlStringLiteral(value) {
5
5
  return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
@@ -14,6 +14,9 @@ async function buildCodexAppServerArgs(args) {
14
14
  const configArgs = [
15
15
  ...buildConfigArg("model", toTomlStringLiteral(args.settings.codex.model)),
16
16
  ...buildConfigArg("model_reasoning_effort", toTomlStringLiteral(args.settings.codex.reasoningEffort)),
17
+ ...(args.settings.codex.serviceTier
18
+ ? buildConfigArg("service_tier", toTomlStringLiteral(args.settings.codex.serviceTier))
19
+ : []),
17
20
  ...buildConfigArg("personality", toTomlStringLiteral(args.settings.general.personality)),
18
21
  ...buildConfigArg("approval_policy", toTomlStringLiteral("never")),
19
22
  ...buildConfigArg("sandbox_mode", toTomlStringLiteral("danger-full-access")),
@@ -29,6 +32,14 @@ async function buildCodexAppServerArgs(args) {
29
32
  agentProjectDir: args.agentProjectDir,
30
33
  workspaceRoot: args.workspaceRoot,
31
34
  }),
35
+ ...buildMobileMcpConfigArgs({
36
+ agentId: args.agentId,
37
+ agentProjectDir: args.agentProjectDir,
38
+ agentToken: args.agentToken,
39
+ serverBaseUrl: args.serverBaseUrl,
40
+ userId: args.userId,
41
+ workspaceRoot: args.workspaceRoot,
42
+ }),
32
43
  ...buildFeatureArg(true, "goals"),
33
44
  ...buildFeatureArg(args.settings.codex.computerUseEnabled, "computer_use"),
34
45
  ...buildFeatureArg(args.settings.codex.browserUseEnabled, "browser_use"),
@@ -41,6 +52,10 @@ async function buildCodexAppServerEnv(args) {
41
52
  ...process.env,
42
53
  ...buildAgentSettingsEnvPatch(args.settings),
43
54
  CODEX_HOME: args.resolveCodexHomePath(),
55
+ DOER_AGENT_TOKEN: args.agentToken,
56
+ DOER_MOBILE_AGENT_ID: args.agentId,
57
+ DOER_MOBILE_SERVER_BASE_URL: args.serverBaseUrl,
58
+ DOER_MOBILE_USER_ID: args.userId,
44
59
  };
45
60
  }
46
61
  export function createCodexAppServerManager(args) {
@@ -50,14 +65,22 @@ export function createCodexAppServerManager(args) {
50
65
  const createClient = async () => {
51
66
  const settings = await args.readAgentSettingsConfig({ workspaceRoot: args.workspaceRoot });
52
67
  const appServerArgs = await buildCodexAppServerArgs({
68
+ agentId: args.agentId,
69
+ agentToken: args.agentToken,
53
70
  workspaceRoot: args.workspaceRoot,
54
71
  agentProjectDir: args.agentProjectDir,
72
+ serverBaseUrl: args.serverBaseUrl,
55
73
  settings,
74
+ userId: args.userId,
56
75
  });
57
76
  const env = await buildCodexAppServerEnv({
77
+ agentId: args.agentId,
78
+ agentToken: args.agentToken,
58
79
  workspaceRoot: args.workspaceRoot,
80
+ serverBaseUrl: args.serverBaseUrl,
59
81
  resolveCodexHomePath: args.resolveCodexHomePath,
60
82
  settings,
83
+ userId: args.userId,
61
84
  });
62
85
  args.onLog?.(`starting codex app-server model=${settings.codex.model} reasoningEffort=${settings.codex.reasoningEffort} personality=${settings.general.personality} computerUse=${settings.codex.computerUseEnabled} browserUse=${settings.codex.browserUseEnabled}`);
63
86
  return new CodexAppServerClient({
@@ -0,0 +1,254 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import * as z from "zod/v4";
4
+ function env(name) {
5
+ const value = process.env[name]?.trim() || "";
6
+ if (!value) {
7
+ throw new Error(`${name} is required`);
8
+ }
9
+ return value;
10
+ }
11
+ function optionalEnv(name) {
12
+ return process.env[name]?.trim() || "";
13
+ }
14
+ function formatJson(value) {
15
+ return JSON.stringify(value, null, 2);
16
+ }
17
+ function normalizeLimit(value, fallback, max) {
18
+ if (!Number.isFinite(value) || !value) {
19
+ return fallback;
20
+ }
21
+ return Math.min(max, Math.max(1, Math.trunc(value)));
22
+ }
23
+ function getConfig() {
24
+ return {
25
+ agentId: env("DOER_MOBILE_AGENT_ID"),
26
+ agentToken: env("DOER_AGENT_TOKEN"),
27
+ serverBaseUrl: env("DOER_MOBILE_SERVER_BASE_URL").replace(/\/$/, ""),
28
+ userId: env("DOER_MOBILE_USER_ID"),
29
+ };
30
+ }
31
+ async function requestJson(path) {
32
+ const config = getConfig();
33
+ const response = await fetch(`${config.serverBaseUrl}${path}`, {
34
+ headers: {
35
+ Authorization: `Bearer ${config.agentToken}`,
36
+ Accept: "application/json",
37
+ },
38
+ });
39
+ const data = await response.json().catch(() => ({}));
40
+ if (!response.ok) {
41
+ throw new Error(typeof data.error === "string" ? data.error : `Doer server returned ${response.status}`);
42
+ }
43
+ return data;
44
+ }
45
+ async function listMobileAgents() {
46
+ const config = getConfig();
47
+ const data = await requestJson(`/api/users/${encodeURIComponent(config.userId)}/agents/${encodeURIComponent(config.agentId)}/mobile-agents`);
48
+ return Array.isArray(data.mobileAgents) ? data.mobileAgents : [];
49
+ }
50
+ async function resolveDeviceId(deviceId) {
51
+ const normalized = deviceId?.trim() || "";
52
+ if (normalized) {
53
+ return normalized;
54
+ }
55
+ const mobileAgents = await listMobileAgents();
56
+ const first = mobileAgents[0]?.deviceId?.trim() || "";
57
+ if (!first) {
58
+ throw new Error("No mobile agents registered");
59
+ }
60
+ return first;
61
+ }
62
+ async function getMobileInfo(deviceId) {
63
+ const config = getConfig();
64
+ const resolvedDeviceId = await resolveDeviceId(deviceId);
65
+ return await requestJson(`/api/users/${encodeURIComponent(config.userId)}/agents/${encodeURIComponent(config.agentId)}/mobile-agents/${encodeURIComponent(resolvedDeviceId)}/info`);
66
+ }
67
+ async function getMobileLogs(args) {
68
+ const config = getConfig();
69
+ const resolvedDeviceId = await resolveDeviceId(args.deviceId);
70
+ const qs = new URLSearchParams({
71
+ limit: String(normalizeLimit(args.limit, 100, 1000)),
72
+ });
73
+ if (Number.isInteger(args.beforeSeq)) {
74
+ qs.set("beforeSeq", String(args.beforeSeq));
75
+ }
76
+ if (Number.isInteger(args.afterSeq)) {
77
+ qs.set("afterSeq", String(args.afterSeq));
78
+ }
79
+ const data = await requestJson(`/api/users/${encodeURIComponent(config.userId)}/agents/${encodeURIComponent(config.agentId)}/mobile-agents/${encodeURIComponent(resolvedDeviceId)}/events?${qs.toString()}`);
80
+ return {
81
+ events: Array.isArray(data.events) ? data.events : [],
82
+ hasMore: data.hasMore === true,
83
+ latestSeq: typeof data.latestSeq === "number" ? data.latestSeq : null,
84
+ nextBeforeSeq: typeof data.nextBeforeSeq === "number" ? data.nextBeforeSeq : null,
85
+ };
86
+ }
87
+ async function searchMobileLogs(args) {
88
+ const config = getConfig();
89
+ const resolvedDeviceId = await resolveDeviceId(args.deviceId);
90
+ const qs = new URLSearchParams({
91
+ limit: String(normalizeLimit(args.limit, 100, 1000)),
92
+ });
93
+ if (args.kind?.trim()) {
94
+ qs.set("kind", args.kind.trim());
95
+ }
96
+ if (args.query?.trim()) {
97
+ qs.set("query", args.query.trim());
98
+ }
99
+ if (args.since?.trim()) {
100
+ qs.set("since", args.since.trim());
101
+ }
102
+ if (args.until?.trim()) {
103
+ qs.set("until", args.until.trim());
104
+ }
105
+ if (Number.isInteger(args.beforeSeq)) {
106
+ qs.set("beforeSeq", String(args.beforeSeq));
107
+ }
108
+ if (Number.isInteger(args.afterSeq)) {
109
+ qs.set("afterSeq", String(args.afterSeq));
110
+ }
111
+ const data = await requestJson(`/api/users/${encodeURIComponent(config.userId)}/agents/${encodeURIComponent(config.agentId)}/mobile-agents/${encodeURIComponent(resolvedDeviceId)}/events?${qs.toString()}`);
112
+ return {
113
+ events: Array.isArray(data.events) ? data.events : [],
114
+ hasMore: data.hasMore === true,
115
+ latestSeq: typeof data.latestSeq === "number" ? data.latestSeq : null,
116
+ nextBeforeSeq: typeof data.nextBeforeSeq === "number" ? data.nextBeforeSeq : null,
117
+ };
118
+ }
119
+ function extractLatestLocation(events) {
120
+ for (let index = events.length - 1; index >= 0; index -= 1) {
121
+ const event = events[index];
122
+ if (event?.kind === "location.current" || event?.kind === "location.changed") {
123
+ return event;
124
+ }
125
+ }
126
+ return null;
127
+ }
128
+ function extractLocationEvents(events) {
129
+ return events.filter((event) => event.kind.startsWith("location."));
130
+ }
131
+ function extractNotifications(events) {
132
+ return events.filter((event) => event.kind.startsWith("notification."));
133
+ }
134
+ async function main() {
135
+ const server = new McpServer({
136
+ name: "doer-mobile",
137
+ version: "0.1.0",
138
+ }, {
139
+ capabilities: {
140
+ tools: {},
141
+ },
142
+ instructions: "Inspect mobile agents paired with the current Doer agent. Use these tools to list devices, poll cursor-based logs, and inspect latest location/notification context.",
143
+ });
144
+ server.registerTool("mobile_list", {
145
+ description: "List mobile agents paired with the current Doer agent.",
146
+ inputSchema: {},
147
+ }, async () => {
148
+ const mobileAgents = await listMobileAgents();
149
+ return {
150
+ content: [{ type: "text", text: formatJson({ mobileAgents }) }],
151
+ structuredContent: { mobileAgents },
152
+ };
153
+ });
154
+ server.registerTool("mobile_info", {
155
+ description: "Read info for a mobile agent. Defaults to the first registered device.",
156
+ inputSchema: {
157
+ deviceId: z.string().optional().describe("Mobile device id. Defaults to the first registered mobile agent."),
158
+ },
159
+ }, async ({ deviceId }) => {
160
+ const result = await getMobileInfo(deviceId);
161
+ return {
162
+ content: [{ type: "text", text: formatJson(result) }],
163
+ structuredContent: result,
164
+ };
165
+ });
166
+ server.registerTool("mobile_logs", {
167
+ description: "Read a cursor-based page of mobile log events from the device-local SQLite log.",
168
+ inputSchema: {
169
+ afterSeq: z.number().int().min(0).optional().describe("Return events newer than this seq, in ascending order."),
170
+ beforeSeq: z.number().int().min(1).optional().describe("Return events older than this seq, in ascending order."),
171
+ deviceId: z.string().optional().describe("Mobile device id. Defaults to the first registered mobile agent."),
172
+ limit: z.number().int().min(1).max(1000).optional().describe("Maximum number of recent events."),
173
+ },
174
+ }, async ({ afterSeq, beforeSeq, deviceId, limit }) => {
175
+ const page = await getMobileLogs({ afterSeq, beforeSeq, deviceId, limit });
176
+ return {
177
+ content: [{ type: "text", text: formatJson(page) }],
178
+ structuredContent: page,
179
+ };
180
+ });
181
+ server.registerTool("mobile_search_logs", {
182
+ description: "Search the device-local mobile SQLite event log by kind, text query, cursor, and observed time range.",
183
+ inputSchema: {
184
+ afterSeq: z.number().int().min(0).optional().describe("Return matching events newer than this seq, in ascending order."),
185
+ beforeSeq: z.number().int().min(1).optional().describe("Return matching events older than this seq, in ascending order."),
186
+ deviceId: z.string().optional().describe("Mobile device id. Defaults to the first registered mobile agent."),
187
+ kind: z.string().optional().describe("Event kind or kind prefix, such as location.current or notification."),
188
+ query: z.string().optional().describe("Case-insensitive text to search in the event kind or payload."),
189
+ since: z.string().optional().describe("Inclusive ISO timestamp lower bound."),
190
+ until: z.string().optional().describe("Inclusive ISO timestamp upper bound."),
191
+ limit: z.number().int().min(1).max(1000).optional().describe("Maximum number of matching events."),
192
+ },
193
+ }, async ({ afterSeq, beforeSeq, deviceId, kind, query, since, until, limit }) => {
194
+ const page = await searchMobileLogs({ afterSeq, beforeSeq, deviceId, kind, query, since, until, limit });
195
+ return {
196
+ content: [{ type: "text", text: formatJson(page) }],
197
+ structuredContent: page,
198
+ };
199
+ });
200
+ server.registerTool("mobile_latest_location", {
201
+ description: "Return the latest location event from recent mobile logs.",
202
+ inputSchema: {
203
+ deviceId: z.string().optional().describe("Mobile device id. Defaults to the first registered mobile agent."),
204
+ limit: z.number().int().min(1).max(1000).optional().describe("How many recent events to scan."),
205
+ },
206
+ }, async ({ deviceId, limit }) => {
207
+ const page = await searchMobileLogs({ deviceId, kind: "location.", limit: limit ?? 200 });
208
+ const location = extractLatestLocation(page.events);
209
+ return {
210
+ content: [{ type: "text", text: formatJson({ location }) }],
211
+ structuredContent: { location },
212
+ };
213
+ });
214
+ server.registerTool("mobile_location_history", {
215
+ description: "Return time-range location events recorded by the mobile agent.",
216
+ inputSchema: {
217
+ afterSeq: z.number().int().min(0).optional().describe("Return location events newer than this seq, in ascending order."),
218
+ beforeSeq: z.number().int().min(1).optional().describe("Return location events older than this seq, in ascending order."),
219
+ deviceId: z.string().optional().describe("Mobile device id. Defaults to the first registered mobile agent."),
220
+ since: z.string().optional().describe("Inclusive ISO timestamp lower bound."),
221
+ until: z.string().optional().describe("Inclusive ISO timestamp upper bound."),
222
+ limit: z.number().int().min(1).max(1000).optional().describe("Maximum number of matching location events."),
223
+ },
224
+ }, async ({ afterSeq, beforeSeq, deviceId, since, until, limit }) => {
225
+ const page = await searchMobileLogs({ afterSeq, beforeSeq, deviceId, kind: "location.", since, until, limit: limit ?? 200 });
226
+ const events = extractLocationEvents(page.events);
227
+ const result = { ...page, events };
228
+ return {
229
+ content: [{ type: "text", text: formatJson(result) }],
230
+ structuredContent: result,
231
+ };
232
+ });
233
+ server.registerTool("mobile_notifications", {
234
+ description: "Return recent notification events from mobile logs.",
235
+ inputSchema: {
236
+ deviceId: z.string().optional().describe("Mobile device id. Defaults to the first registered mobile agent."),
237
+ limit: z.number().int().min(1).max(1000).optional().describe("How many recent events to scan."),
238
+ },
239
+ }, async ({ deviceId, limit }) => {
240
+ const page = await searchMobileLogs({ deviceId, kind: "notification.", limit: limit ?? 200 });
241
+ const events = extractNotifications(page.events);
242
+ return {
243
+ content: [{ type: "text", text: formatJson({ events }) }],
244
+ structuredContent: { events },
245
+ };
246
+ });
247
+ const transport = new StdioServerTransport();
248
+ await server.connect(transport);
249
+ }
250
+ main().catch((error) => {
251
+ const message = error instanceof Error ? error.stack || error.message : String(error);
252
+ process.stderr.write(`${message}\n`);
253
+ process.exit(1);
254
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doer-agent",
3
- "version": "0.6.5",
3
+ "version": "0.6.7",
4
4
  "description": "Reverse-polling agent runtime for doer",
5
5
  "type": "module",
6
6
  "main": "dist/agent.js",