@lumenflow/surfaces 5.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 (36) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +40 -0
  3. package/cli/__tests__/gates.test.ts +97 -0
  4. package/cli/__tests__/inspect.test.ts +184 -0
  5. package/cli/__tests__/task-lifecycle.test.ts +203 -0
  6. package/cli/gates.ts +46 -0
  7. package/cli/index.ts +6 -0
  8. package/cli/inspect.ts +138 -0
  9. package/cli/task-lifecycle.ts +46 -0
  10. package/http/__tests__/agent-runtime-remote-controls.test.ts +249 -0
  11. package/http/__tests__/auth-boundary.test.ts +57 -0
  12. package/http/__tests__/channel-send-governance.test.ts +158 -0
  13. package/http/__tests__/event-stream.test.ts +340 -0
  14. package/http/__tests__/phone-device-tool-api.test.ts +177 -0
  15. package/http/__tests__/remote-exposure.test.ts +212 -0
  16. package/http/__tests__/run-agent.test.ts +447 -0
  17. package/http/__tests__/scope-enforcement.test.ts +349 -0
  18. package/http/__tests__/sidecar-entry.test.ts +158 -0
  19. package/http/__tests__/tool-api-schema-validation.test.ts +213 -0
  20. package/http/__tests__/tool-api.test.ts +491 -0
  21. package/http/__tests__/tool-discovery.test.ts +384 -0
  22. package/http/ag-ui-adapter.ts +352 -0
  23. package/http/auth.ts +294 -0
  24. package/http/control-plane-event-subscriber.ts +233 -0
  25. package/http/event-stream.ts +216 -0
  26. package/http/index.ts +10 -0
  27. package/http/run-agent.ts +416 -0
  28. package/http/server.ts +329 -0
  29. package/http/sidecar-entry.ts +218 -0
  30. package/http/task-api.ts +307 -0
  31. package/http/tool-api.ts +373 -0
  32. package/http/tool-discovery.ts +159 -0
  33. package/mcp/__tests__/server.test.ts +554 -0
  34. package/mcp/index.ts +4 -0
  35. package/mcp/server.ts +250 -0
  36. package/package.json +51 -0
