@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.
Files changed (49) hide show
  1. package/README.md +15 -15
  2. package/dist/bridge/bridge-service.d.ts +1 -1
  3. package/dist/bridge/bridge-service.js +7 -7
  4. package/dist/bridge/control.js +1 -1
  5. package/dist/bridge/export-store.d.ts +35 -0
  6. package/dist/bridge/export-store.js +195 -0
  7. package/dist/bridge/extension-session.d.ts +9 -2
  8. package/dist/bridge/extension-session.js +56 -8
  9. package/dist/cli.js +563 -148
  10. package/dist/commands.d.ts +8 -0
  11. package/dist/commands.js +276 -40
  12. package/dist/constants.d.ts +2 -2
  13. package/dist/constants.js +4 -2
  14. package/dist/http-client.d.ts +24 -0
  15. package/dist/http-client.js +101 -0
  16. package/dist/index.d.ts +4 -4
  17. package/dist/index.js +2 -2
  18. package/dist/mcp.d.ts +4 -3
  19. package/dist/mcp.js +221 -134
  20. package/dist/protocol/schema.d.ts +503 -34
  21. package/dist/protocol/schema.js +396 -15
  22. package/dist/server.d.ts +7 -4
  23. package/dist/server.js +70 -26
  24. package/dist/update-notifier-worker.d.ts +2 -0
  25. package/dist/update-notifier-worker.js +3 -0
  26. package/dist/update-notifier.d.ts +9 -0
  27. package/dist/update-notifier.js +178 -0
  28. package/dist/workflow-watch.d.ts +8 -7
  29. package/dist/workflow-watch.js +39 -29
  30. package/package.json +16 -6
  31. package/docs/cli.md +0 -112
  32. package/docs/http-api.md +0 -149
  33. package/docs/mcp.md +0 -90
  34. package/docs/operations.md +0 -85
  35. package/docs/workflow-watch.md +0 -86
  36. package/src/bridge/bridge-service.ts +0 -291
  37. package/src/bridge/control.ts +0 -281
  38. package/src/bridge/daemon.ts +0 -102
  39. package/src/bridge/extension-session.ts +0 -286
  40. package/src/bridge/pairing-state.ts +0 -92
  41. package/src/cli.ts +0 -1019
  42. package/src/commands.ts +0 -158
  43. package/src/constants.ts +0 -10
  44. package/src/errors.ts +0 -72
  45. package/src/index.ts +0 -35
  46. package/src/mcp.ts +0 -386
  47. package/src/protocol/schema.ts +0 -202
  48. package/src/server.ts +0 -623
  49. package/src/workflow-watch.ts +0 -364
@@ -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 { DEFAULT_WORKFLOW_WATCH_WS_PATH } from './constants.js';
2
+ import { DEFAULT_WORKFLOW_STATUS_WATCH_WS_PATH } from './constants.js';
3
3
  import { BridgeError } from './errors.js';
