-
Notifications
You must be signed in to change notification settings - Fork 0
feat: AAP host + Mobile Use app integration (launch/stop/skills end-to-end) #250
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
5256998
feat: AAP host + Mobile Use app integration (launch/stop/skills end-t…
zvadaadam 836b68d
fix: resolve AAP review findings + green CI
zvadaadam c26fe27
chore: address CodeRabbit nitpicks (rename, ts-pattern, type precision)
zvadaadam da5f477
fix: AAP registrar concurrency, query catch-up, and path anchoring
zvadaadam 9b8ee7b
fix: aap/lifecycle drains stderr on close, not exit
zvadaadam 7df9bc8
fix: lifecycle tests — race + timer leak after close-event switch
zvadaadam 58214fb
fix: revert lifecycle "close" event — Linux pipe-orphan hang
zvadaadam File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,161 @@ | ||
| // agent-server/agents/deus-tools/apps.ts | ||
| // | ||
| // AAP lifecycle tools surfaced in the Deus MCP server. Thin wrappers: each | ||
| // tool validates its args, calls `EventBroadcaster.requestXxx(...)` to hit | ||
| // the backend's apps.service (where real state + process management lives), | ||
| // and formats the result for the agent. | ||
| // | ||
| // The backend is the single writer for AAP state. These tools never touch | ||
| // the registrar directly — when a launch succeeds, the backend's mcp-bridge | ||
| // fires `aap/register-mcp` back to the agent-server, which the registrar | ||
| // handles separately. Two clean halves, one choke point per direction. | ||
|
|
||
| import { tool } from "@anthropic-ai/claude-agent-sdk"; | ||
| import type { SdkMcpToolDefinition } from "@anthropic-ai/claude-agent-sdk"; | ||
| import { z } from "zod"; | ||
| import { getErrorMessage } from "@shared/lib/errors"; | ||
| import { EventBroadcaster } from "../../event-broadcaster"; | ||
|
|
||
| // ---------------------------------------------------------------------------- | ||
| // Response helpers | ||
| // ---------------------------------------------------------------------------- | ||
|
|
||
| function textResult(text: string) { | ||
| return { content: [{ type: "text" as const, text }] }; | ||
| } | ||
|
|
||
| /** | ||
| * Wrap a tool handler with error catching. Returns error text instead of | ||
| * throwing — same pattern as browser/simulator tools. An exception escaping | ||
| * a tool handler would break the ongoing agent turn. | ||
| */ | ||
| function withErrorCatch<T>( | ||
| fn: (args: T) => Promise<{ content: Array<{ type: string; [k: string]: unknown }> }> | ||
| ) { | ||
| return async (args: T) => { | ||
| try { | ||
| return await fn(args); | ||
| } catch (err) { | ||
| return textResult(`AAP error: ${getErrorMessage(err)}`); | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| // ---------------------------------------------------------------------------- | ||
| // Factory | ||
| // ---------------------------------------------------------------------------- | ||
|
|
||
| export function createAppsTools(sessionId: string): SdkMcpToolDefinition<any>[] { | ||
| return [ | ||
| // -- ListApps ------------------------------------------------------------- | ||
| tool( | ||
| "list_apps", | ||
| `List installed Deus apps and which are currently running in YOUR workspace. | ||
|
|
||
| Returns a JSON object: | ||
| { apps: InstalledApp[], runningAppIds: string[] } | ||
| Each app has { id, name, description, version, icon?, bootstrap? }. | ||
| Use the app's \`id\` (e.g. "deus.mobile-use") as the argument to launch_app. | ||
|
|
||
| Running apps are auto-scoped to the agent's current session/workspace; | ||
| you don't need (and can't pass) a workspaceId.`, | ||
| {}, | ||
| withErrorCatch(async () => { | ||
| console.log(`[deusMCPServer] list_apps invoked for session ${sessionId}`); | ||
| const response = await EventBroadcaster.requestListApps({ sessionId }); | ||
| return textResult(JSON.stringify(response, null, 2)); | ||
| }) | ||
| ), | ||
|
|
||
| // -- LaunchApp ------------------------------------------------------------ | ||
| tool( | ||
| "launch_app", | ||
| `Launch an installed Deus app in YOUR current workspace. The backend | ||
| spawns the app's subprocess, waits for its ready probe, and (on success) | ||
| registers its MCP tools into THIS agent session. New tools appear as | ||
| \`mcp__{app_server_name}__*\` (e.g. \`mcp__deus_mobile_use__snapshot\` for | ||
| the mobile-use app) within a few seconds — they're immediately callable. | ||
|
|
||
| One instance per (appId, workspace): a duplicate launch returns the | ||
| existing runningAppId. The app's manifest \`bootstrap\` — a short help | ||
| string — is returned so you know how to use its tools. | ||
|
|
||
| Workspace is inferred from your session — do NOT pass a workspaceId.`, | ||
| { | ||
| appId: z.string().describe('App id (e.g. "deus.mobile-use"). Get from list_apps.'), | ||
| }, | ||
| withErrorCatch(async (args: { appId: string }) => { | ||
| console.log( | ||
| `[deusMCPServer] launch_app invoked for session ${sessionId} appId=${args.appId}` | ||
| ); | ||
| const response = await EventBroadcaster.requestLaunchApp({ | ||
| appId: args.appId, | ||
| sessionId, | ||
| }); | ||
|
|
||
| const lines = [ | ||
| `Launched ${args.appId}`, | ||
| ` runningAppId: ${response.runningAppId}`, | ||
| ` url: ${response.url}`, | ||
| ]; | ||
| if (response.bootstrap) { | ||
| lines.push("", `App bootstrap hint:`, response.bootstrap); | ||
| } | ||
| lines.push( | ||
| "", | ||
| `The app's MCP tools (mcp__{server}__*) will appear in your tool list shortly.` | ||
| ); | ||
| return textResult(lines.join("\n")); | ||
| }) | ||
| ), | ||
|
|
||
| // -- StopApp -------------------------------------------------------------- | ||
| tool( | ||
| "stop_app", | ||
| `Stop a running Deus app by its runningAppId. The backend sends SIGTERM, | ||
| waits for the stop timeout, then SIGKILLs if needed. The app's MCP tools | ||
| are automatically removed from your tool list.`, | ||
| { | ||
| runningAppId: z.string().describe("The runningAppId returned by launch_app."), | ||
| }, | ||
| withErrorCatch(async (args: { runningAppId: string }) => { | ||
| console.log( | ||
| `[deusMCPServer] stop_app invoked for session ${sessionId} runningAppId=${args.runningAppId}` | ||
| ); | ||
| const response = await EventBroadcaster.requestStopApp({ | ||
| runningAppId: args.runningAppId, | ||
| }); | ||
| return textResult( | ||
| response.success | ||
| ? `Stopped runningAppId ${args.runningAppId}.` | ||
| : `Failed to stop runningAppId ${args.runningAppId}.` | ||
| ); | ||
| }) | ||
| ), | ||
|
|
||
| // -- ReadAppSkill --------------------------------------------------------- | ||
| tool( | ||
| "read_app_skill", | ||
| `Read the detailed usage docs ("skill") an installed Deus app ships with. | ||
| The \`launch_app\` tool deliberately keeps its response lean — call this | ||
| only when you need deeper guidance on how to drive the app's MCP tools | ||
| (typical triggers: first use of an app in a session, or unfamiliar tool | ||
| names showing up after a launch). Content is markdown; may include | ||
| command examples, workflow patterns, and JSON shape references. | ||
|
|
||
| Returns an empty string if the app declares no skills.`, | ||
| { | ||
| appId: z.string().describe('App id (e.g. "deus.mobile-use"). Get from list_apps.'), | ||
| }, | ||
| withErrorCatch(async (args: { appId: string }) => { | ||
| console.log( | ||
| `[deusMCPServer] read_app_skill invoked for session ${sessionId} appId=${args.appId}` | ||
| ); | ||
| const response = await EventBroadcaster.requestReadAppSkill({ appId: args.appId }); | ||
| return textResult( | ||
| response.content.length > 0 ? response.content : `No skills declared for ${args.appId}.` | ||
| ); | ||
| }) | ||
| ), | ||
| ]; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.