apcore-mcp 0.14.0 → 0.16.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.
- package/README.md +82 -1
- package/dist/adapters/approval.d.ts +21 -0
- package/dist/adapters/approval.d.ts.map +1 -1
- package/dist/adapters/approval.js +49 -0
- package/dist/adapters/approval.js.map +1 -1
- package/dist/adapters/errors.d.ts +20 -0
- package/dist/adapters/errors.d.ts.map +1 -1
- package/dist/adapters/errors.js +61 -41
- package/dist/adapters/errors.js.map +1 -1
- package/dist/adapters/id-normalizer.d.ts +6 -5
- package/dist/adapters/id-normalizer.d.ts.map +1 -1
- package/dist/adapters/id-normalizer.js +9 -12
- package/dist/adapters/id-normalizer.js.map +1 -1
- package/dist/adapters/schema.d.ts +11 -0
- package/dist/adapters/schema.d.ts.map +1 -1
- package/dist/adapters/schema.js +43 -1
- package/dist/adapters/schema.js.map +1 -1
- package/dist/apcore-mcp.d.ts +17 -1
- package/dist/apcore-mcp.d.ts.map +1 -1
- package/dist/apcore-mcp.js +42 -4
- package/dist/apcore-mcp.js.map +1 -1
- package/dist/approval-store.d.ts +64 -0
- package/dist/approval-store.d.ts.map +1 -0
- package/dist/approval-store.js +139 -0
- package/dist/approval-store.js.map +1 -0
- package/dist/auth/index.d.ts +7 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +7 -0
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/jwt.d.ts.map +1 -1
- package/dist/auth/jwt.js +4 -1
- package/dist/auth/jwt.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +10 -1
- package/dist/cli.js.map +1 -1
- package/dist/constants.d.ts +37 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +43 -0
- package/dist/constants.js.map +1 -0
- package/dist/converters/openai.d.ts +12 -0
- package/dist/converters/openai.d.ts.map +1 -1
- package/dist/converters/openai.js +28 -3
- package/dist/converters/openai.js.map +1 -1
- package/dist/index.d.ts +12 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -4
- package/dist/index.js.map +1 -1
- package/dist/markdown.d.ts +54 -0
- package/dist/markdown.d.ts.map +1 -0
- package/dist/markdown.js +125 -0
- package/dist/markdown.js.map +1 -0
- package/dist/server/approval-bridge.d.ts +27 -0
- package/dist/server/approval-bridge.d.ts.map +1 -0
- package/dist/server/approval-bridge.js +77 -0
- package/dist/server/approval-bridge.js.map +1 -0
- package/dist/server/async-task-bridge.d.ts +39 -1
- package/dist/server/async-task-bridge.d.ts.map +1 -1
- package/dist/server/async-task-bridge.js +195 -9
- package/dist/server/async-task-bridge.js.map +1 -1
- package/dist/server/context.d.ts +8 -0
- package/dist/server/context.d.ts.map +1 -1
- package/dist/server/context.js +15 -0
- package/dist/server/context.js.map +1 -1
- package/dist/server/factory.d.ts +34 -1
- package/dist/server/factory.d.ts.map +1 -1
- package/dist/server/factory.js +69 -3
- package/dist/server/factory.js.map +1 -1
- package/dist/server/router.d.ts +29 -6
- package/dist/server/router.d.ts.map +1 -1
- package/dist/server/router.js +75 -25
- package/dist/server/router.js.map +1 -1
- package/dist/server/transport.d.ts +8 -1
- package/dist/server/transport.d.ts.map +1 -1
- package/dist/server/transport.js +15 -2
- package/dist/server/transport.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -1
- package/package.json +5 -8
- package/dist/explorer/index.d.ts +0 -2
- package/dist/explorer/index.d.ts.map +0 -1
- package/dist/explorer/index.js +0 -2
- package/dist/explorer/index.js.map +0 -1
- package/dist/inspector/index.d.ts +0 -2
- package/dist/inspector/index.d.ts.map +0 -1
- package/dist/inspector/index.js +0 -2
- package/dist/inspector/index.js.map +0 -1
- package/dist/server/index.d.ts +0 -11
- package/dist/server/index.d.ts.map +0 -1
- package/dist/server/index.js +0 -8
- package/dist/server/index.js.map +0 -1
package/dist/markdown.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown rendering for apcore modules via apcore-toolkit.
|
|
3
|
+
*
|
|
4
|
+
* LLMs read MCP/OpenAI tool `description` strings as their primary
|
|
5
|
+
* signal for tool selection — the richer the description, the better
|
|
6
|
+
* the agent picks the right tool. apcore-toolkit's
|
|
7
|
+
* `formatModule({ style: "markdown" })` emits a canonical, cross-SDK
|
|
8
|
+
* byte-equivalent rendering with title, description, parameters list,
|
|
9
|
+
* returns list, behavior table, tags, and examples.
|
|
10
|
+
*
|
|
11
|
+
* This module bridges apcore's `ModuleDescriptor` (the runtime type
|
|
12
|
+
* flowing through apcore-mcp) to apcore-toolkit's `ScannedModule`
|
|
13
|
+
* (the input format `formatModule` expects), then delegates.
|
|
14
|
+
*
|
|
15
|
+
* apcore-toolkit is an OPTIONAL peer dependency in package.json (it
|
|
16
|
+
* appears under `optionalDependencies`). Callers must check
|
|
17
|
+
* {@link isMarkdownAvailable} before invoking
|
|
18
|
+
* {@link renderModuleMarkdown}.
|
|
19
|
+
*/
|
|
20
|
+
let _toolkit;
|
|
21
|
+
let _toolkitLoaded = false;
|
|
22
|
+
async function loadToolkit() {
|
|
23
|
+
if (_toolkitLoaded)
|
|
24
|
+
return _toolkit ?? null;
|
|
25
|
+
try {
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
27
|
+
const mod = (await import("apcore-toolkit"));
|
|
28
|
+
const formatModule = mod.formatModule ?? mod.default?.formatModule;
|
|
29
|
+
if (typeof formatModule === "function") {
|
|
30
|
+
_toolkit = { formatModule };
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
_toolkit = null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
_toolkit = null;
|
|
38
|
+
}
|
|
39
|
+
_toolkitLoaded = true;
|
|
40
|
+
return _toolkit ?? null;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Synchronous availability check using a side-effect-free flag set by
|
|
44
|
+
* {@link primeMarkdownToolkit}. Returns false until the toolkit has been
|
|
45
|
+
* primed; callers that need to check before the first async render should
|
|
46
|
+
* `await primeMarkdownToolkit()` at startup.
|
|
47
|
+
*/
|
|
48
|
+
export function isMarkdownAvailable() {
|
|
49
|
+
return _toolkitLoaded && _toolkit !== null;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Eagerly load apcore-toolkit so subsequent {@link isMarkdownAvailable}
|
|
53
|
+
* checks return synchronously. Safe to call multiple times — the import
|
|
54
|
+
* resolves to the same cached module.
|
|
55
|
+
*/
|
|
56
|
+
export async function primeMarkdownToolkit() {
|
|
57
|
+
await loadToolkit();
|
|
58
|
+
return isMarkdownAvailable();
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Adapt an apcore `ModuleDescriptor` to a toolkit `ScannedModule`-shaped
|
|
62
|
+
* object. The two types are near-supersets — overlapping fields are
|
|
63
|
+
* copied verbatim, toolkit-only fields (`target`, `documentation`,
|
|
64
|
+
* `suggestedAlias`, `warnings`) get sensible defaults.
|
|
65
|
+
*
|
|
66
|
+
* Note: apcore-mcp's `ModuleDescriptor` uses snake_case
|
|
67
|
+
* (`module_id`, `input_schema`); apcore-toolkit's `ScannedModule` uses
|
|
68
|
+
* camelCase (`moduleId`, `inputSchema`). We rename here.
|
|
69
|
+
*/
|
|
70
|
+
function descriptorToScannedModule(descriptor) {
|
|
71
|
+
// The TS `ModuleDescriptor` type doesn't declare a `display` field
|
|
72
|
+
// (apcore-mcp-typescript predates apcore 0.19.0's display overlay)
|
|
73
|
+
// but real instances coming from apcore-js may carry one — read it
|
|
74
|
+
// through an index lookup so we don't lose the overlay when present.
|
|
75
|
+
const display = descriptor.display ?? null;
|
|
76
|
+
return {
|
|
77
|
+
moduleId: descriptor.moduleId,
|
|
78
|
+
description: descriptor.description ?? "",
|
|
79
|
+
inputSchema: descriptor.inputSchema ?? {},
|
|
80
|
+
outputSchema: descriptor.outputSchema ?? {},
|
|
81
|
+
tags: [...(descriptor.tags ?? [])],
|
|
82
|
+
target: "",
|
|
83
|
+
version: descriptor.version ?? "1.0.0",
|
|
84
|
+
annotations: descriptor.annotations ?? null,
|
|
85
|
+
documentation: descriptor.documentation ?? null,
|
|
86
|
+
suggestedAlias: null,
|
|
87
|
+
examples: [...(descriptor.examples ?? [])],
|
|
88
|
+
metadata: { ...(descriptor.metadata ?? {}) },
|
|
89
|
+
display,
|
|
90
|
+
warnings: [],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Render a `ModuleDescriptor` as canonical apcore-toolkit Markdown.
|
|
95
|
+
*
|
|
96
|
+
* Returns `null` when the toolkit hasn't been primed yet — callers that want
|
|
97
|
+
* Markdown rendering inside synchronous code paths (like
|
|
98
|
+
* `MCPServerFactory.buildTool`) MUST `await primeMarkdownToolkit()` during
|
|
99
|
+
* their async startup so the cached reference is populated.
|
|
100
|
+
*
|
|
101
|
+
* Once primed, this function is a thin sync wrapper around `formatModule`
|
|
102
|
+
* (which is itself synchronous in apcore-toolkit-js). Returns the Markdown
|
|
103
|
+
* body — title, description, parameters list, returns list, behavior table
|
|
104
|
+
* (toolkit 0.6.x emits only fields that differ from defaults), tags, and
|
|
105
|
+
* examples.
|
|
106
|
+
*
|
|
107
|
+
* Returns `null` (not an error) when apcore-toolkit is not installed, so
|
|
108
|
+
* callers can fall back to plain `descriptor.description`.
|
|
109
|
+
*/
|
|
110
|
+
export function renderModuleMarkdownSync(descriptor, options = {}) {
|
|
111
|
+
if (!_toolkitLoaded || !_toolkit)
|
|
112
|
+
return null;
|
|
113
|
+
const scanned = descriptorToScannedModule(descriptor);
|
|
114
|
+
try {
|
|
115
|
+
const rendered = _toolkit.formatModule(scanned, {
|
|
116
|
+
style: "markdown",
|
|
117
|
+
display: options.display ?? true,
|
|
118
|
+
});
|
|
119
|
+
return typeof rendered === "string" ? rendered : null;
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=markdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.js","sourceRoot":"","sources":["../src/markdown.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAkBH,IAAI,QAA0C,CAAC;AAC/C,IAAI,cAAc,GAAG,KAAK,CAAC;AAE3B,KAAK,UAAU,WAAW;IACxB,IAAI,cAAc;QAAE,OAAO,QAAQ,IAAI,IAAI,CAAC;IAC5C,IAAI,CAAC;QACH,8DAA8D;QAC9D,MAAM,GAAG,GAAG,CAAC,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAQ,CAAC;QACpD,MAAM,YAAY,GAAG,GAAG,CAAC,YAAY,IAAI,GAAG,CAAC,OAAO,EAAE,YAAY,CAAC;QACnE,IAAI,OAAO,YAAY,KAAK,UAAU,EAAE,CAAC;YACvC,QAAQ,GAAG,EAAE,YAAY,EAAE,CAAC;QAC9B,CAAC;aAAM,CAAC;YACN,QAAQ,GAAG,IAAI,CAAC;QAClB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,QAAQ,GAAG,IAAI,CAAC;IAClB,CAAC;IACD,cAAc,GAAG,IAAI,CAAC;IACtB,OAAO,QAAQ,IAAI,IAAI,CAAC;AAC1B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB;IACjC,OAAO,cAAc,IAAI,QAAQ,KAAK,IAAI,CAAC;AAC7C,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB;IACxC,MAAM,WAAW,EAAE,CAAC;IACpB,OAAO,mBAAmB,EAAE,CAAC;AAC/B,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,yBAAyB,CAChC,UAA4B;IAE5B,mEAAmE;IACnE,mEAAmE;IACnE,mEAAmE;IACnE,qEAAqE;IACrE,MAAM,OAAO,GACV,UAAsE,CAAC,OAAO,IAAI,IAAI,CAAC;IAC1F,OAAO;QACL,QAAQ,EAAE,UAAU,CAAC,QAAQ;QAC7B,WAAW,EAAE,UAAU,CAAC,WAAW,IAAI,EAAE;QACzC,WAAW,EAAE,UAAU,CAAC,WAAW,IAAI,EAAE;QACzC,YAAY,EAAE,UAAU,CAAC,YAAY,IAAI,EAAE;QAC3C,IAAI,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;QAClC,MAAM,EAAE,EAAE;QACV,OAAO,EAAE,UAAU,CAAC,OAAO,IAAI,OAAO;QACtC,WAAW,EAAE,UAAU,CAAC,WAAW,IAAI,IAAI;QAC3C,aAAa,EAAE,UAAU,CAAC,aAAa,IAAI,IAAI;QAC/C,cAAc,EAAE,IAAI;QACpB,QAAQ,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC;QAC1C,QAAQ,EAAE,EAAE,GAAG,CAAC,UAAU,CAAC,QAAQ,IAAI,EAAE,CAAC,EAAE;QAC5C,OAAO;QACP,QAAQ,EAAE,EAAE;KACb,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,wBAAwB,CACtC,UAA4B,EAC5B,UAAiC,EAAE;IAEnC,IAAI,CAAC,cAAc,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC9C,MAAM,OAAO,GAAG,yBAAyB,CAAC,UAAU,CAAC,CAAC;IACtD,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,QAAQ,CAAC,YAAY,CAAC,OAAO,EAAE;YAC9C,KAAK,EAAE,UAAU;YACjB,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,IAAI;SACjC,CAAC,CAAC;QACH,OAAO,OAAO,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC;IACxD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ApprovalBridge: exposes __apcore_approval_check as an MCP meta-tool.
|
|
3
|
+
*
|
|
4
|
+
* Symmetric with AsyncTaskBridge. Registered in MCPServerFactory.registerHandlers()
|
|
5
|
+
* alongside asyncBridge.
|
|
6
|
+
*/
|
|
7
|
+
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
8
|
+
export declare const APPROVAL_META_TOOL_NAMES: readonly ["__apcore_approval_check"];
|
|
9
|
+
export type ApprovalMetaToolName = (typeof APPROVAL_META_TOOL_NAMES)[number];
|
|
10
|
+
export declare class ApprovalBridge {
|
|
11
|
+
private readonly handler;
|
|
12
|
+
constructor(handler: {
|
|
13
|
+
checkApproval(approvalId: string): Promise<{
|
|
14
|
+
status: string;
|
|
15
|
+
reason?: string;
|
|
16
|
+
approvalId?: string;
|
|
17
|
+
}>;
|
|
18
|
+
});
|
|
19
|
+
static isMetaTool(name: string): name is ApprovalMetaToolName;
|
|
20
|
+
buildMetaTools(): Tool[];
|
|
21
|
+
handleMetaTool(name: string, arguments_: Record<string, unknown>): Promise<[Array<{
|
|
22
|
+
type: string;
|
|
23
|
+
text: string;
|
|
24
|
+
}>, boolean, string | null]>;
|
|
25
|
+
private handleCheck;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=approval-bridge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval-bridge.d.ts","sourceRoot":"","sources":["../../src/server/approval-bridge.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oCAAoC,CAAC;AAE/D,eAAO,MAAM,wBAAwB,sCAAuC,CAAC;AAE7E,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7E,qBAAa,cAAc;IAEb,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE;QACpC,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,MAAM,CAAC,EAAE,MAAM,CAAC;YAAC,UAAU,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;KACtG;IAED,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,IAAI,oBAAoB;IAI7D,cAAc,IAAI,IAAI,EAAE;IAyBlB,cAAc,CAClB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAClC,OAAO,CAAC,CAAC,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;YAW7D,WAAW;CAiC1B"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export const APPROVAL_META_TOOL_NAMES = ["__apcore_approval_check"];
|
|
2
|
+
export class ApprovalBridge {
|
|
3
|
+
handler;
|
|
4
|
+
// handler is StorageBackedApprovalHandler but typed loosely for cross-SDK compat
|
|
5
|
+
constructor(handler) {
|
|
6
|
+
this.handler = handler;
|
|
7
|
+
}
|
|
8
|
+
static isMetaTool(name) {
|
|
9
|
+
return APPROVAL_META_TOOL_NAMES.includes(name);
|
|
10
|
+
}
|
|
11
|
+
buildMetaTools() {
|
|
12
|
+
return [
|
|
13
|
+
{
|
|
14
|
+
name: "__apcore_approval_check",
|
|
15
|
+
description: "Poll the status of a pending approval request. " +
|
|
16
|
+
"Returns {approval_id, status, reason}. " +
|
|
17
|
+
"status is 'pending', 'approved', or 'rejected'. " +
|
|
18
|
+
"When 'approved', retry the original tool call with " +
|
|
19
|
+
"_meta.approvalId set to this approval_id.",
|
|
20
|
+
inputSchema: {
|
|
21
|
+
type: "object",
|
|
22
|
+
properties: {
|
|
23
|
+
approval_id: {
|
|
24
|
+
type: "string",
|
|
25
|
+
description: "The approval_id from the APPROVAL_PENDING response",
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
required: ["approval_id"],
|
|
29
|
+
additionalProperties: false,
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
async handleMetaTool(name, arguments_) {
|
|
35
|
+
if (name === "__apcore_approval_check") {
|
|
36
|
+
return this.handleCheck(arguments_);
|
|
37
|
+
}
|
|
38
|
+
return [
|
|
39
|
+
[{ type: "text", text: `Unknown approval meta-tool: ${name}` }],
|
|
40
|
+
true,
|
|
41
|
+
null,
|
|
42
|
+
];
|
|
43
|
+
}
|
|
44
|
+
async handleCheck(args) {
|
|
45
|
+
const approvalId = args["approval_id"];
|
|
46
|
+
if (typeof approvalId !== "string" || !approvalId) {
|
|
47
|
+
return [
|
|
48
|
+
[{ type: "text", text: "approval_id is required" }],
|
|
49
|
+
true,
|
|
50
|
+
null,
|
|
51
|
+
];
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const result = await this.handler.checkApproval(approvalId);
|
|
55
|
+
const payload = {
|
|
56
|
+
approval_id: approvalId,
|
|
57
|
+
status: result.status,
|
|
58
|
+
};
|
|
59
|
+
if (result.reason !== undefined) {
|
|
60
|
+
payload["reason"] = result.reason;
|
|
61
|
+
}
|
|
62
|
+
return [
|
|
63
|
+
[{ type: "text", text: JSON.stringify(payload) }],
|
|
64
|
+
false,
|
|
65
|
+
null,
|
|
66
|
+
];
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
return [
|
|
70
|
+
[{ type: "text", text: `Approval check failed: ${String(err)}` }],
|
|
71
|
+
true,
|
|
72
|
+
null,
|
|
73
|
+
];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=approval-bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval-bridge.js","sourceRoot":"","sources":["../../src/server/approval-bridge.ts"],"names":[],"mappings":"AAQA,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,yBAAyB,CAAU,CAAC;AAI7E,MAAM,OAAO,cAAc;IAEI;IAD7B,iFAAiF;IACjF,YAA6B,OAE5B;QAF4B,YAAO,GAAP,OAAO,CAEnC;IAAG,CAAC;IAEL,MAAM,CAAC,UAAU,CAAC,IAAY;QAC5B,OAAQ,wBAA8C,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACxE,CAAC;IAED,cAAc;QACZ,OAAO;YACL;gBACE,IAAI,EAAE,yBAAyB;gBAC/B,WAAW,EACT,iDAAiD;oBACjD,yCAAyC;oBACzC,kDAAkD;oBAClD,qDAAqD;oBACrD,2CAA2C;gBAC7C,WAAW,EAAE;oBACX,IAAI,EAAE,QAAiB;oBACvB,UAAU,EAAE;wBACV,WAAW,EAAE;4BACX,IAAI,EAAE,QAAQ;4BACd,WAAW,EAAE,oDAAoD;yBAClE;qBACF;oBACD,QAAQ,EAAE,CAAC,aAAa,CAAC;oBACzB,oBAAoB,EAAE,KAAK;iBAC5B;aACF;SACF,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,cAAc,CAClB,IAAY,EACZ,UAAmC;QAEnC,IAAI,IAAI,KAAK,yBAAyB,EAAE,CAAC;YACvC,OAAO,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC;QACtC,CAAC;QACD,OAAO;YACL,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,+BAA+B,IAAI,EAAE,EAAE,CAAC;YAC/D,IAAI;YACJ,IAAI;SACL,CAAC;IACJ,CAAC;IAEO,KAAK,CAAC,WAAW,CACvB,IAA6B;QAE7B,MAAM,UAAU,GAAG,IAAI,CAAC,aAAa,CAAC,CAAC;QACvC,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,CAAC,UAAU,EAAE,CAAC;YAClD,OAAO;gBACL,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,yBAAyB,EAAE,CAAC;gBACnD,IAAI;gBACJ,IAAI;aACL,CAAC;QACJ,CAAC;QACD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;YAC5D,MAAM,OAAO,GAA4B;gBACvC,WAAW,EAAE,UAAU;gBACvB,MAAM,EAAE,MAAM,CAAC,MAAM;aACtB,CAAC;YACF,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;gBAChC,OAAO,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC;YACpC,CAAC;YACD,OAAO;gBACL,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;gBACjD,KAAK;gBACL,IAAI;aACL,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO;gBACL,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,0BAA0B,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;gBACjE,IAAI;gBACJ,IAAI;aACL,CAAC;QACJ,CAAC;IACH,CAAC;CACF"}
|
|
@@ -14,12 +14,18 @@
|
|
|
14
14
|
import type { ModuleDescriptor } from "../types.js";
|
|
15
15
|
/** Reserved meta-tool prefix. Module ids starting with this are forbidden. */
|
|
16
16
|
export declare const APCORE_META_TOOL_PREFIX = "__apcore_";
|
|
17
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* Reserved meta-tool names.
|
|
19
|
+
*
|
|
20
|
+
* Four task-management tools (submit/status/cancel/list) plus one
|
|
21
|
+
* preflight tool (`__apcore_module_preview`, apcore 0.21 PROTOCOL_SPEC §5.6).
|
|
22
|
+
*/
|
|
18
23
|
export declare const META_TOOL_NAMES: Readonly<{
|
|
19
24
|
readonly SUBMIT: "__apcore_task_submit";
|
|
20
25
|
readonly STATUS: "__apcore_task_status";
|
|
21
26
|
readonly CANCEL: "__apcore_task_cancel";
|
|
22
27
|
readonly LIST: "__apcore_task_list";
|
|
28
|
+
readonly PREVIEW: "__apcore_module_preview";
|
|
23
29
|
}>;
|
|
24
30
|
/** Duck-typed TaskInfo projection matching apcore-js `TaskInfo`. */
|
|
25
31
|
export interface TaskInfoProjection {
|
|
@@ -59,6 +65,30 @@ export interface AsyncTaskManagerLike {
|
|
|
59
65
|
}>;
|
|
60
66
|
shutdown?(): Promise<void>;
|
|
61
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Minimal duck-typed `Executor` contract used by `__apcore_module_preview`.
|
|
70
|
+
*
|
|
71
|
+
* `validate(moduleId, inputs, context)` is the dry-run preflight entry
|
|
72
|
+
* point in apcore 0.20+; it never throws on input-shape errors —
|
|
73
|
+
* malformed inputs are surfaced as a failed `PreflightCheckResult` in
|
|
74
|
+
* the returned `PreflightResult`. apcore 0.21 added
|
|
75
|
+
* `predicted_changes` populated from `Module.preview()`.
|
|
76
|
+
*/
|
|
77
|
+
export interface ExecutorLike {
|
|
78
|
+
validate(moduleId: string, inputs?: Record<string, unknown> | null, context?: unknown | null): Promise<{
|
|
79
|
+
valid: boolean;
|
|
80
|
+
requiresApproval?: boolean;
|
|
81
|
+
requires_approval?: boolean;
|
|
82
|
+
predictedChanges?: unknown[];
|
|
83
|
+
predicted_changes?: unknown[];
|
|
84
|
+
checks?: Array<{
|
|
85
|
+
check: string;
|
|
86
|
+
passed: boolean;
|
|
87
|
+
error?: unknown;
|
|
88
|
+
warnings?: string[];
|
|
89
|
+
}>;
|
|
90
|
+
}>;
|
|
91
|
+
}
|
|
62
92
|
/** MCP-facing Tool shape used to advertise meta-tools. */
|
|
63
93
|
export interface AsyncMetaTool {
|
|
64
94
|
name: string;
|
|
@@ -84,6 +114,13 @@ export interface AsyncTaskBridgeOptions {
|
|
|
84
114
|
* behavior for tests / direct construction without a registry). [A-D-008]
|
|
85
115
|
*/
|
|
86
116
|
descriptorLookup?: (moduleId: string) => ModuleDescriptor | null | undefined;
|
|
117
|
+
/**
|
|
118
|
+
* Optional executor reference used by `__apcore_module_preview` to drive
|
|
119
|
+
* the cross-language `executor.validate()` preflight (apcore PROTOCOL_SPEC
|
|
120
|
+
* §5.6). When omitted, the preview meta-tool returns
|
|
121
|
+
* `PREVIEW_UNAVAILABLE`.
|
|
122
|
+
*/
|
|
123
|
+
executor?: ExecutorLike;
|
|
87
124
|
}
|
|
88
125
|
/**
|
|
89
126
|
* Routes MCP tool calls either to the synchronous Execution Router (default)
|
|
@@ -95,6 +132,7 @@ export declare class AsyncTaskBridge {
|
|
|
95
132
|
private readonly _redactSensitive?;
|
|
96
133
|
private readonly _outputSchemaMap;
|
|
97
134
|
private readonly _descriptorLookup?;
|
|
135
|
+
private readonly _executor?;
|
|
98
136
|
/**
|
|
99
137
|
* Maps task_id → progressToken recorded at submit time so terminal-
|
|
100
138
|
* state notifications can be fanned out via the original token.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"async-task-bridge.d.ts","sourceRoot":"","sources":["../../src/server/async-task-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"async-task-bridge.d.ts","sourceRoot":"","sources":["../../src/server/async-task-bridge.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,14 +11,64 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Full spec: apcore-mcp/docs/features/async-task-bridge.md (F-043).
|
|
13
13
|
*/
|
|
14
|
+
import { ErrorCodes } from "../types.js";
|
|
15
|
+
/**
|
|
16
|
+
* Lazy snapshot of apcore-js's TaskLimitExceededError class for `instanceof`
|
|
17
|
+
* dispatch in handleMetaTool. Mirrors the lazy-load pattern used by
|
|
18
|
+
* adapters/errors.ts so callers don't pay an import-time tax.
|
|
19
|
+
*/
|
|
20
|
+
let _taskLimitExceededClass;
|
|
21
|
+
async function _loadTaskLimitExceededClass() {
|
|
22
|
+
if (_taskLimitExceededClass !== undefined) {
|
|
23
|
+
return _taskLimitExceededClass ?? null;
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
27
|
+
const apcore = (await import("apcore-js"));
|
|
28
|
+
_taskLimitExceededClass = apcore.TaskLimitExceededError ?? null;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
_taskLimitExceededClass = null;
|
|
32
|
+
}
|
|
33
|
+
return _taskLimitExceededClass ?? null;
|
|
34
|
+
}
|
|
35
|
+
// Eager fire-and-forget load so the cache is warm by the time handleMetaTool
|
|
36
|
+
// fires under typical flows. The synchronous code path below tolerates a cold
|
|
37
|
+
// cache by also duck-typing on `error.code`.
|
|
38
|
+
void _loadTaskLimitExceededClass();
|
|
39
|
+
/**
|
|
40
|
+
* Detect a task-capacity-exceeded error from apcore-js. Prefers `instanceof`
|
|
41
|
+
* dispatch when the class has been resolved; falls back to duck-typing on
|
|
42
|
+
* `error.code === "TASK_LIMIT_EXCEEDED"` so cold-cache or
|
|
43
|
+
* apcore-js-unavailable scenarios still classify correctly.
|
|
44
|
+
*/
|
|
45
|
+
function _isTaskLimitExceeded(err) {
|
|
46
|
+
if (err === null || typeof err !== "object")
|
|
47
|
+
return false;
|
|
48
|
+
const cls = _taskLimitExceededClass;
|
|
49
|
+
if (cls && err instanceof cls)
|
|
50
|
+
return true;
|
|
51
|
+
const obj = err;
|
|
52
|
+
if (obj.code === ErrorCodes.TASK_LIMIT_EXCEEDED)
|
|
53
|
+
return true;
|
|
54
|
+
if (obj.name === "TaskLimitExceededError")
|
|
55
|
+
return true;
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
14
58
|
/** Reserved meta-tool prefix. Module ids starting with this are forbidden. */
|
|
15
59
|
export const APCORE_META_TOOL_PREFIX = "__apcore_";
|
|
16
|
-
/**
|
|
60
|
+
/**
|
|
61
|
+
* Reserved meta-tool names.
|
|
62
|
+
*
|
|
63
|
+
* Four task-management tools (submit/status/cancel/list) plus one
|
|
64
|
+
* preflight tool (`__apcore_module_preview`, apcore 0.21 PROTOCOL_SPEC §5.6).
|
|
65
|
+
*/
|
|
17
66
|
export const META_TOOL_NAMES = Object.freeze({
|
|
18
67
|
SUBMIT: "__apcore_task_submit",
|
|
19
68
|
STATUS: "__apcore_task_status",
|
|
20
69
|
CANCEL: "__apcore_task_cancel",
|
|
21
70
|
LIST: "__apcore_task_list",
|
|
71
|
+
PREVIEW: "__apcore_module_preview",
|
|
22
72
|
});
|
|
23
73
|
/**
|
|
24
74
|
* Routes MCP tool calls either to the synchronous Execution Router (default)
|
|
@@ -30,6 +80,7 @@ export class AsyncTaskBridge {
|
|
|
30
80
|
_redactSensitive;
|
|
31
81
|
_outputSchemaMap;
|
|
32
82
|
_descriptorLookup;
|
|
83
|
+
_executor;
|
|
33
84
|
/**
|
|
34
85
|
* Maps task_id → progressToken recorded at submit time so terminal-
|
|
35
86
|
* state notifications can be fanned out via the original token.
|
|
@@ -48,6 +99,7 @@ export class AsyncTaskBridge {
|
|
|
48
99
|
this._redactSensitive = options?.redactSensitive;
|
|
49
100
|
this._outputSchemaMap = options?.outputSchemaMap ?? {};
|
|
50
101
|
this._descriptorLookup = options?.descriptorLookup;
|
|
102
|
+
this._executor = options?.executor;
|
|
51
103
|
}
|
|
52
104
|
/** Whether async routing and meta-tools are enabled. */
|
|
53
105
|
get enabled() {
|
|
@@ -67,14 +119,17 @@ export class AsyncTaskBridge {
|
|
|
67
119
|
const meta = descriptor.metadata ?? {};
|
|
68
120
|
// Accept both boolean true AND the string "true" — Python and Rust
|
|
69
121
|
// both accept the string form (registries that store annotations as
|
|
70
|
-
// YAML produce string booleans).
|
|
122
|
+
// YAML produce string booleans). Match Python's case-insensitive
|
|
123
|
+
// semantics so that `"True"` / `"TRUE"` round-tripped through YAML
|
|
124
|
+
// dispatchers agree across SDKs. [A-D-021 / A-003 cross-lang fix]
|
|
125
|
+
const isTruthyAsyncString = (v) => typeof v === "string" && v.toLowerCase() === "true";
|
|
71
126
|
const asyncFlag = meta["async"];
|
|
72
|
-
if (asyncFlag === true || asyncFlag
|
|
127
|
+
if (asyncFlag === true || isTruthyAsyncString(asyncFlag))
|
|
73
128
|
return true;
|
|
74
129
|
const annotations = descriptor.annotations;
|
|
75
130
|
if (annotations?.extra) {
|
|
76
131
|
const flag = annotations.extra["mcp_async"];
|
|
77
|
-
if (flag === true || flag
|
|
132
|
+
if (flag === true || isTruthyAsyncString(flag))
|
|
78
133
|
return true;
|
|
79
134
|
}
|
|
80
135
|
return false;
|
|
@@ -218,6 +273,24 @@ export class AsyncTaskBridge {
|
|
|
218
273
|
},
|
|
219
274
|
},
|
|
220
275
|
},
|
|
276
|
+
{
|
|
277
|
+
name: META_TOOL_NAMES.PREVIEW,
|
|
278
|
+
description: "Preview a module call: predict state changes, validate inputs, " +
|
|
279
|
+
"and check approval requirements WITHOUT executing the module. " +
|
|
280
|
+
"Returns `{valid, requires_approval, predicted_changes, checks}`. " +
|
|
281
|
+
"Use this before invoking destructive or stateful modules to let " +
|
|
282
|
+
"the AI orchestrator answer 'what would change in the world if I " +
|
|
283
|
+
"called this?' (apcore PROTOCOL_SPEC §5.6).",
|
|
284
|
+
inputSchema: {
|
|
285
|
+
type: "object",
|
|
286
|
+
additionalProperties: false,
|
|
287
|
+
properties: {
|
|
288
|
+
module_id: { type: "string" },
|
|
289
|
+
arguments: { type: "object", additionalProperties: true, default: {} },
|
|
290
|
+
},
|
|
291
|
+
required: ["module_id"],
|
|
292
|
+
},
|
|
293
|
+
},
|
|
221
294
|
];
|
|
222
295
|
}
|
|
223
296
|
/**
|
|
@@ -229,7 +302,8 @@ export class AsyncTaskBridge {
|
|
|
229
302
|
(toolName === META_TOOL_NAMES.SUBMIT ||
|
|
230
303
|
toolName === META_TOOL_NAMES.STATUS ||
|
|
231
304
|
toolName === META_TOOL_NAMES.CANCEL ||
|
|
232
|
-
toolName === META_TOOL_NAMES.LIST
|
|
305
|
+
toolName === META_TOOL_NAMES.LIST ||
|
|
306
|
+
toolName === META_TOOL_NAMES.PREVIEW));
|
|
233
307
|
}
|
|
234
308
|
/**
|
|
235
309
|
* Dispatch a meta-tool invocation. The caller must have already confirmed
|
|
@@ -271,7 +345,30 @@ export class AsyncTaskBridge {
|
|
|
271
345
|
throw new Error("__apcore_task_submit requires `arguments` to be a JSON object");
|
|
272
346
|
}
|
|
273
347
|
const inputs = rawArgs ?? {};
|
|
274
|
-
|
|
348
|
+
// [A-D-222] Catch TaskLimitExceededError from manager.submit() and
|
|
349
|
+
// surface it as a structured TASK_LIMIT_EXCEEDED envelope with
|
|
350
|
+
// retryable=true. Mirrors Python's
|
|
351
|
+
// `apcore_mcp/server/async_task_bridge.py:375-376` which explicitly
|
|
352
|
+
// catches TaskLimitExceededError. Pre-fix TS let the bare error
|
|
353
|
+
// bubble — it would still get mapped by the router's ErrorMapper,
|
|
354
|
+
// but bridges that call handleMetaTool directly (toolkit/library
|
|
355
|
+
// use) saw a raw Error without errorType or retryable hints.
|
|
356
|
+
try {
|
|
357
|
+
return (await this.submit(moduleId, inputs, context));
|
|
358
|
+
}
|
|
359
|
+
catch (err) {
|
|
360
|
+
if (_isTaskLimitExceeded(err)) {
|
|
361
|
+
const e = err;
|
|
362
|
+
return {
|
|
363
|
+
isError: true,
|
|
364
|
+
errorType: ErrorCodes.TASK_LIMIT_EXCEEDED,
|
|
365
|
+
message: e.message,
|
|
366
|
+
details: e.details ?? null,
|
|
367
|
+
retryable: true,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
throw err;
|
|
371
|
+
}
|
|
275
372
|
}
|
|
276
373
|
case META_TOOL_NAMES.STATUS: {
|
|
277
374
|
const taskId = args["task_id"];
|
|
@@ -280,9 +377,14 @@ export class AsyncTaskBridge {
|
|
|
280
377
|
}
|
|
281
378
|
const info = this._manager.getStatus(taskId);
|
|
282
379
|
if (!info) {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
380
|
+
// [D10-003] Return envelope (matching Python's `(content, is_error=True, None)`
|
|
381
|
+
// and Rust's `Ok(envelope)`). Throwing here would force the caller's catch path
|
|
382
|
+
// to diverge from Python+Rust, breaking cross-SDK error handling.
|
|
383
|
+
return {
|
|
384
|
+
error: "ASYNC_TASK_NOT_FOUND",
|
|
385
|
+
task_id: taskId,
|
|
386
|
+
isError: true,
|
|
387
|
+
};
|
|
286
388
|
}
|
|
287
389
|
const projection = this._projectTaskInfo(info);
|
|
288
390
|
// [A-D-025] Inline result on terminal completed status — even
|
|
@@ -314,6 +416,18 @@ export class AsyncTaskBridge {
|
|
|
314
416
|
if (typeof taskId !== "string" || taskId.length === 0) {
|
|
315
417
|
throw new Error("__apcore_task_cancel requires task_id (string)");
|
|
316
418
|
}
|
|
419
|
+
// [D10-004] Pre-check existence (matching Python `_handle_cancel_tool`).
|
|
420
|
+
// Without this, unknown task_ids silently return `{cancelled: false}` —
|
|
421
|
+
// callers can't distinguish "task existed and is uncancellable" from
|
|
422
|
+
// "task never existed".
|
|
423
|
+
if (!this._manager.getStatus(taskId)) {
|
|
424
|
+
this._progressTokens.delete(taskId);
|
|
425
|
+
return {
|
|
426
|
+
error: "ASYNC_TASK_NOT_FOUND",
|
|
427
|
+
task_id: taskId,
|
|
428
|
+
isError: true,
|
|
429
|
+
};
|
|
430
|
+
}
|
|
317
431
|
const cancelled = await this._manager.cancel(taskId);
|
|
318
432
|
// [A-D-009] Drop the progress-token binding after a single-task cancel
|
|
319
433
|
// so notifications/progress for cancelled tasks don't fan out to a
|
|
@@ -323,6 +437,72 @@ export class AsyncTaskBridge {
|
|
|
323
437
|
this._progressTokens.delete(taskId);
|
|
324
438
|
return { task_id: taskId, cancelled };
|
|
325
439
|
}
|
|
440
|
+
case META_TOOL_NAMES.PREVIEW: {
|
|
441
|
+
const moduleId = args["module_id"];
|
|
442
|
+
if (typeof moduleId !== "string" || moduleId.length === 0) {
|
|
443
|
+
throw new Error("__apcore_module_preview requires module_id (string)");
|
|
444
|
+
}
|
|
445
|
+
// Reject reserved-prefix module ids the same way the submit branch
|
|
446
|
+
// does at line 474 — the preview meta-tool must not introspect the
|
|
447
|
+
// bridge's own meta-tools (apcore PROTOCOL_SPEC §5.6 contract).
|
|
448
|
+
// Cross-SDK parity: Python and Rust apply the same guard.
|
|
449
|
+
if (moduleId.startsWith(APCORE_META_TOOL_PREFIX)) {
|
|
450
|
+
throw new Error(`Reserved module id: "${moduleId}". Names starting with ` +
|
|
451
|
+
`"${APCORE_META_TOOL_PREFIX}" are reserved for apcore-mcp ` +
|
|
452
|
+
"meta-tools and cannot be previewed via this handler.");
|
|
453
|
+
}
|
|
454
|
+
if (!this._executor) {
|
|
455
|
+
// Bridge was constructed without an executor reference — preview
|
|
456
|
+
// is a runtime no-op rather than an error so the meta-tool stays
|
|
457
|
+
// discoverable across heterogenous deployments.
|
|
458
|
+
return {
|
|
459
|
+
error: "PREVIEW_UNAVAILABLE",
|
|
460
|
+
message: "Module preview requires an Executor reference; bridge was built without one.",
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
// Preserve `arguments: null` verbatim (forwarded as null to
|
|
464
|
+
// executor.validate). The calling business decides whether
|
|
465
|
+
// null is acceptable for the target module. Reject only
|
|
466
|
+
// structurally-impossible shapes — arrays and scalars can
|
|
467
|
+
// never represent a JSON object.
|
|
468
|
+
const rawArgs = args["arguments"];
|
|
469
|
+
if (rawArgs !== undefined &&
|
|
470
|
+
rawArgs !== null &&
|
|
471
|
+
(typeof rawArgs !== "object" || Array.isArray(rawArgs))) {
|
|
472
|
+
throw new Error("__apcore_module_preview requires `arguments` to be a JSON object or null");
|
|
473
|
+
}
|
|
474
|
+
// null → null, undefined → null (no caller intent to pass inputs),
|
|
475
|
+
// {x:1} → {x:1}. Distinguishing missing from explicit null is not
|
|
476
|
+
// observable downstream — both represent "no inputs supplied".
|
|
477
|
+
const inputs = rawArgs === undefined
|
|
478
|
+
? null
|
|
479
|
+
: rawArgs;
|
|
480
|
+
const preflight = await this._executor.validate(moduleId, inputs, context ?? null);
|
|
481
|
+
// PreflightResult fields land in either snake_case or camelCase
|
|
482
|
+
// depending on the apcore-js minor; normalize to the canonical
|
|
483
|
+
// wire shape (snake_case) Python and Rust emit.
|
|
484
|
+
const requiresApproval = preflight.requires_approval ??
|
|
485
|
+
preflight.requiresApproval ??
|
|
486
|
+
false;
|
|
487
|
+
const predictedChanges = preflight.predicted_changes ?? preflight.predictedChanges ?? [];
|
|
488
|
+
const checks = (preflight.checks ?? []).map((c) => {
|
|
489
|
+
const out = {
|
|
490
|
+
check: c.check,
|
|
491
|
+
passed: c.passed,
|
|
492
|
+
};
|
|
493
|
+
if (c.error !== undefined && c.error !== null)
|
|
494
|
+
out["error"] = c.error;
|
|
495
|
+
if (c.warnings && c.warnings.length > 0)
|
|
496
|
+
out["warnings"] = c.warnings;
|
|
497
|
+
return out;
|
|
498
|
+
});
|
|
499
|
+
return {
|
|
500
|
+
valid: preflight.valid,
|
|
501
|
+
requires_approval: requiresApproval,
|
|
502
|
+
predicted_changes: predictedChanges,
|
|
503
|
+
checks,
|
|
504
|
+
};
|
|
505
|
+
}
|
|
326
506
|
case META_TOOL_NAMES.LIST: {
|
|
327
507
|
// [A-D-024] Validate status filter against the spec enum;
|
|
328
508
|
// Python+Rust both reject unknown values. Pre-fix TS forwarded
|
|
@@ -379,11 +559,17 @@ export async function createAsyncTaskBridge(executor, options) {
|
|
|
379
559
|
return null;
|
|
380
560
|
const manager = new AsyncTaskManager(executor, options?.maxConcurrent ?? 10, options?.maxTasks ?? 1000);
|
|
381
561
|
const redactSensitive = apcore.redactSensitive ?? apcore.default?.redactSensitive;
|
|
562
|
+
// Pass the executor through so `__apcore_module_preview` can drive
|
|
563
|
+
// `executor.validate()` (apcore PROTOCOL_SPEC §5.6).
|
|
564
|
+
const executorLike = typeof executor?.validate === "function"
|
|
565
|
+
? executor
|
|
566
|
+
: undefined;
|
|
382
567
|
return new AsyncTaskBridge(manager, {
|
|
383
568
|
enabled: true,
|
|
384
569
|
redactSensitive: typeof redactSensitive === "function" ? redactSensitive : undefined,
|
|
385
570
|
outputSchemaMap: options?.outputSchemaMap,
|
|
386
571
|
descriptorLookup: options?.descriptorLookup,
|
|
572
|
+
executor: executorLike,
|
|
387
573
|
});
|
|
388
574
|
}
|
|
389
575
|
catch {
|