package/mcp/server.ts ADDED
@@ -0,0 +1,250 @@
1
+ // Copyright (c) 2026 Hellmai Ltd
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import {
5
+ ExecutionContextSchema,
6
+ TaskSpecSchema,
7
+ toMcpJsonSchema,
8
+ type ClaimTaskInput,
9
+ type CompleteTaskInput,
10
+ type ExecutionContext,
11
+ type KernelRuntime,
12
+ type TaskInspection,
13
+ type TaskSpec,
14
+ } from '@lumenflow/kernel';
15
+
16
+ export interface McpInvocation {
17
+ name: string;
18
+ arguments?: unknown;
19
+ }
20
+
21
+ export interface McpToolDefinition {
22
+ name: string;
23
+ description: string;
24
+ input_schema: Record<string, unknown>;
25
+ }
26
+
27
+ export interface McpServer {
28
+ listTools(): McpToolDefinition[];
29
+ handleInvocation(invocation: McpInvocation, context?: ExecutionContext): Promise<unknown>;
30
+ }
31
+
32
+ const CLAIM_TASK_INPUT_SCHEMA: Record<string, unknown> = {
33
+ type: 'object',
34
+ properties: {
35
+ task_id: { type: 'string' },
36
+ by: { type: 'string' },
37
+ session_id: { type: 'string' },
38
+ timestamp: { type: 'string' },
39
+ domain_data: { type: 'object' },
40
+ },
41
+ required: ['task_id', 'by', 'session_id'],
42
+ };
43
+
44
+ const COMPLETE_TASK_INPUT_SCHEMA: Record<string, unknown> = {
45
+ type: 'object',
46
+ properties: {
47
+ task_id: { type: 'string' },
48
+ run_id: { type: 'string' },
49
+ timestamp: { type: 'string' },
50
+ evidence_refs: {
51
+ type: 'array',
52
+ items: { type: 'string' },
53
+ },
54
+ },
55
+ required: ['task_id'],
56
+ };
57
+
58
+ const EMPTY_OBJECT_INPUT_SCHEMA: Record<string, unknown> = {
59
+ type: 'object',
60
+ properties: {},
61
+ additionalProperties: false,
62
+ };
63
+
64
+ const PACK_SOURCE_VALUES = ['local', 'git', 'registry'] as const;
65
+
66
+ const PACK_INSTALL_INPUT_SCHEMA: Record<string, unknown> = {
67
+ type: 'object',
68
+ properties: {
69
+ id: { type: 'string' },
70
+ source: { type: 'string', enum: [...PACK_SOURCE_VALUES] },
71
+ version: { type: 'string' },
72
+ integrity: { type: 'string' },
73
+ url: { type: 'string' },
74
+ registry_url: { type: 'string' },
75
+ },
76
+ required: ['id', 'source', 'version'],
77
+ additionalProperties: false,
78
+ };
79
+
80
+ function requireObject(args: unknown, message: string): Record<string, unknown> {
81
+ if (!args || typeof args !== 'object' || Array.isArray(args)) {
82
+ throw new Error(message);
83
+ }
84
+ return args as Record<string, unknown>;
85
+ }
86
+
87
+ function parseRequiredString(value: unknown, message: string): string {
88
+ if (typeof value !== 'string' || value.trim().length === 0) {
89
+ throw new Error(message);
90
+ }
91
+ return value;
92
+ }
93
+
94
+ function parseOptionalString(value: unknown, message: string): string | undefined {
95
+ if (value === undefined) {
96
+ return undefined;
97
+ }
98
+ if (typeof value !== 'string' || value.trim().length === 0) {
99
+ throw new Error(message);
100
+ }
101
+ return value;
102
+ }
103
+
104
+ function parseOptionalDomainData(value: unknown): Record<string, unknown> | undefined {
105
+ if (value === undefined) {
106
+ return undefined;
107
+ }
108
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
109
+ throw new Error('task:claim requires domain_data to be an object when provided.');
110
+ }
111
+ return value as Record<string, unknown>;
112
+ }
113
+
114
+ function parseOptionalEvidenceRefs(value: unknown): string[] | undefined {
115
+ if (value === undefined) {
116
+ return undefined;
117
+ }
118
+ if (
119
+ !Array.isArray(value) ||
120
+ value.some((item) => typeof item !== 'string' || item.length === 0)
121
+ ) {
122
+ throw new Error('task:complete requires evidence_refs to be an array of non-empty strings.');
123
+ }
124
+ return value;
125
+ }
126
+
127
+ function parseTaskId(args: unknown): string {
128
+ if (typeof args === 'string' && args.trim().length > 0) {
129
+ return args;
130
+ }
131
+ if (!args || typeof args !== 'object') {
132
+ throw new Error('task:inspect expects task_id input.');
133
+ }
134
+ const taskId = (args as { task_id?: unknown }).task_id;
135
+ if (typeof taskId !== 'string' || taskId.trim().length === 0) {
136
+ throw new Error('task:inspect expects task_id input.');
137
+ }
138
+ return taskId;
139
+ }
140
+
141
+ function parseTaskSpec(args: unknown): TaskSpec {
142
+ return TaskSpecSchema.parse(args);
143
+ }
144
+
145
+ function parseClaimTaskInput(args: unknown): ClaimTaskInput {
146
+ const input = requireObject(args, 'task:claim expects object input.');
147
+ return {
148
+ task_id: parseRequiredString(input.task_id, 'task:claim requires task_id.'),
149
+ by: parseRequiredString(input.by, 'task:claim requires by.'),
150
+ session_id: parseRequiredString(input.session_id, 'task:claim requires session_id.'),
151
+ timestamp: parseOptionalString(
152
+ input.timestamp,
153
+ 'task:claim requires timestamp to be a string when provided.',
154
+ ),
155
+ domain_data: parseOptionalDomainData(input.domain_data),
156
+ };
157
+ }
158
+
159
+ function parseCompleteTaskInput(args: unknown): CompleteTaskInput {
160
+ const input = requireObject(args, 'task:complete expects object input.');
161
+ return {
162
+ task_id: parseRequiredString(input.task_id, 'task:complete requires task_id.'),
163
+ run_id: parseOptionalString(
164
+ input.run_id,
165
+ 'task:complete requires run_id to be a string when provided.',
166
+ ),
167
+ timestamp: parseOptionalString(
168
+ input.timestamp,
169
+ 'task:complete requires timestamp to be a string when provided.',
170
+ ),
171
+ evidence_refs: parseOptionalEvidenceRefs(input.evidence_refs),
172
+ };
173
+ }
174
+
175
+ function useCaseToolDefinitions(): McpToolDefinition[] {
176
+ return [
177
+ {
178
+ name: 'task:create',
179
+ description: 'Create a task using KernelRuntime.createTask.',
180
+ input_schema: toMcpJsonSchema(TaskSpecSchema),
181
+ },
182
+ {
183
+ name: 'task:claim',
184
+ description: 'Claim a task using KernelRuntime.claimTask.',
185
+ input_schema: CLAIM_TASK_INPUT_SCHEMA,
186
+ },
187
+ {
188
+ name: 'task:complete',
189
+ description: 'Complete a task using KernelRuntime.completeTask.',
190
+ input_schema: COMPLETE_TASK_INPUT_SCHEMA,
191
+ },
192
+ {
193
+ name: 'task:inspect',
194
+ description: 'Inspect a task using KernelRuntime.inspectTask.',
195
+ input_schema: toMcpJsonSchema(TaskSpecSchema.pick({ id: true })),
196
+ },
197
+ {
198
+ name: 'pack:list',
199
+ description: 'List currently loaded packs via runtime tool execution.',
200
+ input_schema: EMPTY_OBJECT_INPUT_SCHEMA,
201
+ },
202
+ {
203
+ name: 'pack:install',
204
+ description: 'Install a pack via runtime tool execution.',
205
+ input_schema: PACK_INSTALL_INPUT_SCHEMA,
206
+ },
207
+ {
208
+ name: 'workspace:info',
209
+ description: 'Inspect workspace metadata via runtime tool execution.',
210
+ input_schema: EMPTY_OBJECT_INPUT_SCHEMA,
211
+ },
212
+ ];
213
+ }
214
+
215
+ export function createMcpServer(runtime: KernelRuntime): McpServer {
216
+ return {
217
+ listTools(): McpToolDefinition[] {
218
+ return useCaseToolDefinitions();
219
+ },
220
+
221
+ async handleInvocation(
222
+ invocation: McpInvocation,
223
+ context?: ExecutionContext,
224
+ ): Promise<unknown> {
225
+ if (invocation.name === 'task:create') {
226
+ return runtime.createTask(parseTaskSpec(invocation.arguments));
227
+ }
228
+ if (invocation.name === 'task:claim') {
229
+ return runtime.claimTask(parseClaimTaskInput(invocation.arguments));
230
+ }
231
+ if (invocation.name === 'task:complete') {
232
+ return runtime.completeTask(parseCompleteTaskInput(invocation.arguments));
233
+ }
234
+ if (invocation.name === 'task:inspect' || invocation.name === 'task:status') {
235
+ return runtime.inspectTask(parseTaskId(invocation.arguments));
236
+ }
237
+
238
+ if (!context) {
239
+ throw new Error(`Tool invocation for ${invocation.name} requires execution context.`);
240
+ }
241
+
242
+ const validatedContext = ExecutionContextSchema.parse(context);
243
+ return runtime.executeTool(invocation.name, invocation.arguments, validatedContext);
244
+ },
245
+ };
246
+ }
247
+
248
+ export function asTaskInspection(result: unknown): TaskInspection {
249
+ return result as TaskInspection;
250
+ }
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@lumenflow/surfaces",
3
+ "version": "5.1.0",
4
+ "description": "LumenFlow kernel surfaces — HTTP, CLI, and MCP integration layers (Apache-2.0 cloud-facing boundary, ADR-011 §1)",
5
+ "keywords": [
6
+ "lumenflow",
7
+ "surfaces",
8
+ "http",
9
+ "cli",
10
+ "mcp",
11
+ "conductor"
12
+ ],
13
+ "homepage": "https://github.com/hellmai/lumenflow",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/hellmai/lumenflow.git",
17
+ "directory": "packages/@lumenflow/surfaces"
18
+ },
19
+ "license": "Apache-2.0",
20
+ "author": {
21
+ "name": "LumenFlow Contributors",
22
+ "url": "https://lumenflow.dev"
23
+ },
24
+ "type": "module",
25
+ "files": [
26
+ "http",
27
+ "cli",
28
+ "mcp",
29
+ "LICENSE",
30
+ "README.md"
31
+ ],
32
+ "dependencies": {
33
+ "@lumenflow/kernel": "5.1.0",
34
+ "@lumenflow/control-plane-sdk": "5.1.0"
35
+ },
36
+ "devDependencies": {
37
+ "typescript": "^5.9.3",
38
+ "@lumenflow/host": "5.1.0",
39
+ "@lumenflow/packs-agent-runtime": "5.1.0"
40
+ },
41
+ "engines": {
42
+ "node": ">=22"
43
+ },
44
+ "publishConfig": {
45
+ "access": "public"
46
+ },
47
+ "scripts": {
48
+ "lint": "eslint http cli mcp --quiet --rule no-console:off --rule @typescript-eslint/no-unused-vars:off",
49
+ "typecheck": "echo 'surfaces typecheck: deferred until cross-package import refactor (see README)'"
50
+ }
51
+ }