@portel/photon 1.23.1 → 1.25.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 (61) hide show
  1. package/README.md +66 -0
  2. package/dist/auto-ui/streamable-http-transport.d.ts.map +1 -1
  3. package/dist/auto-ui/streamable-http-transport.js +262 -18
  4. package/dist/auto-ui/streamable-http-transport.js.map +1 -1
  5. package/dist/beam.bundle.js +58287 -56177
  6. package/dist/beam.bundle.js.map +4 -4
  7. package/dist/capability-negotiator.d.ts +9 -0
  8. package/dist/capability-negotiator.d.ts.map +1 -1
  9. package/dist/capability-negotiator.js +14 -0
  10. package/dist/capability-negotiator.js.map +1 -1
  11. package/dist/cli/commands/claim.d.ts +17 -0
  12. package/dist/cli/commands/claim.d.ts.map +1 -0
  13. package/dist/cli/commands/claim.js +124 -0
  14. package/dist/cli/commands/claim.js.map +1 -0
  15. package/dist/cli/commands/run.d.ts.map +1 -1
  16. package/dist/cli/commands/run.js +2 -0
  17. package/dist/cli/commands/run.js.map +1 -1
  18. package/dist/cli/index.d.ts.map +1 -1
  19. package/dist/cli/index.js +2 -0
  20. package/dist/cli/index.js.map +1 -1
  21. package/dist/daemon/claims.d.ts +108 -0
  22. package/dist/daemon/claims.d.ts.map +1 -0
  23. package/dist/daemon/claims.js +245 -0
  24. package/dist/daemon/claims.js.map +1 -0
  25. package/dist/daemon/client.d.ts.map +1 -1
  26. package/dist/daemon/client.js +15 -29
  27. package/dist/daemon/client.js.map +1 -1
  28. package/dist/daemon/cron.d.ts +36 -0
  29. package/dist/daemon/cron.d.ts.map +1 -0
  30. package/dist/daemon/cron.js +216 -0
  31. package/dist/daemon/cron.js.map +1 -0
  32. package/dist/daemon/schedule-loader.d.ts +76 -0
  33. package/dist/daemon/schedule-loader.d.ts.map +1 -0
  34. package/dist/daemon/schedule-loader.js +124 -0
  35. package/dist/daemon/schedule-loader.js.map +1 -0
  36. package/dist/daemon/server.js +76 -226
  37. package/dist/daemon/server.js.map +1 -1
  38. package/dist/deploy/cloudflare.d.ts.map +1 -1
  39. package/dist/deploy/cloudflare.js +154 -4
  40. package/dist/deploy/cloudflare.js.map +1 -1
  41. package/dist/loader.d.ts +22 -1
  42. package/dist/loader.d.ts.map +1 -1
  43. package/dist/loader.js +246 -30
  44. package/dist/loader.js.map +1 -1
  45. package/dist/photon-cli-runner.d.ts.map +1 -1
  46. package/dist/photon-cli-runner.js +32 -2
  47. package/dist/photon-cli-runner.js.map +1 -1
  48. package/dist/server.d.ts +10 -0
  49. package/dist/server.d.ts.map +1 -1
  50. package/dist/server.js +50 -1
  51. package/dist/server.js.map +1 -1
  52. package/dist/shared/memory-sqlite.d.ts +37 -0
  53. package/dist/shared/memory-sqlite.d.ts.map +1 -0
  54. package/dist/shared/memory-sqlite.js +143 -0
  55. package/dist/shared/memory-sqlite.js.map +1 -0
  56. package/dist/telemetry/context.d.ts +9 -0
  57. package/dist/telemetry/context.d.ts.map +1 -1
  58. package/dist/telemetry/context.js.map +1 -1
  59. package/package.json +4 -4
  60. package/templates/cloudflare/worker.ts.template +53 -74
  61. package/templates/cloudflare/wrangler.toml.template +6 -1
