@agent-native/core 0.49.24 → 0.49.26
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/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +8 -1
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/cli/recap.d.ts.map +1 -1
- package/dist/cli/recap.js +43 -11
- package/dist/cli/recap.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +2 -1
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/blocks/library/AnnotatedCodeBlock.js +7 -7
- package/dist/client/blocks/library/AnnotatedCodeBlock.js.map +1 -1
- package/dist/client/blocks/library/DiffBlock.js +3 -3
- package/dist/client/blocks/library/DiffBlock.js.map +1 -1
- package/dist/client/blocks/library/annotation-rail.d.ts +4 -3
- package/dist/client/blocks/library/annotation-rail.d.ts.map +1 -1
- package/dist/client/blocks/library/annotation-rail.js +16 -9
- package/dist/client/blocks/library/annotation-rail.js.map +1 -1
- package/dist/client/blocks/types.d.ts +2 -2
- package/dist/client/blocks/types.js.map +1 -1
- package/dist/coding-tools/run-code.d.ts.map +1 -1
- package/dist/coding-tools/run-code.js +198 -15
- package/dist/coding-tools/run-code.js.map +1 -1
- package/dist/extensions/fetch-tool.js +1 -1
- package/dist/extensions/fetch-tool.js.map +1 -1
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +1 -0
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/mcp/builtin-tools.d.ts +8 -4
- package/dist/mcp/builtin-tools.d.ts.map +1 -1
- package/dist/mcp/builtin-tools.js +247 -13
- package/dist/mcp/builtin-tools.js.map +1 -1
- package/dist/provider-api/actions/query-staged-dataset.d.ts.map +1 -1
- package/dist/provider-api/actions/query-staged-dataset.js +1 -0
- package/dist/provider-api/actions/query-staged-dataset.js.map +1 -1
- package/dist/provider-api/index.d.ts +15 -4
- package/dist/provider-api/index.d.ts.map +1 -1
- package/dist/provider-api/index.js +191 -43
- package/dist/provider-api/index.js.map +1 -1
- package/dist/provider-api/staged-datasets-store.d.ts.map +1 -1
- package/dist/provider-api/staged-datasets-store.js +29 -6
- package/dist/provider-api/staged-datasets-store.js.map +1 -1
- package/dist/provider-api/staging.d.ts +6 -1
- package/dist/provider-api/staging.d.ts.map +1 -1
- package/dist/provider-api/staging.js +35 -6
- package/dist/provider-api/staging.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +157 -80
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/prompts/shared-rules.d.ts +1 -1
- package/dist/server/prompts/shared-rules.d.ts.map +1 -1
- package/dist/server/prompts/shared-rules.js +5 -7
- package/dist/server/prompts/shared-rules.js.map +1 -1
- package/dist/server/schema-prompt.js +1 -1
- package/dist/server/schema-prompt.js.map +1 -1
- package/dist/templates/default/.agents/skills/actions/SKILL.md +37 -9
- package/dist/templates/default/.agents/skills/adding-a-feature/SKILL.md +7 -1
- package/dist/templates/workspace-core/.agents/skills/actions/SKILL.md +37 -9
- package/dist/templates/workspace-core/.agents/skills/adding-a-feature/SKILL.md +7 -1
- package/package.json +1 -1
- package/src/templates/default/.agents/skills/actions/SKILL.md +37 -9
- package/src/templates/default/.agents/skills/adding-a-feature/SKILL.md +7 -1
- package/src/templates/workspace-core/.agents/skills/actions/SKILL.md +37 -9
- package/src/templates/workspace-core/.agents/skills/adding-a-feature/SKILL.md +7 -1
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
* | `list_apps` | none | `{ apps: [{ id, url, running }] }` |
|
|
15
15
|
* | `open_app` | none | `{ url }` (+ deep-link `link`) |
|
|
16
16
|
* | `create_embed_session`| ticket mint | `{ startUrl }` for MCP App iframes |
|
|
17
|
-
* | `ask_app` | agent loop | `{ app, routedVia, response }`
|
|
17
|
+
* | `ask_app` | agent loop | `{ app, routedVia, response }` or task |
|
|
18
|
+
* | `ask_app_status` | none | poll a durable `ask_app` task |
|
|
18
19
|
* | `create_workspace_app`| scaffolds | `{ name, url, port, deepLink }` (+ link) |
|
|
19
20
|
*
|
|
20
21
|
* `open_app` / `create_workspace_app` return an **absolute** URL on the
|
|
@@ -31,12 +32,15 @@
|
|
|
31
32
|
*/
|
|
32
33
|
import type { ActionEntry } from "../agent/production-agent.js";
|
|
33
34
|
import type { MCPConfig } from "./build-server.js";
|
|
35
|
+
type AskAppRequestMeta = {
|
|
36
|
+
origin?: string;
|
|
37
|
+
basePath?: string;
|
|
38
|
+
};
|
|
34
39
|
/**
|
|
35
40
|
* Build the generic cross-app builtin tool registry. Called by
|
|
36
41
|
* `createMCPServerForRequest`; the result is merged UNDER the config's
|
|
37
42
|
* actions so template actions of the same name win.
|
|
38
43
|
*/
|
|
39
|
-
export declare function getBuiltinCrossAppTools(config: MCPConfig, requestMeta?:
|
|
40
|
-
|
|
41
|
-
}): Record<string, ActionEntry>;
|
|
44
|
+
export declare function getBuiltinCrossAppTools(config: MCPConfig, requestMeta?: AskAppRequestMeta): Record<string, ActionEntry>;
|
|
45
|
+
export {};
|
|
42
46
|
//# sourceMappingURL=builtin-tools.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builtin-tools.d.ts","sourceRoot":"","sources":["../../src/mcp/builtin-tools.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"builtin-tools.d.ts","sourceRoot":"","sources":["../../src/mcp/builtin-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAIhE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAuDnD,KAAK,iBAAiB,GAAG;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AA8iChE;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,SAAS,EACjB,WAAW,CAAC,EAAE,iBAAiB,GAC9B,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAU7B"}
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
* | `list_apps` | none | `{ apps: [{ id, url, running }] }` |
|
|
15
15
|
* | `open_app` | none | `{ url }` (+ deep-link `link`) |
|
|
16
16
|
* | `create_embed_session`| ticket mint | `{ startUrl }` for MCP App iframes |
|
|
17
|
-
* | `ask_app` | agent loop | `{ app, routedVia, response }`
|
|
17
|
+
* | `ask_app` | agent loop | `{ app, routedVia, response }` or task |
|
|
18
|
+
* | `ask_app_status` | none | poll a durable `ask_app` task |
|
|
18
19
|
* | `create_workspace_app`| scaffolds | `{ name, url, port, deepLink }` (+ link) |
|
|
19
20
|
*
|
|
20
21
|
* `open_app` / `create_workspace_app` return an **absolute** URL on the
|
|
@@ -61,6 +62,15 @@ function currentAppId(config) {
|
|
|
61
62
|
return (config.appId || config.name || "app").toLowerCase();
|
|
62
63
|
}
|
|
63
64
|
const CONTROL_CHARS = new RegExp("[\\u0000-\\u001f\\u007f]");
|
|
65
|
+
const ASK_APP_DEFAULT_INLINE_WAIT_MS = 20_000;
|
|
66
|
+
const ASK_APP_MAX_INLINE_WAIT_MS = 25_000;
|
|
67
|
+
const ASK_APP_POLL_INTERVAL_MS = 1_500;
|
|
68
|
+
const ASK_APP_A2A_REQUEST_TIMEOUT_MS = 10_000;
|
|
69
|
+
const ASK_APP_TERMINAL_STATES = new Set([
|
|
70
|
+
"completed",
|
|
71
|
+
"failed",
|
|
72
|
+
"canceled",
|
|
73
|
+
]);
|
|
64
74
|
function safeAppPath(raw) {
|
|
65
75
|
if (typeof raw !== "string" || !raw.trim())
|
|
66
76
|
return null;
|
|
@@ -106,6 +116,140 @@ function withMcpChatBridgeParam(path) {
|
|
|
106
116
|
return path;
|
|
107
117
|
}
|
|
108
118
|
}
|
|
119
|
+
function agentNativeA2AEndpoint(urlOrOrigin) {
|
|
120
|
+
const value = urlOrOrigin.replace(/\/+$/, "");
|
|
121
|
+
try {
|
|
122
|
+
const parsed = new URL(value);
|
|
123
|
+
const pathname = parsed.pathname.replace(/\/+$/, "");
|
|
124
|
+
if (pathname.endsWith("/_agent-native/a2a") || pathname.endsWith("/a2a")) {
|
|
125
|
+
return value;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
// Fall through and append the conventional Agent Native endpoint.
|
|
130
|
+
}
|
|
131
|
+
return `${value}/_agent-native/a2a`;
|
|
132
|
+
}
|
|
133
|
+
function selfA2AEndpointUrl(requestMeta) {
|
|
134
|
+
const origin = requestMeta?.origin?.replace(/\/+$/, "");
|
|
135
|
+
if (!origin)
|
|
136
|
+
return null;
|
|
137
|
+
const basePath = requestMeta?.basePath || getConfiguredAppBasePath();
|
|
138
|
+
return agentNativeA2AEndpoint(`${origin}${basePath}`);
|
|
139
|
+
}
|
|
140
|
+
function boundedAskAppWaitMs(raw) {
|
|
141
|
+
if (raw == null || raw === "")
|
|
142
|
+
return ASK_APP_DEFAULT_INLINE_WAIT_MS;
|
|
143
|
+
const parsed = Number(raw);
|
|
144
|
+
if (!Number.isFinite(parsed))
|
|
145
|
+
return ASK_APP_DEFAULT_INLINE_WAIT_MS;
|
|
146
|
+
return Math.max(0, Math.min(ASK_APP_MAX_INLINE_WAIT_MS, Math.trunc(parsed)));
|
|
147
|
+
}
|
|
148
|
+
function isExplicitAsyncAsk(raw) {
|
|
149
|
+
return raw === true || raw === "true" || raw === 1 || raw === "1";
|
|
150
|
+
}
|
|
151
|
+
function taskState(task) {
|
|
152
|
+
return String(task.status?.state ?? "unknown");
|
|
153
|
+
}
|
|
154
|
+
function isTerminalTask(task) {
|
|
155
|
+
return ASK_APP_TERMINAL_STATES.has(taskState(task));
|
|
156
|
+
}
|
|
157
|
+
function taskText(task) {
|
|
158
|
+
return (task.status.message?.parts
|
|
159
|
+
?.filter((part) => part.type === "text")
|
|
160
|
+
.map((part) => part.text)
|
|
161
|
+
.join("\n")
|
|
162
|
+
.trim() ?? "");
|
|
163
|
+
}
|
|
164
|
+
function askAppTaskResult(route, task) {
|
|
165
|
+
const status = taskState(task);
|
|
166
|
+
const response = taskText(task);
|
|
167
|
+
const base = {
|
|
168
|
+
app: route.app,
|
|
169
|
+
routedVia: route.routedVia,
|
|
170
|
+
taskId: task.id,
|
|
171
|
+
status,
|
|
172
|
+
...(route.note ? { note: route.note } : {}),
|
|
173
|
+
};
|
|
174
|
+
if (status === "completed") {
|
|
175
|
+
return {
|
|
176
|
+
...base,
|
|
177
|
+
response: response || "(no response)",
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
if (status === "failed" || status === "canceled") {
|
|
181
|
+
return {
|
|
182
|
+
...base,
|
|
183
|
+
...(response ? { response } : {}),
|
|
184
|
+
error: response || `ask_app task ${status}.`,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
...base,
|
|
189
|
+
poll: {
|
|
190
|
+
tool: "ask_app_status",
|
|
191
|
+
arguments: { app: route.app, taskId: task.id },
|
|
192
|
+
},
|
|
193
|
+
message: `ask_app is still ${status}. Call ask_app_status with ` +
|
|
194
|
+
`taskId "${task.id}" to retrieve the final response.`,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
async function createA2AClientForAskApp(origin) {
|
|
198
|
+
const { A2AClient } = await import("../a2a/client.js");
|
|
199
|
+
const { resolveA2ACallerAuth } = await import("../a2a/caller-auth.js");
|
|
200
|
+
const auth = await resolveA2ACallerAuth();
|
|
201
|
+
const metadata = {};
|
|
202
|
+
if (auth.userEmail)
|
|
203
|
+
metadata.userEmail = auth.userEmail;
|
|
204
|
+
if (auth.orgDomain)
|
|
205
|
+
metadata.orgDomain = auth.orgDomain;
|
|
206
|
+
return {
|
|
207
|
+
client: new A2AClient(origin, auth.apiKey, {
|
|
208
|
+
requestTimeoutMs: ASK_APP_A2A_REQUEST_TIMEOUT_MS,
|
|
209
|
+
}),
|
|
210
|
+
metadata,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
async function waitForA2ATask(client, initialTask, maxWaitMs) {
|
|
214
|
+
if (maxWaitMs <= 0 || isTerminalTask(initialTask))
|
|
215
|
+
return initialTask;
|
|
216
|
+
const deadline = Date.now() + maxWaitMs;
|
|
217
|
+
let current = initialTask;
|
|
218
|
+
while (!isTerminalTask(current)) {
|
|
219
|
+
const remaining = deadline - Date.now();
|
|
220
|
+
if (remaining <= 0)
|
|
221
|
+
return current;
|
|
222
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(ASK_APP_POLL_INTERVAL_MS, remaining)));
|
|
223
|
+
try {
|
|
224
|
+
current = await client.getTask(initialTask.id);
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
// Transient status fetch failures should not turn a successfully
|
|
228
|
+
// submitted durable task into a failed MCP call.
|
|
229
|
+
if (Date.now() >= deadline)
|
|
230
|
+
return current;
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return current;
|
|
235
|
+
}
|
|
236
|
+
async function submitAskAppA2ATask(route, message, maxWaitMs) {
|
|
237
|
+
const { client, metadata } = await createA2AClientForAskApp(route.origin);
|
|
238
|
+
const task = await client.send({
|
|
239
|
+
role: "user",
|
|
240
|
+
parts: [{ type: "text", text: message }],
|
|
241
|
+
}, {
|
|
242
|
+
async: true,
|
|
243
|
+
metadata,
|
|
244
|
+
});
|
|
245
|
+
const finalOrRunning = await waitForA2ATask(client, task, maxWaitMs);
|
|
246
|
+
return askAppTaskResult(route, finalOrRunning);
|
|
247
|
+
}
|
|
248
|
+
async function fetchAskAppA2ATask(route, taskId) {
|
|
249
|
+
const { client } = await createA2AClientForAskApp(route.origin);
|
|
250
|
+
const task = await client.getTask(taskId);
|
|
251
|
+
return askAppTaskResult(route, task);
|
|
252
|
+
}
|
|
109
253
|
/**
|
|
110
254
|
* Resolve the absolute origin of a *target* workspace app (e.g.
|
|
111
255
|
* `http://127.0.0.1:8101`) so cross-app deep links / A2A calls point at the
|
|
@@ -457,7 +601,10 @@ function createEmbedSessionTool(requestMeta) {
|
|
|
457
601
|
* Throws on failure so the caller can be honest — it never falls back to this
|
|
458
602
|
* app's agent and pretends it was the target.
|
|
459
603
|
*/
|
|
460
|
-
async function routeAskOverA2A(origin, id, message) {
|
|
604
|
+
async function routeAskOverA2A(origin, id, message, options) {
|
|
605
|
+
if (options?.durable) {
|
|
606
|
+
return submitAskAppA2ATask({ app: id, origin: agentNativeA2AEndpoint(origin), routedVia: "a2a" }, message, options.maxWaitMs ?? ASK_APP_DEFAULT_INLINE_WAIT_MS);
|
|
607
|
+
}
|
|
461
608
|
const { callAgent } = await import("../a2a/client.js");
|
|
462
609
|
const { resolveA2ACallerAuth } = await import("../a2a/caller-auth.js");
|
|
463
610
|
// The MCP handler runs inside `runWithRequestContext`, so this is the
|
|
@@ -475,17 +622,48 @@ async function routeAskOverA2A(origin, id, message) {
|
|
|
475
622
|
});
|
|
476
623
|
return { app: id, routedVia: "a2a", response };
|
|
477
624
|
}
|
|
625
|
+
async function resolveAskAppStatusRoute(config, requestedApp, requestMeta) {
|
|
626
|
+
const selfId = currentAppId(config);
|
|
627
|
+
const normalized = requestedApp.trim().toLowerCase();
|
|
628
|
+
const selfEndpointUrl = selfA2AEndpointUrl(requestMeta);
|
|
629
|
+
if (!normalized || normalized === selfId) {
|
|
630
|
+
if (!selfEndpointUrl) {
|
|
631
|
+
throw new Error("ask_app_status requires a running app origin for local tasks.");
|
|
632
|
+
}
|
|
633
|
+
return { app: selfId, origin: selfEndpointUrl, routedVia: "local" };
|
|
634
|
+
}
|
|
635
|
+
const targetApp = await resolveTargetAppOrigin(config, requestedApp);
|
|
636
|
+
if (targetApp) {
|
|
637
|
+
return {
|
|
638
|
+
app: targetApp.id,
|
|
639
|
+
origin: agentNativeA2AEndpoint(targetApp.origin),
|
|
640
|
+
routedVia: "a2a",
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
const orgApps = await fetchOrgApps({ selfId }).catch(() => []);
|
|
644
|
+
const dirMatch = orgApps.find((a) => a.id === normalized);
|
|
645
|
+
if (dirMatch) {
|
|
646
|
+
return {
|
|
647
|
+
app: dirMatch.id,
|
|
648
|
+
origin: agentNativeA2AEndpoint(dirMatch.a2aUrl),
|
|
649
|
+
routedVia: "a2a",
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
throw new Error(`No reachable ask_app task route for app "${requestedApp}".`);
|
|
653
|
+
}
|
|
478
654
|
// ---------------------------------------------------------------------------
|
|
479
655
|
// ask_app
|
|
480
656
|
// ---------------------------------------------------------------------------
|
|
481
|
-
function askAppTool(config) {
|
|
657
|
+
function askAppTool(config, requestMeta) {
|
|
482
658
|
return {
|
|
483
659
|
tool: tool("Send a natural-language message to an app's AI agent and get its " +
|
|
484
660
|
"response. Use for complex, multi-step tasks needing the agent's " +
|
|
485
661
|
"reasoning and full app context. In a single-app project the 'app' " +
|
|
486
662
|
"param is optional (defaults to this app). When 'app' names a " +
|
|
487
663
|
"different workspace app it is routed there over A2A; the result's " +
|
|
488
|
-
"'routedVia' field reports whether it ran cross-app or locally."
|
|
664
|
+
"'routedVia' field reports whether it ran cross-app or locally. " +
|
|
665
|
+
"On hosted MCP, long tasks may return a durable taskId instead of a " +
|
|
666
|
+
"final response; call ask_app_status with that taskId until completed.", {
|
|
489
667
|
app: {
|
|
490
668
|
type: "string",
|
|
491
669
|
description: "App id to route to (optional in a single-app project)",
|
|
@@ -494,6 +672,14 @@ function askAppTool(config) {
|
|
|
494
672
|
type: "string",
|
|
495
673
|
description: "The message to send to the app's agent",
|
|
496
674
|
},
|
|
675
|
+
async: {
|
|
676
|
+
type: "boolean",
|
|
677
|
+
description: "When true, start a durable task and return immediately with a taskId.",
|
|
678
|
+
},
|
|
679
|
+
maxWaitMs: {
|
|
680
|
+
type: "number",
|
|
681
|
+
description: "Maximum time to wait inline before returning a taskId. Hosted MCP clamps this to 25000ms.",
|
|
682
|
+
},
|
|
497
683
|
}, ["message"]),
|
|
498
684
|
run: async (args) => {
|
|
499
685
|
const message = String(args.message ?? "").trim();
|
|
@@ -501,6 +687,10 @@ function askAppTool(config) {
|
|
|
501
687
|
throw new Error("ask_app requires a 'message'.");
|
|
502
688
|
const requestedApp = String(args.app ?? "").trim();
|
|
503
689
|
const selfId = currentAppId(config);
|
|
690
|
+
const useDurableA2A = Boolean(requestMeta?.origin);
|
|
691
|
+
const maxWaitMs = isExplicitAsyncAsk(args.async)
|
|
692
|
+
? 0
|
|
693
|
+
: boundedAskAppWaitMs(args.maxWaitMs);
|
|
504
694
|
// Cross-app: the caller named a *different* workspace app. Route the
|
|
505
695
|
// message to THAT app's agent over A2A (its `/_agent-native/a2a`
|
|
506
696
|
// endpoint runs the real agent loop with JWT identity) rather than
|
|
@@ -508,7 +698,10 @@ function askAppTool(config) {
|
|
|
508
698
|
const targetApp = await resolveTargetAppOrigin(config, requestedApp);
|
|
509
699
|
if (targetApp) {
|
|
510
700
|
try {
|
|
511
|
-
return await routeAskOverA2A(targetApp.origin, targetApp.id, message
|
|
701
|
+
return await routeAskOverA2A(targetApp.origin, targetApp.id, message, {
|
|
702
|
+
durable: useDurableA2A,
|
|
703
|
+
maxWaitMs,
|
|
704
|
+
});
|
|
512
705
|
}
|
|
513
706
|
catch (err) {
|
|
514
707
|
// Be honest: routing was attempted and failed — do NOT fall back to
|
|
@@ -527,7 +720,10 @@ function askAppTool(config) {
|
|
|
527
720
|
const dirMatch = orgApps.find((a) => a.id === requestedApp.toLowerCase());
|
|
528
721
|
if (dirMatch) {
|
|
529
722
|
try {
|
|
530
|
-
return await routeAskOverA2A(dirMatch.a2aUrl, dirMatch.id, message
|
|
723
|
+
return await routeAskOverA2A(dirMatch.a2aUrl, dirMatch.id, message, {
|
|
724
|
+
durable: useDurableA2A,
|
|
725
|
+
maxWaitMs,
|
|
726
|
+
});
|
|
531
727
|
}
|
|
532
728
|
catch (err) {
|
|
533
729
|
throw new Error(`Failed to route ask_app to "${dirMatch.id}" via A2A ` +
|
|
@@ -544,21 +740,58 @@ function askAppTool(config) {
|
|
|
544
740
|
// If the caller named an app we couldn't route to (unknown id, or no
|
|
545
741
|
// workspace), say so honestly instead of claiming we reached it.
|
|
546
742
|
const unresolved = !!requestedApp && requestedApp.toLowerCase() !== selfId;
|
|
743
|
+
const note = unresolved
|
|
744
|
+
? `Requested app "${requestedApp}" is not a reachable workspace ` +
|
|
745
|
+
`app; answered with this app ("${selfId}") instead.`
|
|
746
|
+
: undefined;
|
|
747
|
+
// Hosted MCP cannot safely keep a JSON request/response open for a full
|
|
748
|
+
// agent loop: serverless gateways can return an inactivity 504 before
|
|
749
|
+
// the result body exists. When we know the running app origin, submit the
|
|
750
|
+
// local ask through the app's durable A2A task path and only wait a
|
|
751
|
+
// short bounded window for fast completions.
|
|
752
|
+
const localA2AEndpointUrl = selfA2AEndpointUrl(requestMeta);
|
|
753
|
+
if (localA2AEndpointUrl) {
|
|
754
|
+
return submitAskAppA2ATask({
|
|
755
|
+
app: selfId,
|
|
756
|
+
origin: localA2AEndpointUrl,
|
|
757
|
+
routedVia: "local",
|
|
758
|
+
...(note ? { note } : {}),
|
|
759
|
+
}, message, maxWaitMs);
|
|
760
|
+
}
|
|
547
761
|
const response = await config.askAgent(message);
|
|
548
762
|
return {
|
|
549
763
|
app: selfId,
|
|
550
764
|
routedVia: "local",
|
|
551
|
-
...(
|
|
552
|
-
? {
|
|
553
|
-
note: `Requested app "${requestedApp}" is not a reachable workspace ` +
|
|
554
|
-
`app; answered with this app ("${selfId}") instead.`,
|
|
555
|
-
}
|
|
556
|
-
: {}),
|
|
765
|
+
...(note ? { note } : {}),
|
|
557
766
|
response,
|
|
558
767
|
};
|
|
559
768
|
},
|
|
560
769
|
};
|
|
561
770
|
}
|
|
771
|
+
function askAppStatusTool(config, requestMeta) {
|
|
772
|
+
return {
|
|
773
|
+
tool: tool("Poll a durable ask_app task and return its current status or final response.", {
|
|
774
|
+
app: {
|
|
775
|
+
type: "string",
|
|
776
|
+
description: "App id returned by ask_app. Optional for same-app local tasks.",
|
|
777
|
+
},
|
|
778
|
+
taskId: {
|
|
779
|
+
type: "string",
|
|
780
|
+
description: "The durable task id returned by ask_app.",
|
|
781
|
+
},
|
|
782
|
+
}, ["taskId"]),
|
|
783
|
+
readOnly: true,
|
|
784
|
+
parallelSafe: true,
|
|
785
|
+
run: async (args) => {
|
|
786
|
+
const taskId = String(args.taskId ?? "").trim();
|
|
787
|
+
if (!taskId)
|
|
788
|
+
throw new Error("ask_app_status requires 'taskId'.");
|
|
789
|
+
const requestedApp = String(args.app ?? "").trim();
|
|
790
|
+
const route = await resolveAskAppStatusRoute(config, requestedApp, requestMeta);
|
|
791
|
+
return fetchAskAppA2ATask(route, taskId);
|
|
792
|
+
},
|
|
793
|
+
};
|
|
794
|
+
}
|
|
562
795
|
// ---------------------------------------------------------------------------
|
|
563
796
|
// list_templates
|
|
564
797
|
// ---------------------------------------------------------------------------
|
|
@@ -697,7 +930,8 @@ export function getBuiltinCrossAppTools(config, requestMeta) {
|
|
|
697
930
|
list_apps: listAppsTool(config, requestMeta),
|
|
698
931
|
open_app: openAppTool(config, requestMeta),
|
|
699
932
|
create_embed_session: createEmbedSessionTool(requestMeta),
|
|
700
|
-
ask_app: askAppTool(config),
|
|
933
|
+
ask_app: askAppTool(config, requestMeta),
|
|
934
|
+
ask_app_status: askAppStatusTool(config, requestMeta),
|
|
701
935
|
create_workspace_app: createWorkspaceAppTool(),
|
|
702
936
|
list_templates: listTemplatesTool(),
|
|
703
937
|
};
|