doer-agent 0.7.0 → 0.7.2

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.
@@ -1,10 +1,32 @@
1
1
  import { StringCodec } from "nats";
2
2
  const codexAppRpcCodec = StringCodec();
3
+ const codexAppRpcOmitRules = [
4
+ {
5
+ method: "thread/turns/list",
6
+ itemType: "imageGeneration",
7
+ keys: ["result"],
8
+ },
9
+ ];
3
10
  function recordValue(value) {
4
11
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
5
12
  }
6
- function stripLargeThreadTurnItemFields(method, value) {
7
- if (method !== "thread/turns/list") {
13
+ function omitKeysFromRecord(record, keys) {
14
+ const next = { ...record };
15
+ for (const key of keys) {
16
+ delete next[key];
17
+ }
18
+ return next;
19
+ }
20
+ function omitRulesForThreadTurnItem(method, item) {
21
+ return codexAppRpcOmitRules.filter((rule) => {
22
+ if (rule.method !== method) {
23
+ return false;
24
+ }
25
+ return !rule.itemType || item.type === rule.itemType;
26
+ });
27
+ }
28
+ function applyCodexAppRpcOmitRules(method, value) {
29
+ if (!codexAppRpcOmitRules.some((rule) => rule.method === method)) {
8
30
  return value;
9
31
  }
10
32
  const response = recordValue(value);
@@ -22,12 +44,14 @@ function stripLargeThreadTurnItemFields(method, value) {
22
44
  ...turn,
23
45
  items: turn.items.map((itemValue) => {
24
46
  const item = recordValue(itemValue);
25
- if (!item || item.type !== "imageGeneration" || typeof item.result !== "string") {
47
+ if (!item) {
48
+ return itemValue;
49
+ }
50
+ const rules = omitRulesForThreadTurnItem(method, item);
51
+ if (rules.length === 0) {
26
52
  return itemValue;
27
53
  }
28
- const withoutResult = { ...item };
29
- delete withoutResult.result;
30
- return withoutResult;
54
+ return rules.reduce((current, rule) => omitKeysFromRecord(current, rule.keys), item);
31
55
  }),
32
56
  };
33
57
  }),
@@ -54,7 +78,7 @@ async function handleCodexAppRpcMessage(args) {
54
78
  const payload = JSON.parse(codexAppRpcCodec.decode(args.msg.data));
55
79
  const request = normalizeCodexAppRpcRequest({ request: payload, agentId: args.agentId });
56
80
  requestId = request.requestId;
57
- const result = stripLargeThreadTurnItemFields(request.method, await args.manager.request(request.method, request.params));
81
+ const result = applyCodexAppRpcOmitRules(request.method, await args.manager.request(request.method, request.params));
58
82
  args.msg.respond(codexAppRpcCodec.encode(JSON.stringify({
59
83
  requestId,
60
84
  ok: true,
@@ -100,6 +100,29 @@ async function showMobileNotification(args) {
100
100
  url: args.url,
101
101
  });
102
102
  }
103
+ async function showMobileConfirmation(args) {
104
+ const config = getConfig();
105
+ const resolvedDeviceId = await resolveDeviceId(args.deviceId);
106
+ return await postJson(`/api/users/${encodeURIComponent(config.userId)}/agents/${encodeURIComponent(config.agentId)}/mobile-agents/${encodeURIComponent(resolvedDeviceId)}/show-confirmation`, {
107
+ title: args.title,
108
+ text: args.text,
109
+ requestId: args.requestId,
110
+ yesLabel: args.yesLabel,
111
+ noLabel: args.noLabel,
112
+ notificationId: args.notificationId,
113
+ });
114
+ }
115
+ async function runMobileCommand(args) {
116
+ const config = getConfig();
117
+ const resolvedDeviceId = await resolveDeviceId(args.deviceId);
118
+ return await postJson(`/api/users/${encodeURIComponent(config.userId)}/agents/${encodeURIComponent(config.agentId)}/mobile-agents/${encodeURIComponent(resolvedDeviceId)}/run-command`, {
119
+ command: args.command,
120
+ cwd: args.cwd,
121
+ env: args.env,
122
+ timeoutMs: args.timeoutMs,
123
+ maxOutputBytes: args.maxOutputBytes,
124
+ });
125
+ }
103
126
  async function getMobileLogs(args) {
104
127
  const config = getConfig();
105
128
  const resolvedDeviceId = await resolveDeviceId(args.deviceId);
@@ -167,6 +190,9 @@ function extractLocationEvents(events) {
167
190
  function extractNotifications(events) {
168
191
  return events.filter((event) => event.kind.startsWith("notification."));
169
192
  }
193
+ function extractConfirmations(events) {
194
+ return events.filter((event) => event.kind.startsWith("confirmation."));
195
+ }
170
196
  async function main() {
171
197
  const server = new McpServer({
172
198
  name: "doer-mobile",
@@ -294,6 +320,61 @@ async function main() {
294
320
  structuredContent: result,
295
321
  };
296
322
  });
323
+ server.registerTool("mobile_show_confirmation", {
324
+ description: "Show a high-priority mobile confirmation notification with only Yes/No actions. Responses are stored in the mobile event log as confirmation.response.",
325
+ inputSchema: {
326
+ deviceId: z.string().optional().describe("Mobile device id. Defaults to the first registered mobile agent."),
327
+ requestId: z.string().optional().describe("Caller-provided id for matching the eventual confirmation.response log. Generated by the server when omitted."),
328
+ title: z.string().min(1).describe("Confirmation title."),
329
+ text: z.string().min(1).describe("Confirmation body text."),
330
+ yesLabel: z.string().optional().describe("Label for the affirmative action. Defaults to Yes."),
331
+ noLabel: z.string().optional().describe("Label for the negative action. Defaults to No."),
332
+ notificationId: z.number().int().optional().describe("Optional Android notification id. Reusing an id updates the existing confirmation notification."),
333
+ },
334
+ }, async ({ deviceId, requestId, title, text, yesLabel, noLabel, notificationId }) => {
335
+ const result = await showMobileConfirmation({ deviceId, requestId, title, text, yesLabel, noLabel, notificationId });
336
+ return {
337
+ content: [{ type: "text", text: formatJson(result) }],
338
+ structuredContent: result,
339
+ };
340
+ });
341
+ server.registerTool("mobile_run_shell_command", {
342
+ description: "Run a shell command on an Android mobile agent using the app sandbox permissions. This is not root; command output is size-limited and timed out.",
343
+ inputSchema: {
344
+ deviceId: z.string().optional().describe("Mobile device id. Defaults to the first registered mobile agent."),
345
+ command: z.string().min(1).describe("Shell command passed to /system/bin/sh -c on the device."),
346
+ cwd: z.string().nullable().optional().describe("Optional working directory on the Android device. Defaults to the app process default directory."),
347
+ env: z.record(z.string(), z.string()).optional().describe("Optional environment variables for the command."),
348
+ timeoutMs: z.number().int().min(1000).max(60000).optional().describe("Command timeout in milliseconds. Defaults to 10000."),
349
+ maxOutputBytes: z.number().int().min(1024).max(65536).optional().describe("Maximum bytes to capture per stdout/stderr stream. Defaults to 16384."),
350
+ },
351
+ }, async ({ deviceId, command, cwd, env, timeoutMs, maxOutputBytes }) => {
352
+ const result = await runMobileCommand({ deviceId, command, cwd, env, timeoutMs, maxOutputBytes });
353
+ return {
354
+ content: [{ type: "text", text: formatJson(result) }],
355
+ structuredContent: result,
356
+ };
357
+ });
358
+ server.registerTool("mobile_search_confirmation_logs", {
359
+ description: "Return recent confirmation request/response/dismissed events stored in mobile logs.",
360
+ inputSchema: {
361
+ deviceId: z.string().optional().describe("Mobile device id. Defaults to the first registered mobile agent."),
362
+ limit: z.number().int().min(1).max(1000).optional().describe("How many recent events to scan."),
363
+ requestId: z.string().optional().describe("Optional confirmation request id to search for."),
364
+ },
365
+ }, async ({ deviceId, limit, requestId }) => {
366
+ const page = await searchMobileLogs({
367
+ deviceId,
368
+ kind: "confirmation.",
369
+ limit: limit ?? 200,
370
+ query: requestId?.trim() || undefined,
371
+ });
372
+ const events = extractConfirmations(page.events);
373
+ return {
374
+ content: [{ type: "text", text: formatJson({ events }) }],
375
+ structuredContent: { events },
376
+ };
377
+ });
297
378
  server.registerTool("mobile_search_notification_logs", {
298
379
  description: "Return recent notification events stored in mobile logs.",
299
380
  inputSchema: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doer-agent",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "description": "Reverse-polling agent runtime for doer",
5
5
  "type": "module",
6
6
  "main": "dist/agent.js",