@@ -16,6 +16,15 @@ export interface RequestContext {
16
16
  parentTraceparent?: string;
17
17
  /** Authenticated caller when available. */
18
18
  caller?: CallerInfo;
19
+ /**
20
+ * Originating CLI invocation directory, propagated end-to-end across
21
+ * worker thread and cross-photon-call boundaries. Lets photons resolve
22
+ * defaults relative to where the user ran the command, not the daemon's
23
+ * cwd. `process.cwd()` inside a worker is the daemon process's cwd, which
24
+ * is rarely what the photon author wants. Photons read this back via
25
+ * `this.callerCwd`.
26
+ */
27
+ cwd?: string;
19
28
  /** Wall-clock start of the tool call. */
20
29
  startedAt: number;
21
30
  }
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/telemetry/context.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEtD,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8EAA8E;IAC9E,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,2CAA2C;IAC3C,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,yCAAyC;IACzC,SAAS,EAAE,MAAM,CAAC;CACnB;AAID,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,GAAG,EAAE,cAAc,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAE5E;AAED,wBAAgB,iBAAiB,IAAI,cAAc,GAAG,SAAS,CAE9D"}
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/telemetry/context.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEtD,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8EAA8E;IAC9E,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,2CAA2C;IAC3C,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB;;;;;;;OAOG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,yCAAyC;IACzC,SAAS,EAAE,MAAM,CAAC;CACnB;AAID,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,GAAG,EAAE,cAAc,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAE5E;AAED,wBAAgB,iBAAiB,IAAI,cAAc,GAAG,SAAS,CAE9D"}
@@ -1 +1 @@
1
- {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/telemetry/context.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAgBrD,MAAM,OAAO,GAAG,IAAI,iBAAiB,EAAkB,CAAC;AAExD,MAAM,UAAU,qBAAqB,CAAI,GAAmB,EAAE,EAAW;IACvE,OAAO,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,OAAO,OAAO,CAAC,QAAQ,EAAE,CAAC;AAC5B,CAAC"}
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/telemetry/context.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAyBrD,MAAM,OAAO,GAAG,IAAI,iBAAiB,EAAkB,CAAC;AAExD,MAAM,UAAU,qBAAqB,CAAI,GAAmB,EAAE,EAAW;IACvE,OAAO,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,OAAO,OAAO,CAAC,QAAQ,EAAE,CAAC;AAC5B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portel/photon",
3
- "version": "1.23.1",
3
+ "version": "1.25.0",
4
4
  "description": "You focus on the business logic. We'll enable the rest. Build MCP servers and CLI tools in a single TypeScript file.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -113,7 +113,7 @@
113
113
  "@modelcontextprotocol/ext-apps": "^1.0.1",
114
114
  "@modelcontextprotocol/sdk": "^1.25.2",
115
115
  "@portel/cli": "^1.1.0",
116
- "@portel/photon-core": "^2.24.0",
116
+ "@portel/photon-core": "^2.25.0",
117
117
  "boxen": "^8.0.1",
118
118
  "chalk": "^5.4.1",
119
119
  "chart.js": "^4.5.1",
@@ -141,7 +141,7 @@
141
141
  "@codemirror/theme-one-dark": "^6.1.3",
142
142
  "@codemirror/view": "^6.39.12",
143
143
  "@playwright/test": "^1.58.0",
144
- "@release-it/conventional-changelog": "^10.0.1",
144
+ "@release-it/conventional-changelog": "^11.0.0",
145
145
  "@types/inquirer": "^9.0.9",
146
146
  "@types/node": "^22.10.2",
147
147
  "@types/qrcode": "^1.5.6",
@@ -157,7 +157,7 @@
157
157
  "openapi-types": "^12.1.3",
158
158
  "playwright": "^1.57.0",
159
159
  "prettier": "^3.8.0",
160
- "release-it": "^19.0.6",
160
+ "release-it": "^20.0.0",
161
161
  "tsx": "^4.20.6",
162
162
  "typescript": "^5.7.3"
163
163
  },
