@tapicker/bridge 1.0.0 → 1.1.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 +15 -15
- package/dist/bridge/bridge-service.d.ts +1 -1
- package/dist/bridge/bridge-service.js +7 -7
- package/dist/bridge/control.js +1 -1
- package/dist/bridge/export-store.d.ts +35 -0
- package/dist/bridge/export-store.js +195 -0
- package/dist/bridge/extension-session.d.ts +9 -2
- package/dist/bridge/extension-session.js +56 -8
- package/dist/cli.js +563 -148
- package/dist/commands.d.ts +8 -0
- package/dist/commands.js +276 -40
- package/dist/constants.d.ts +2 -2
- package/dist/constants.js +4 -2
- package/dist/http-client.d.ts +24 -0
- package/dist/http-client.js +101 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.js +2 -2
- package/dist/mcp.d.ts +4 -3
- package/dist/mcp.js +221 -134
- package/dist/protocol/schema.d.ts +503 -34
- package/dist/protocol/schema.js +396 -15
- package/dist/server.d.ts +7 -4
- package/dist/server.js +70 -26
- package/dist/update-notifier-worker.d.ts +2 -0
- package/dist/update-notifier-worker.js +3 -0
- package/dist/update-notifier.d.ts +9 -0
- package/dist/update-notifier.js +178 -0
- package/dist/workflow-watch.d.ts +8 -7
- package/dist/workflow-watch.js +39 -29
- package/package.json +16 -6
- package/docs/cli.md +0 -112
- package/docs/http-api.md +0 -149
- package/docs/mcp.md +0 -90
- package/docs/operations.md +0 -85
- package/docs/workflow-watch.md +0 -86
- package/src/bridge/bridge-service.ts +0 -291
- package/src/bridge/control.ts +0 -281
- package/src/bridge/daemon.ts +0 -102
- package/src/bridge/extension-session.ts +0 -286
- package/src/bridge/pairing-state.ts +0 -92
- package/src/cli.ts +0 -1019
- package/src/commands.ts +0 -158
- package/src/constants.ts +0 -10
- package/src/errors.ts +0 -72
- package/src/index.ts +0 -35
- package/src/mcp.ts +0 -386
- package/src/protocol/schema.ts +0 -202
- package/src/server.ts +0 -623
- package/src/workflow-watch.ts +0 -364
package/dist/commands.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import type { ExportStore } from './bridge/export-store.js';
|
|
2
3
|
import type { BridgeService } from './bridge/bridge-service.js';
|
|
3
4
|
export type CommandContext = {
|
|
4
5
|
bridge: BridgeService;
|
|
6
|
+
exports?: ExportStore;
|
|
5
7
|
};
|
|
6
8
|
type CommandDefinition = {
|
|
7
9
|
description: string;
|
|
8
10
|
schema: z.ZodType;
|
|
11
|
+
resultSchema: z.ZodType;
|
|
9
12
|
execute: (input: unknown, context: CommandContext, clientId?: string) => Promise<unknown>;
|
|
10
13
|
};
|
|
11
14
|
export declare function getCommandDefinition(name: string): CommandDefinition;
|
|
@@ -17,11 +20,16 @@ export declare function getCommandCapabilities(): ({
|
|
|
17
20
|
clientId: z.ZodOptional<z.ZodUUID>;
|
|
18
21
|
input: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
19
22
|
}, z.core.$strict>>;
|
|
23
|
+
outputSchema: z.core.ZodStandardJSONSchemaPayload<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
|
|
20
24
|
} | {
|
|
21
25
|
name: string;
|
|
22
26
|
description: string;
|
|
23
27
|
transport: string;
|
|
24
28
|
path: string;
|
|
29
|
+
outputSchema: z.core.ZodStandardJSONSchemaPayload<z.ZodObject<{
|
|
30
|
+
workflowId: z.ZodString;
|
|
31
|
+
clientId: z.ZodUUID;
|
|
32
|
+
}, z.core.$strip>>;
|
|
25
33
|
inputSchema: z.core.ZodStandardJSONSchemaPayload<z.ZodObject<{
|
|
26
34
|
clientId: z.ZodOptional<z.ZodUUID>;
|
|
27
35
|
input: z.ZodObject<{
|
package/dist/commands.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import {
|
|
2
|
+
import { DEFAULT_WORKFLOW_STATUS_WATCH_WS_PATH } from './constants.js';
|
|
3
3
|
import { BridgeError } from './errors.js';
|
|
4
|
-
import { WorkflowControlInputSchema, DataInputSchema, WorkflowStartInputSchema, WorkflowStartResultSchema,
|
|
4
|
+
import { WorkflowControlInputSchema, DataInputSchema, WorkflowStartInputSchema, WorkflowStartResultSchema, WorkflowStatusInputSchema, WorkflowStatusResultSchema, WorkflowControlResultSchema, DataGetResultSchema, DataGetInputSchema, DataListInputSchema, DataListResultSchema, DataPreviewInputSchema, DataPreviewResultSchema, DEFAULT_DATA_PAGE_SIZE, DataDeleteResultSchema, UserMeInputSchema, UserMeResultSchema, DataExportInputSchema, DataExportReceiptSchema, DataExportResultSchema, WorkflowStatusWatchRequestSchema, WorkflowLogsInputSchema, WorkflowLogInputSchema, WorkflowLogsResultSchema, WorkflowLogResultSchema, WorkflowListInputSchema, WorkflowListResultSchema, WorkflowGetInputSchema, WorkflowGetResultSchema, WorkflowCreateInputSchema, WorkflowCreateResultSchema, WorkflowUpdateInputSchema, WorkflowUpdateResultSchema, WorkflowGuideInputSchema, WorkflowGuideResultSchema, WorkflowSchemaInputSchema, WorkflowSchemaResultSchema, WorkflowValidateInputSchema, WorkflowValidateResultSchema, } from './protocol/schema.js';
|
|
5
5
|
const CommandRequestSchema = z
|
|
6
6
|
.object({
|
|
7
7
|
clientId: z.uuid().optional(),
|
|
@@ -9,72 +9,306 @@ const CommandRequestSchema = z
|
|
|
9
9
|
})
|
|
10
10
|
.strict();
|
|
11
11
|
const commandDefinitions = {
|
|
12
|
-
'workflow.
|
|
13
|
-
description: '
|
|
14
|
-
schema:
|
|
12
|
+
'workflow.guide': {
|
|
13
|
+
description: 'Read Workflow authoring documentation by topic; start with overview and follow references.topic',
|
|
14
|
+
schema: WorkflowGuideInputSchema,
|
|
15
|
+
resultSchema: WorkflowGuideResultSchema,
|
|
15
16
|
execute: async (input, context, clientId) => {
|
|
16
|
-
const
|
|
17
|
-
if (
|
|
18
|
-
throw new BridgeError('InvalidExtensionResponse', 'Extension returned
|
|
17
|
+
const result = await forwardCommand('workflow.guide', input, WorkflowGuideResultSchema, context, clientId);
|
|
18
|
+
if (result.topic !== input.topic) {
|
|
19
|
+
throw new BridgeError('InvalidExtensionResponse', 'Extension returned a different Workflow guide topic', {
|
|
19
20
|
status: 502,
|
|
20
21
|
});
|
|
21
22
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
return result;
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
'workflow.schema': {
|
|
27
|
+
description: 'Get JSON Schema for Workflow documents using the selected node types; omit nodeTypes for all',
|
|
28
|
+
schema: WorkflowSchemaInputSchema,
|
|
29
|
+
resultSchema: WorkflowSchemaResultSchema,
|
|
30
|
+
execute: (input, context, clientId) => forwardCommand('workflow.schema', input, WorkflowSchemaResultSchema, context, clientId),
|
|
31
|
+
},
|
|
32
|
+
'workflow.validate': {
|
|
33
|
+
description: 'Check a Workflow document without saving or running it; inspect isValid and normalized nodes or issues',
|
|
34
|
+
schema: WorkflowValidateInputSchema,
|
|
35
|
+
resultSchema: WorkflowValidateResultSchema,
|
|
36
|
+
execute: (input, context, clientId) => forwardCommand('workflow.validate', input, WorkflowValidateResultSchema, context, clientId),
|
|
37
|
+
},
|
|
38
|
+
'workflow.list': {
|
|
39
|
+
description: 'List Workflow summaries with scope, name, exact host filtering and pagination',
|
|
40
|
+
schema: WorkflowListInputSchema,
|
|
41
|
+
resultSchema: WorkflowListResultSchema,
|
|
42
|
+
execute: async (input, context, clientId) => {
|
|
43
|
+
const result = await forwardCommand('workflow.list', input, WorkflowListResultSchema, context, clientId);
|
|
44
|
+
const query = input;
|
|
45
|
+
if (result.scope !== query.scope ||
|
|
46
|
+
result.page !== query.page ||
|
|
47
|
+
result.pageSize !== query.pageSize ||
|
|
48
|
+
result.workflows.length > query.pageSize) {
|
|
49
|
+
throw new BridgeError('InvalidExtensionResponse', 'Extension returned a different Workflow list query', {
|
|
25
50
|
status: 502,
|
|
26
|
-
details: parsed.success
|
|
27
|
-
? {
|
|
28
|
-
requestedWorkflowId: input.workflowId,
|
|
29
|
-
returnedWorkflowId: parsed.data.workflowId,
|
|
30
|
-
}
|
|
31
|
-
: parsed.error.issues,
|
|
32
51
|
});
|
|
33
52
|
}
|
|
34
|
-
return
|
|
53
|
+
return result;
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
'workflow.get': {
|
|
57
|
+
description: 'Read current Workflow metadata and startup arguments, with nodes when permitted',
|
|
58
|
+
schema: WorkflowGetInputSchema,
|
|
59
|
+
resultSchema: WorkflowGetResultSchema,
|
|
60
|
+
execute: async (input, context, clientId) => {
|
|
61
|
+
const result = await forwardCommand('workflow.get', input, WorkflowGetResultSchema, context, clientId);
|
|
62
|
+
if (result.workflow.id !== input.workflowId) {
|
|
63
|
+
throw new BridgeError('InvalidExtensionResponse', 'Extension returned a different Workflow', { status: 502 });
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
'workflow.create': {
|
|
69
|
+
description: 'Validate and create a private Workflow without running it; check the list before retrying a failed request',
|
|
70
|
+
schema: WorkflowCreateInputSchema,
|
|
71
|
+
resultSchema: WorkflowCreateResultSchema,
|
|
72
|
+
execute: async (input, context, clientId) => {
|
|
73
|
+
const result = await forwardCommand('workflow.create', input, WorkflowCreateResultSchema, context, clientId);
|
|
74
|
+
if (!result.workflowName.endsWith(`/${input.name}`)) {
|
|
75
|
+
throw new BridgeError('InvalidExtensionResponse', 'Extension returned a different Workflow name; check the Workflow list before retrying', { status: 502 });
|
|
76
|
+
}
|
|
77
|
+
return result;
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
'workflow.update': {
|
|
81
|
+
description: 'Update Workflow fields using the revision from workflow.get; on conflict read again and reconcile changes',
|
|
82
|
+
schema: WorkflowUpdateInputSchema,
|
|
83
|
+
resultSchema: WorkflowUpdateResultSchema,
|
|
84
|
+
execute: async (input, context, clientId) => {
|
|
85
|
+
const result = await forwardCommand('workflow.update', input, WorkflowUpdateResultSchema, context, clientId);
|
|
86
|
+
const query = input;
|
|
87
|
+
if (result.workflowId !== query.workflowId || result.revision !== query.revision + 1) {
|
|
88
|
+
throw new BridgeError('InvalidExtensionResponse', 'Extension returned an invalid Workflow update response; read the Workflow before retrying', { status: 502 });
|
|
89
|
+
}
|
|
90
|
+
return result;
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
'workflow.start': {
|
|
94
|
+
description: 'Start one Workflow in a selected connected Tapicker Extension',
|
|
95
|
+
schema: WorkflowStartInputSchema,
|
|
96
|
+
resultSchema: WorkflowStartResultSchema,
|
|
97
|
+
execute: async (input, context, clientId) => {
|
|
98
|
+
const result = await forwardCommand('workflow.start', input, WorkflowStartResultSchema, context, clientId, 55_000);
|
|
99
|
+
if (result.workflowId !== input.workflowId) {
|
|
100
|
+
throw new BridgeError('InvalidExtensionResponse', 'Extension returned a different Workflow start response', {
|
|
101
|
+
status: 502,
|
|
102
|
+
details: {
|
|
103
|
+
requestedWorkflowId: input.workflowId,
|
|
104
|
+
returnedWorkflowId: result.workflowId,
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return result;
|
|
35
109
|
},
|
|
36
110
|
},
|
|
37
111
|
'workflow.pause': {
|
|
38
112
|
description: 'Pause a running Workflow in a selected connected Tapicker Extension',
|
|
39
113
|
schema: WorkflowControlInputSchema,
|
|
40
|
-
|
|
114
|
+
resultSchema: WorkflowControlResultSchema,
|
|
115
|
+
execute: (input, context, clientId) => controlWorkflow('pause', input, context, clientId),
|
|
41
116
|
},
|
|
42
117
|
'workflow.resume': {
|
|
43
118
|
description: 'Resume a paused Workflow in a selected connected Tapicker Extension',
|
|
44
119
|
schema: WorkflowControlInputSchema,
|
|
45
|
-
|
|
120
|
+
resultSchema: WorkflowControlResultSchema,
|
|
121
|
+
execute: (input, context, clientId) => controlWorkflow('resume', input, context, clientId),
|
|
46
122
|
},
|
|
47
123
|
'workflow.stop': {
|
|
48
|
-
description: '
|
|
124
|
+
description: 'Request a stop for the active Workflow instance',
|
|
49
125
|
schema: WorkflowControlInputSchema,
|
|
50
|
-
|
|
126
|
+
resultSchema: WorkflowControlResultSchema,
|
|
127
|
+
execute: (input, context, clientId) => controlWorkflow('stop', input, context, clientId),
|
|
51
128
|
},
|
|
52
129
|
'workflow.status': {
|
|
53
|
-
description: '
|
|
54
|
-
schema:
|
|
130
|
+
description: 'Read the latest recorded Workflow status in the selected Extension',
|
|
131
|
+
schema: WorkflowStatusInputSchema,
|
|
132
|
+
resultSchema: WorkflowStatusResultSchema,
|
|
55
133
|
execute: async (input, context, clientId) => {
|
|
56
|
-
const result = await
|
|
57
|
-
const
|
|
58
|
-
if (
|
|
59
|
-
|
|
134
|
+
const result = await forwardCommand('workflow.status', input, WorkflowStatusResultSchema, context, clientId);
|
|
135
|
+
const query = input;
|
|
136
|
+
if (result.workflowId !== query.workflowId ||
|
|
137
|
+
(query.runAt !== undefined && result.runAt !== query.runAt) ||
|
|
138
|
+
(result.dataSummary !== undefined && (!query.includeDataSummary || result.status !== 'stopped'))) {
|
|
139
|
+
throw new BridgeError('InvalidExtensionResponse', 'Extension returned a different Workflow status response', {
|
|
60
140
|
status: 502,
|
|
61
|
-
details: parsed.success ? undefined : parsed.error.issues,
|
|
62
141
|
});
|
|
63
142
|
}
|
|
64
|
-
return
|
|
143
|
+
return result;
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
'workflow.logs': {
|
|
147
|
+
description: 'Query the latest run logs with optional level filters and pagination; read a single log for full details',
|
|
148
|
+
schema: WorkflowLogsInputSchema,
|
|
149
|
+
resultSchema: WorkflowLogsResultSchema,
|
|
150
|
+
execute: async (input, context, clientId) => {
|
|
151
|
+
const query = input;
|
|
152
|
+
const result = await forwardCommand('workflow.logs', input, WorkflowLogsResultSchema, context, clientId);
|
|
153
|
+
if (result.workflowId !== query.workflowId ||
|
|
154
|
+
(query.runAt !== undefined && result.runAt !== query.runAt) ||
|
|
155
|
+
result.logs.length > query.limit ||
|
|
156
|
+
result.logs.some((log, index, logs) => log.workflowId !== query.workflowId ||
|
|
157
|
+
(query.levels !== undefined && !query.levels.includes(log.level)) ||
|
|
158
|
+
(query.before !== undefined && log.id >= query.before) ||
|
|
159
|
+
(query.after !== undefined && log.id <= query.after) ||
|
|
160
|
+
(index > 0 && log.id <= logs[index - 1].id))) {
|
|
161
|
+
throw new BridgeError('InvalidExtensionResponse', 'Extension returned an invalid workflow.logs response', {
|
|
162
|
+
status: 502,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
return result;
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
'workflow.log': {
|
|
169
|
+
description: 'Get one log from the latest run with full readable details, without preview truncation',
|
|
170
|
+
schema: WorkflowLogInputSchema,
|
|
171
|
+
resultSchema: WorkflowLogResultSchema,
|
|
172
|
+
execute: async (input, context, clientId) => {
|
|
173
|
+
const query = input;
|
|
174
|
+
const result = await forwardCommand('workflow.log', input, WorkflowLogResultSchema, context, clientId);
|
|
175
|
+
if (result.workflowId !== query.workflowId ||
|
|
176
|
+
result.log.workflowId !== query.workflowId ||
|
|
177
|
+
result.log.id !== query.logId ||
|
|
178
|
+
(query.runAt !== undefined && result.runAt !== query.runAt)) {
|
|
179
|
+
throw new BridgeError('InvalidExtensionResponse', 'Extension returned an invalid workflow.log response', {
|
|
180
|
+
status: 502,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
return result;
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
'data.list': {
|
|
187
|
+
description: 'List saved data tables with optional name and Workflow ownership filters',
|
|
188
|
+
schema: DataListInputSchema,
|
|
189
|
+
resultSchema: DataListResultSchema,
|
|
190
|
+
execute: async (input, context, clientId) => {
|
|
191
|
+
const query = input;
|
|
192
|
+
const result = await forwardCommand('data.list', input, DataListResultSchema, context, clientId);
|
|
193
|
+
if (result.page !== query.page ||
|
|
194
|
+
result.pageSize !== query.pageSize ||
|
|
195
|
+
result.tables.length > query.pageSize ||
|
|
196
|
+
(result.hasMore && result.tables.length !== query.pageSize)) {
|
|
197
|
+
throw new BridgeError('InvalidExtensionResponse', 'Extension returned an invalid data.list response', {
|
|
198
|
+
status: 502,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
return result;
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
'data.preview': {
|
|
205
|
+
description: 'Preview up to 10 oldest or latest inserted rows without using export quota',
|
|
206
|
+
schema: DataPreviewInputSchema,
|
|
207
|
+
resultSchema: DataPreviewResultSchema,
|
|
208
|
+
execute: async (input, context, clientId) => {
|
|
209
|
+
const result = await forwardCommand('data.preview', input, DataPreviewResultSchema, context, clientId);
|
|
210
|
+
const query = input;
|
|
211
|
+
if (result.tableId !== query.tableId || result.sample !== query.sample) {
|
|
212
|
+
throw new BridgeError('InvalidExtensionResponse', 'Extension returned an invalid data.preview response', {
|
|
213
|
+
status: 502,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
return result;
|
|
65
217
|
},
|
|
66
218
|
},
|
|
67
219
|
'data.get': {
|
|
68
|
-
description: '
|
|
69
|
-
schema:
|
|
70
|
-
|
|
220
|
+
description: 'Read one page or all rows from a saved table; returned rows consume export quota',
|
|
221
|
+
schema: DataGetInputSchema,
|
|
222
|
+
resultSchema: DataGetResultSchema,
|
|
223
|
+
execute: async (input, context, clientId) => {
|
|
224
|
+
const result = await forwardCommand('data.get', input, DataGetResultSchema, context, clientId);
|
|
225
|
+
const query = input;
|
|
226
|
+
const validPagination = 'page' in result
|
|
227
|
+
? query.all !== true &&
|
|
228
|
+
result.page === (query.page ?? 1) &&
|
|
229
|
+
result.pageSize === (query.pageSize ?? DEFAULT_DATA_PAGE_SIZE) &&
|
|
230
|
+
result.rows.length <= result.pageSize &&
|
|
231
|
+
(!result.hasMore || result.rows.length === result.pageSize)
|
|
232
|
+
: query.all === true;
|
|
233
|
+
if (result.tableId !== query.tableId || !validPagination) {
|
|
234
|
+
throw new BridgeError('InvalidExtensionResponse', 'Extension returned an invalid data.get response', {
|
|
235
|
+
status: 502,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
return result;
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
'data.export': {
|
|
242
|
+
description: 'Export a saved table as a downloadable file',
|
|
243
|
+
schema: DataExportInputSchema,
|
|
244
|
+
resultSchema: DataExportResultSchema,
|
|
245
|
+
execute: async (input, context, clientId) => {
|
|
246
|
+
if (!context.exports)
|
|
247
|
+
throw new BridgeError('ExportUnavailable', 'File exports are unavailable', { status: 503 });
|
|
248
|
+
const query = input;
|
|
249
|
+
const selectedClientId = context.bridge.resolveCommandClientId('data.export', clientId);
|
|
250
|
+
return context.exports.create(async (upload) => {
|
|
251
|
+
const result = await forwardCommand('data.export', { ...query, ...upload }, DataExportReceiptSchema, context, selectedClientId, 120_000);
|
|
252
|
+
if (result.tableId !== query.tableId || result.format !== query.format) {
|
|
253
|
+
throw new BridgeError('InvalidExtensionResponse', 'Extension returned an invalid data.export response', {
|
|
254
|
+
status: 502,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
return result;
|
|
258
|
+
});
|
|
259
|
+
},
|
|
71
260
|
},
|
|
72
261
|
'data.delete': {
|
|
73
262
|
description: 'Delete an extracted data table by tableId',
|
|
74
263
|
schema: DataInputSchema,
|
|
75
|
-
|
|
264
|
+
resultSchema: DataDeleteResultSchema,
|
|
265
|
+
execute: async (input, context, clientId) => {
|
|
266
|
+
const result = await forwardCommand('data.delete', input, DataDeleteResultSchema, context, clientId);
|
|
267
|
+
if (result.tableId !== input.tableId) {
|
|
268
|
+
throw new BridgeError('InvalidExtensionResponse', 'Extension returned an invalid data.delete response', {
|
|
269
|
+
status: 502,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
return result;
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
'user.me': {
|
|
276
|
+
description: 'Read the current Tapicker account and plan for the selected Extension',
|
|
277
|
+
schema: UserMeInputSchema.default({}),
|
|
278
|
+
resultSchema: UserMeResultSchema,
|
|
279
|
+
execute: async (input, context, clientId) => forwardCommand('user.me', input, UserMeResultSchema, context, clientId),
|
|
76
280
|
},
|
|
77
281
|
};
|
|
282
|
+
/**
|
|
283
|
+
* 命令共用目标路由与结果校验,业务节点规则只由 Extension 维护。
|
|
284
|
+
*/
|
|
285
|
+
async function forwardCommand(name, input, schema, context, clientId, ...timeout) {
|
|
286
|
+
const { clientId: selectedClientId, result } = await context.bridge.sendCommandWithClientId(name, input, clientId, ...timeout);
|
|
287
|
+
if (!result || typeof result !== 'object' || Array.isArray(result)) {
|
|
288
|
+
throw new BridgeError('InvalidExtensionResponse', `Extension returned an invalid ${name} response`, { status: 502 });
|
|
289
|
+
}
|
|
290
|
+
const parsed = schema.safeParse({ ...result, clientId: selectedClientId });
|
|
291
|
+
if (!parsed.success) {
|
|
292
|
+
throw new BridgeError('InvalidExtensionResponse', `Extension returned an invalid ${name} response`, {
|
|
293
|
+
status: 502,
|
|
294
|
+
details: { issues: parsed.error.issues },
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
return parsed.data;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* 校验控制操作回执,避免空响应或工作流标识不匹配时误报成功。
|
|
301
|
+
*/
|
|
302
|
+
async function controlWorkflow(action, input, context, clientId) {
|
|
303
|
+
const result = await forwardCommand(`workflow.${action}`, input, WorkflowControlResultSchema, context, clientId);
|
|
304
|
+
if (result.workflowId !== input.workflowId) {
|
|
305
|
+
throw new BridgeError('InvalidExtensionResponse', `Extension returned a different workflow.${action} response`, {
|
|
306
|
+
status: 502,
|
|
307
|
+
details: { requestedWorkflowId: input.workflowId, returnedWorkflowId: result.workflowId },
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
return result;
|
|
311
|
+
}
|
|
78
312
|
export function getCommandDefinition(name) {
|
|
79
313
|
return commandDefinitions[name];
|
|
80
314
|
}
|
|
@@ -87,14 +321,14 @@ export async function dispatchCommand(name, rawInput, context) {
|
|
|
87
321
|
if (!request.success) {
|
|
88
322
|
throw new BridgeError('InvalidArguments', `Invalid command request for ${name}`, {
|
|
89
323
|
status: 400,
|
|
90
|
-
details: request.error.issues,
|
|
324
|
+
details: { issues: request.error.issues },
|
|
91
325
|
});
|
|
92
326
|
}
|
|
93
327
|
const parsed = definition.schema.safeParse(request.data.input);
|
|
94
328
|
if (!parsed.success) {
|
|
95
329
|
throw new BridgeError('InvalidArguments', `Invalid arguments for ${name}`, {
|
|
96
330
|
status: 400,
|
|
97
|
-
details: parsed.error.issues,
|
|
331
|
+
details: { issues: parsed.error.issues },
|
|
98
332
|
});
|
|
99
333
|
}
|
|
100
334
|
return definition.execute(parsed.data, context, request.data.clientId);
|
|
@@ -110,13 +344,15 @@ export function getCommandCapabilities() {
|
|
|
110
344
|
input: definition.schema,
|
|
111
345
|
})
|
|
112
346
|
.strict(), { io: 'input' }),
|
|
347
|
+
outputSchema: z.toJSONSchema(definition.resultSchema),
|
|
113
348
|
})),
|
|
114
349
|
{
|
|
115
|
-
name: 'workflow.watch',
|
|
116
|
-
description: 'Watch one Workflow through the workflow.watch WebSocket endpoint',
|
|
350
|
+
name: 'workflow.status.watch',
|
|
351
|
+
description: 'Watch one Workflow through the workflow.status.watch WebSocket endpoint',
|
|
117
352
|
transport: 'websocket',
|
|
118
|
-
path:
|
|
119
|
-
|
|
353
|
+
path: DEFAULT_WORKFLOW_STATUS_WATCH_WS_PATH,
|
|
354
|
+
outputSchema: z.toJSONSchema(WorkflowControlResultSchema),
|
|
355
|
+
inputSchema: z.toJSONSchema(WorkflowStatusWatchRequestSchema, { io: 'input' }),
|
|
120
356
|
},
|
|
121
357
|
];
|
|
122
358
|
}
|
package/dist/constants.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
export declare const VERSION
|
|
1
|
+
export declare const VERSION: string;
|
|
2
2
|
export declare const PROTOCOL_VERSION = 1;
|
|
3
3
|
export declare const DEFAULT_HOST = "127.0.0.1";
|
|
4
4
|
export declare const DEFAULT_PORT = 9520;
|
|
5
5
|
export declare const DEFAULT_PORT_MAX = 9530;
|
|
6
6
|
export declare const DEFAULT_WS_PATH = "/ws";
|
|
7
|
-
export declare const
|
|
7
|
+
export declare const DEFAULT_WORKFLOW_STATUS_WATCH_WS_PATH = "/v1/commands/workflow.status.watch";
|
|
8
8
|
export declare const DEFAULT_MCP_PATH = "/mcp";
|
|
9
9
|
export declare const DEFAULT_REQUEST_TIMEOUT = 30000;
|
|
10
10
|
//# sourceMappingURL=constants.d.ts.map
|
package/dist/constants.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
const packageInfo = createRequire(import.meta.url)('../package.json');
|
|
3
|
+
export const VERSION = packageInfo.version;
|
|
2
4
|
export const PROTOCOL_VERSION = 1;
|
|
3
5
|
export const DEFAULT_HOST = '127.0.0.1';
|
|
4
6
|
export const DEFAULT_PORT = 9520;
|
|
5
7
|
export const DEFAULT_PORT_MAX = 9530;
|
|
6
8
|
export const DEFAULT_WS_PATH = '/ws';
|
|
7
|
-
export const
|
|
9
|
+
export const DEFAULT_WORKFLOW_STATUS_WATCH_WS_PATH = '/v1/commands/workflow.status.watch';
|
|
8
10
|
export const DEFAULT_MCP_PATH = '/mcp';
|
|
9
11
|
export const DEFAULT_REQUEST_TIMEOUT = 30_000;
|
|
10
12
|
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI 与 MCP stdio 共用 HTTP 请求和响应校验。
|
|
3
|
+
*/
|
|
4
|
+
export declare function createHttpBridgeApi(baseUrl: string, token?: string): {
|
|
5
|
+
executeCommand: (name: string, input: unknown) => Promise<unknown>;
|
|
6
|
+
listExtensions: () => Promise<{
|
|
7
|
+
ready: boolean;
|
|
8
|
+
capabilities: string[];
|
|
9
|
+
pairingRequired?: boolean | undefined;
|
|
10
|
+
clientId?: string | undefined;
|
|
11
|
+
clientName?: string | undefined;
|
|
12
|
+
clientVersion?: string | undefined;
|
|
13
|
+
user?: {
|
|
14
|
+
id: string;
|
|
15
|
+
username: string;
|
|
16
|
+
plan: "Free" | "Plus" | "Pro";
|
|
17
|
+
} | undefined;
|
|
18
|
+
}[]>;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* 接受基础地址或复制的 /mcp 端点,统一为 origin;其他路径、凭据和查询串仍拒绝。
|
|
22
|
+
*/
|
|
23
|
+
export declare function parseBridgeUrl(value: string): URL;
|
|
24
|
+
//# sourceMappingURL=http-client.d.ts.map
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { BridgeError } from './errors.js';
|
|
2
|
+
import { ExtensionListResultSchema, SafeResultSchema } from './protocol/schema.js';
|
|
3
|
+
/**
|
|
4
|
+
* CLI 与 MCP stdio 共用 HTTP 请求和响应校验。
|
|
5
|
+
*/
|
|
6
|
+
export function createHttpBridgeApi(baseUrl, token) {
|
|
7
|
+
const bridgeUrl = parseBridgeUrl(baseUrl);
|
|
8
|
+
const headers = {};
|
|
9
|
+
if (token)
|
|
10
|
+
headers.Authorization = `Bearer ${token}`;
|
|
11
|
+
const request = async (path, init = {}, timeout = 30_000) => {
|
|
12
|
+
const url = new URL(path, bridgeUrl);
|
|
13
|
+
let response;
|
|
14
|
+
try {
|
|
15
|
+
response = await fetch(url, {
|
|
16
|
+
...init,
|
|
17
|
+
headers: { ...headers, ...init.headers },
|
|
18
|
+
signal: AbortSignal.timeout(timeout),
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
catch (cause) {
|
|
22
|
+
throw new BridgeError('BridgeUnavailable', `Unable to reach Tapicker Bridge at ${url.origin}`, {
|
|
23
|
+
status: 503,
|
|
24
|
+
cause,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
let body;
|
|
28
|
+
try {
|
|
29
|
+
body = await response.json();
|
|
30
|
+
}
|
|
31
|
+
catch (cause) {
|
|
32
|
+
throw new BridgeError('InvalidBridgeResponse', 'Bridge returned an invalid JSON response', { status: 502, cause });
|
|
33
|
+
}
|
|
34
|
+
return { response, body };
|
|
35
|
+
};
|
|
36
|
+
return {
|
|
37
|
+
executeCommand: async (name, input) => {
|
|
38
|
+
let timeout;
|
|
39
|
+
if (name === 'data.export')
|
|
40
|
+
timeout = 130_000;
|
|
41
|
+
if (name === 'workflow.start')
|
|
42
|
+
timeout = 60_000;
|
|
43
|
+
const { response, body } = await request(`/v1/commands/${encodeURIComponent(name)}`, {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
headers: { 'Content-Type': 'application/json' },
|
|
46
|
+
body: JSON.stringify(input),
|
|
47
|
+
}, timeout);
|
|
48
|
+
const parsed = SafeResultSchema.safeParse(body);
|
|
49
|
+
if (!parsed.success || (parsed.data.ok && (!response.ok || !Object.hasOwn(parsed.data, 'data')))) {
|
|
50
|
+
throw new BridgeError('InvalidBridgeResponse', 'Bridge returned an invalid command response', { status: 502 });
|
|
51
|
+
}
|
|
52
|
+
if (!parsed.data.ok) {
|
|
53
|
+
throw new BridgeError(parsed.data.error.code, parsed.data.error.message, {
|
|
54
|
+
status: response.ok ? 502 : response.status,
|
|
55
|
+
details: parsed.data.error.details,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return parsed.data.data;
|
|
59
|
+
},
|
|
60
|
+
listExtensions: async () => {
|
|
61
|
+
const { response, body } = await request('/v1/extensions');
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
const parsed = SafeResultSchema.safeParse(body);
|
|
64
|
+
if (parsed.success && !parsed.data.ok) {
|
|
65
|
+
throw new BridgeError(parsed.data.error.code, parsed.data.error.message, {
|
|
66
|
+
status: response.status,
|
|
67
|
+
details: parsed.data.error.details,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const parsed = ExtensionListResultSchema.safeParse(body);
|
|
72
|
+
if (!response.ok || !parsed.success) {
|
|
73
|
+
throw new BridgeError('InvalidBridgeResponse', 'Bridge returned an invalid Extension list', { status: 502 });
|
|
74
|
+
}
|
|
75
|
+
return parsed.data.extensions;
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* 接受基础地址或复制的 /mcp 端点,统一为 origin;其他路径、凭据和查询串仍拒绝。
|
|
81
|
+
*/
|
|
82
|
+
export function parseBridgeUrl(value) {
|
|
83
|
+
let url;
|
|
84
|
+
try {
|
|
85
|
+
url = new URL(value);
|
|
86
|
+
}
|
|
87
|
+
catch (cause) {
|
|
88
|
+
throw new BridgeError('InvalidBridgeUrl', 'Bridge URL must be a valid HTTP or HTTPS URL', { status: 400, cause });
|
|
89
|
+
}
|
|
90
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
91
|
+
throw new BridgeError('InvalidBridgeUrl', 'Bridge URL must use HTTP or HTTPS', { status: 400 });
|
|
92
|
+
}
|
|
93
|
+
if (url.username || url.password || !['/', '/mcp', '/mcp/'].includes(url.pathname) || url.search || url.hash) {
|
|
94
|
+
throw new BridgeError('InvalidBridgeUrl', 'Use an HTTP(S) base URL or /mcp endpoint without credentials, query or fragment', {
|
|
95
|
+
status: 400,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
url.pathname = '/';
|
|
99
|
+
return url;
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=http-client.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -3,8 +3,8 @@ export { BridgeService } from './bridge/bridge-service.js';
|
|
|
3
3
|
export { ExtensionSession } from './bridge/extension-session.js';
|
|
4
4
|
export { dispatchCommand, getCommandCapabilities, getCommandDefinition } from './commands.js';
|
|
5
5
|
export { BridgeError, safeError, safeSuccess, serializeBridgeError } from './errors.js';
|
|
6
|
-
export { BridgeHelloDataSchema, BridgePairDataSchema, DataInputSchema, ExtensionHelloInputSchema, ExtensionPairInputSchema, MessageEnvelopeSchema, SafeResultSchema,
|
|
7
|
-
export type { DataInput, WorkflowControlInput, WorkflowStartInput, WorkflowStartResult, WorkflowStatusData, WorkflowStatusEvent,
|
|
8
|
-
export {
|
|
9
|
-
export type {
|
|
6
|
+
export { BridgeHelloDataSchema, BridgePairDataSchema, DataInputSchema, DataListInputSchema, DataListResultSchema, DataPreviewInputSchema, DataPreviewResultSchema, DataGetInputSchema, DataGetResultSchema, DataExportInputSchema, DataExportResultSchema, DataDeleteResultSchema, UserMeInputSchema, UserMeResultSchema, ExtensionListResultSchema, WorkflowControlResultSchema, WorkflowStatusWatchResultSchema, ExtensionHelloInputSchema, ExtensionPairInputSchema, MessageEnvelopeSchema, SafeResultSchema, WorkflowControlInputSchema, WorkflowProcessStatusSchema, WorkflowProcessReasonSchema, WorkflowStatusEventSchema, WorkflowStatusInputSchema, WorkflowStatusResultSchema, WorkflowStatusDataSchema, WorkflowStartInputSchema, WorkflowStartResultSchema, WorkflowStatusWatchRequestSchema, WorkflowLogLevelSchema, WorkflowLogInputSchema, WorkflowLogsInputSchema, WorkflowLogDataSchema, WorkflowLogResultSchema, WorkflowLogsResultSchema, createEnvelope, parseEnvelope, } from './protocol/schema.js';
|
|
7
|
+
export type { DataInput, WorkflowStatusInput, DataListInput, DataGetInput, DataExportResult, WorkflowControlInput, WorkflowStartInput, WorkflowStartResult, WorkflowStatusData, WorkflowStatusEvent, WorkflowStatusWatchRequest, WorkflowLogInput, WorkflowLogsInput, WorkflowLogData, WorkflowLogResult, WorkflowLogsResult, } from './protocol/schema.js';
|
|
8
|
+
export { connectWorkflowStatusWatch, waitForWorkflowStatus, workflowStatusWatchWebSocketUrl } from './workflow-watch.js';
|
|
9
|
+
export type { WorkflowStatusWatchConnection, WorkflowStatusWatchInput, WorkflowStatusWatchResult, } from './workflow-watch.js';
|
|
10
10
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,6 @@ export { BridgeService } from './bridge/bridge-service.js';
|
|
|
3
3
|
export { ExtensionSession } from './bridge/extension-session.js';
|
|
4
4
|
export { dispatchCommand, getCommandCapabilities, getCommandDefinition } from './commands.js';
|
|
5
5
|
export { BridgeError, safeError, safeSuccess, serializeBridgeError } from './errors.js';
|
|
6
|
-
export { BridgeHelloDataSchema, BridgePairDataSchema, DataInputSchema, ExtensionHelloInputSchema, ExtensionPairInputSchema, MessageEnvelopeSchema, SafeResultSchema,
|
|
7
|
-
export {
|
|
6
|
+
export { BridgeHelloDataSchema, BridgePairDataSchema, DataInputSchema, DataListInputSchema, DataListResultSchema, DataPreviewInputSchema, DataPreviewResultSchema, DataGetInputSchema, DataGetResultSchema, DataExportInputSchema, DataExportResultSchema, DataDeleteResultSchema, UserMeInputSchema, UserMeResultSchema, ExtensionListResultSchema, WorkflowControlResultSchema, WorkflowStatusWatchResultSchema, ExtensionHelloInputSchema, ExtensionPairInputSchema, MessageEnvelopeSchema, SafeResultSchema, WorkflowControlInputSchema, WorkflowProcessStatusSchema, WorkflowProcessReasonSchema, WorkflowStatusEventSchema, WorkflowStatusInputSchema, WorkflowStatusResultSchema, WorkflowStatusDataSchema, WorkflowStartInputSchema, WorkflowStartResultSchema, WorkflowStatusWatchRequestSchema, WorkflowLogLevelSchema, WorkflowLogInputSchema, WorkflowLogsInputSchema, WorkflowLogDataSchema, WorkflowLogResultSchema, WorkflowLogsResultSchema, createEnvelope, parseEnvelope, } from './protocol/schema.js';
|
|
7
|
+
export { connectWorkflowStatusWatch, waitForWorkflowStatus, workflowStatusWatchWebSocketUrl } from './workflow-watch.js';
|
|
8
8
|
//# sourceMappingURL=index.js.map
|
package/dist/mcp.d.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/server';
|
|
2
2
|
import type { BridgeService } from './bridge/bridge-service.js';
|
|
3
|
+
import type { ExportStore } from './bridge/export-store.js';
|
|
3
4
|
import type { WorkflowStatusEvent } from './protocol/schema.js';
|
|
4
|
-
import type {
|
|
5
|
+
import type { WorkflowStatusWatchInput, WorkflowStatusWatchResult } from './workflow-watch.js';
|
|
5
6
|
export type TapickerMcpApi = {
|
|
6
7
|
executeCommand: (name: string, request: unknown) => Promise<unknown>;
|
|
7
8
|
listExtensions: () => Promise<unknown[]>;
|
|
8
|
-
|
|
9
|
+
watchWorkflowStatus: (input: WorkflowStatusWatchInput, onStatus: (event: WorkflowStatusEvent) => void, signal?: AbortSignal) => Promise<WorkflowStatusWatchResult>;
|
|
9
10
|
};
|
|
10
|
-
export declare function createBridgeMcpApi(bridge: BridgeService): TapickerMcpApi;
|
|
11
|
+
export declare function createBridgeMcpApi(bridge: BridgeService, exports?: ExportStore): TapickerMcpApi;
|
|
11
12
|
export declare function createHttpMcpApi(baseUrl: string, token?: string): TapickerMcpApi;
|
|
12
13
|
export declare function createTapickerMcpServer(api: TapickerMcpApi): McpServer;
|
|
13
14
|
//# sourceMappingURL=mcp.d.ts.map
|