4
- import { WorkflowControlInputSchema, DataInputSchema, WorkflowStartInputSchema, WorkflowStartResultSchema, WorkflowStatusDataSchema, WorkflowWatchRequestSchema, } from './protocol/schema.js';
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.start': {
13
- description: 'Start one Workflow in a selected connected Tapicker Extension',
14
- schema: WorkflowStartInputSchema,
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 { clientId: selectedClientId, result } = await context.bridge.sendCommandWithClientId('workflow.start', input, clientId);
17
- if (!result || typeof result !== 'object' || Array.isArray(result)) {
18
- throw new BridgeError('InvalidExtensionResponse', 'Extension returned an invalid workflow.start response', {
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
- const parsed = WorkflowStartResultSchema.safeParse({ ...result, clientId: selectedClientId });
23
- if (!parsed.success || parsed.data.workflowId !== input.workflowId) {
24
- throw new BridgeError('InvalidExtensionResponse', 'Extension returned an invalid workflow.start response', {
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 parsed.data;
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
- execute: (input, context, clientId) => context.bridge.sendCommand('workflow.pause', input, clientId),
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
- execute: (input, context, clientId) => context.bridge.sendCommand('workflow.resume', input, clientId),
120
+ resultSchema: WorkflowControlResultSchema,
121
+ execute: (input, context, clientId) => controlWorkflow('resume', input, context, clientId),
46
122
  },
47
123
  'workflow.stop': {
48
- description: 'Stop a running Workflow in a selected connected Tapicker Extension',
124
+ description: 'Request a stop for the active Workflow instance',
49
125
  schema: WorkflowControlInputSchema,
50
- execute: (input, context, clientId) => context.bridge.sendCommand('workflow.stop', input, clientId),
126
+ resultSchema: WorkflowControlResultSchema,
127
+ execute: (input, context, clientId) => controlWorkflow('stop', input, context, clientId),
51
128
  },
52
129
  'workflow.status': {
53
- description: 'Get the current or most recently persisted Workflow status',
54
- schema: WorkflowControlInputSchema,
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 context.bridge.sendCommand('workflow.status.get', input, clientId);
57
- const parsed = WorkflowStatusDataSchema.safeParse(result);
58
- if (!parsed.success || parsed.data.workflowId !== input.workflowId) {
59
- throw new BridgeError('InvalidExtensionResponse', 'Extension returned an invalid workflow status response', {
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 parsed.data;
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: 'Get an extracted data table by tableId; the rows count against the Extension export quota',
69
- schema: DataInputSchema,
70
- execute: (input, context, clientId) => context.bridge.sendCommand('data.get', input, clientId),
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
- execute: (input, context, clientId) => context.bridge.sendCommand('data.delete', input, clientId),
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: DEFAULT_WORKFLOW_WATCH_WS_PATH,
119
- inputSchema: z.toJSONSchema(WorkflowWatchRequestSchema, { io: 'input' }),
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
  }
@@ -1,10 +1,10 @@
1
- export declare const VERSION = "1.0.0";
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 DEFAULT_WORKFLOW_WATCH_WS_PATH = "/v1/commands/workflow.watch";
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
- export const VERSION = '1.0.0';
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 DEFAULT_WORKFLOW_WATCH_WS_PATH = '/v1/commands/workflow.watch';
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, TabTargetSchema, WorkflowControlInputSchema, WorkflowLifecycleStateSchema, WorkflowStatusEventSchema, WorkflowStatusDataSchema, WorkflowStartInputSchema, WorkflowStartResultSchema, WorkflowWatchRequestSchema, createEnvelope, parseEnvelope, } from './protocol/schema.js';
7
- export type { DataInput, WorkflowControlInput, WorkflowStartInput, WorkflowStartResult, WorkflowStatusData, WorkflowStatusEvent, WorkflowWatchRequest, } from './protocol/schema.js';
8
- export { connectWorkflowWatch, waitForWorkflowStatus, workflowWatchWebSocketUrl } from './workflow-watch.js';
9
- export type { WorkflowWatchConnection, WorkflowWatchInput, WorkflowWatchResult } from './workflow-watch.js';
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, TabTargetSchema, WorkflowControlInputSchema, WorkflowLifecycleStateSchema, WorkflowStatusEventSchema, WorkflowStatusDataSchema, WorkflowStartInputSchema, WorkflowStartResultSchema, WorkflowWatchRequestSchema, createEnvelope, parseEnvelope, } from './protocol/schema.js';
7
- export { connectWorkflowWatch, waitForWorkflowStatus, workflowWatchWebSocketUrl } from './workflow-watch.js';
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 { WorkflowWatchInput, WorkflowWatchResult } from './workflow-watch.js';
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
- watchWorkflow: (input: WorkflowWatchInput, onStatus: (event: WorkflowStatusEvent) => void, signal?: AbortSignal) => Promise<WorkflowWatchResult>;
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