@@ -11,13 +11,6 @@ interface Env {
11
11
  [key: string]: string | undefined;
12
12
  }
13
13
 
14
- type SSESession = {
15
- writer: WritableStreamDefaultWriter<Uint8Array>;
16
- encoder: TextEncoder;
17
- };
18
-
19
- const sessions = new Map<string, SSESession>();
20
-
21
14
  // Tool definitions extracted at build time
22
15
  // @ts-ignore - Will be replaced during build
23
16
  const TOOL_DEFINITIONS: any[] = __TOOL_DEFINITIONS__;
@@ -67,11 +60,19 @@ async function handleMCPRequest(request: any, env: Env): Promise<any> {
67
60
  throw new Error(`Unknown tool: ${name}`);
68
61
  }
69
62
  const result = await method.call(photon, args || {});
63
+ // String returns (e.g. markdown from `read`) flow through as-is so
64
+ // clients receive renderable text. Other shapes are JSON-stringified
65
+ // for legibility. Structured shapes are also surfaced via
66
+ // `structuredContent` per MCP — clients that prefer JSON pick it up.
67
+ const isString = typeof result === 'string';
68
+ const text = isString ? result : JSON.stringify(result, null, 2);
69
+ const isObject = result !== null && typeof result === 'object';
70
70
  return {
71
71
  jsonrpc: '2.0',
72
72
  id,
73
73
  result: {
74
- content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
74
+ content: [{ type: 'text', text }],
75
+ ...(isObject && { structuredContent: result }),
75
76
  },
76
77
  };
77
78
  } catch (error: any) {
@@ -100,66 +101,34 @@ async function handleMCPRequest(request: any, env: Env): Promise<any> {
100
101
  }
101
102
 
102
103
  // SSE stream handler
103
- async function handleSSE(request: Request, env: Env): Promise<Response> {
104
- const sessionId = crypto.randomUUID();
105
- const encoder = new TextEncoder();
106
-
107
- const { readable, writable } = new TransformStream();
108
- const writer = writable.getWriter();
109
-
110
- sessions.set(sessionId, { writer, encoder });
111
-
112
- // Send session ID
113
- await writer.write(encoder.encode(`event: session\ndata: ${JSON.stringify({ sessionId })}\n\n`));
114
-
115
- // Keep-alive
116
- const interval = setInterval(async () => {
117
- try {
118
- await writer.write(encoder.encode(`: keepalive\n\n`));
119
- } catch {
120
- clearInterval(interval);
121
- }
122
- }, 30000);
123
-
124
- // Cleanup on close
125
- request.signal.addEventListener('abort', () => {
126
- clearInterval(interval);
127
- sessions.delete(sessionId);
128
- writer.close().catch(() => {});
129
- });
130
-
131
- return new Response(readable, {
132
- headers: {
133
- 'Content-Type': 'text/event-stream',
134
- 'Cache-Control': 'no-cache',
135
- 'Connection': 'keep-alive',
136
- 'Access-Control-Allow-Origin': '*',
137
- },
138
- });
139
- }
140
-
141
- // Message handler for SSE
142
- async function handleMessage(request: Request, env: Env): Promise<Response> {
143
- const url = new URL(request.url);
144
- const sessionId = url.searchParams.get('sessionId');
145
-
146
- if (!sessionId || !sessions.has(sessionId)) {
147
- return new Response('Invalid session', { status: 400 });
104
+ /**
105
+ * Stateless MCP Streamable HTTP handler.
106
+ *
107
+ * Each POST /mcp is a complete JSON-RPC exchange: body in, result out, no
108
+ * session state carried across requests. This matches the MCP Streamable
109
+ * HTTP spec's single-endpoint pattern and stays within the Cloudflare
110
+ * Workers free tier (no Durable Objects needed for session storage).
111
+ */
112
+ async function handleStreamableMCP(request: Request, env: Env): Promise<Response> {
113
+ let body: unknown;
114
+ try {
115
+ body = await request.json();
116
+ } catch (error: any) {
117
+ return Response.json(
118
+ { jsonrpc: '2.0', id: null, error: { code: -32700, message: `Parse error: ${error?.message ?? String(error)}` } },
119
+ { status: 400, headers: CORS_HEADERS }
120
+ );
148
121
  }
149
-
150
- const session = sessions.get(sessionId)!;
151
- const body = await request.json();
152
- const response = await handleMCPRequest(body, env);
153
-
154
- // Send response via SSE
155
- const data = `event: message\ndata: ${JSON.stringify(response)}\n\n`;
156
- await session.writer.write(session.encoder.encode(data));
157
-
158
- return new Response('OK', {
159
- headers: { 'Access-Control-Allow-Origin': '*' },
160
- });
122
+ const result = await handleMCPRequest(body, env);
123
+ return Response.json(result, { headers: CORS_HEADERS });
161
124
  }
162
125
 
126
+ const CORS_HEADERS = {
127
+ 'Access-Control-Allow-Origin': '*',
128
+ 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
129
+ 'Access-Control-Allow-Headers': 'Content-Type, Mcp-Session-Id',
130
+ };
131
+
163
132
  // Playground HTML
164
133
  function getPlaygroundHTML(): string {
165
134
  return `<!DOCTYPE html>
@@ -324,25 +293,35 @@ export default {
324
293
  if (url.pathname === '/' && request.method === 'GET') {
325
294
  return Response.json({
326
295
  name: PHOTON_NAME,
327
- transport: 'sse',
296
+ transport: 'streamable-http',
328
297
  runtime: 'cloudflare-workers',
329
298
  endpoints: {
330
- sse: '/mcp',
331
- messages: '/mcp/messages',
299
+ mcp: '/mcp',
332
300
  ...(DEV_MODE ? { playground: '/playground' } : {}),
333
301
  },
334
302
  tools: TOOL_DEFINITIONS.length,
335
303
  });
336
304
  }
337
305
 
338
- // SSE endpoint
306
+ // MCP Streamable HTTP: one endpoint, stateless per request.
307
+ if (url.pathname === '/mcp' && request.method === 'POST') {
308
+ return handleStreamableMCP(request, env);
309
+ }
339
310
  if (url.pathname === '/mcp' && request.method === 'GET') {
340
- return handleSSE(request, env);
311
+ // Spec-compliant no-op: clients that open an SSE stream for server
312
+ // notifications get a valid empty stream. We do not push anything
313
+ // server-initiated from a stateless Worker.
314
+ return new Response(': streamable-http\n\n', {
315
+ headers: {
316
+ 'Content-Type': 'text/event-stream',
317
+ 'Cache-Control': 'no-cache',
318
+ ...CORS_HEADERS,
319
+ },
320
+ });
341
321
  }
342
-
343
- // Message endpoint
344
- if (url.pathname === '/mcp/messages' && request.method === 'POST') {
345
- return handleMessage(request, env);
322
+ if (url.pathname === '/mcp' && request.method === 'DELETE') {
323
+ // Session termination: stateless, nothing to clean up.
324
+ return new Response(null, { status: 204, headers: CORS_HEADERS });
346
325
  }
347
326
 
348
327
  // Dev-only endpoints
@@ -1,6 +1,11 @@
1
1
  name = "__PHOTON_NAME__"
2
2
  main = "src/worker.ts"
3
- compatibility_date = "2024-01-01"
3
+ # 2024-09-23+ enables the Node.js-style module resolution path so packages
4
+ # that ship distinct browser/node entries (e.g. linkedom inside
5
+ # @extractus/article-extractor) resolve to the Node build instead of a
6
+ # browser build that references `window`.
7
+ compatibility_date = "2024-09-23"
8
+ compatibility_flags = ["nodejs_compat"]
4
9
 
5
10
  # Uncomment to add environment variables
6
11
  # [vars]