@portel/photon 1.19.0 → 1.20.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 (92) hide show
  1. package/dist/auto-ui/beam/routes/api-browse.d.ts.map +1 -1
  2. package/dist/auto-ui/beam/routes/api-browse.js +16 -4
  3. package/dist/auto-ui/beam/routes/api-browse.js.map +1 -1
  4. package/dist/auto-ui/beam/routes/api-config.js +4 -4
  5. package/dist/auto-ui/beam/routes/api-config.js.map +1 -1
  6. package/dist/auto-ui/beam/routes/api-marketplace.d.ts.map +1 -1
  7. package/dist/auto-ui/beam/routes/api-marketplace.js +14 -1
  8. package/dist/auto-ui/beam/routes/api-marketplace.js.map +1 -1
  9. package/dist/auto-ui/beam.d.ts.map +1 -1
  10. package/dist/auto-ui/beam.js +183 -74
  11. package/dist/auto-ui/beam.js.map +1 -1
  12. package/dist/auto-ui/bridge/index.d.ts.map +1 -1
  13. package/dist/auto-ui/bridge/index.js +17 -0
  14. package/dist/auto-ui/bridge/index.js.map +1 -1
  15. package/dist/auto-ui/streamable-http-transport.d.ts +1 -0
  16. package/dist/auto-ui/streamable-http-transport.d.ts.map +1 -1
  17. package/dist/auto-ui/streamable-http-transport.js +64 -16
  18. package/dist/auto-ui/streamable-http-transport.js.map +1 -1
  19. package/dist/auto-ui/types.d.ts +12 -0
  20. package/dist/auto-ui/types.d.ts.map +1 -1
  21. package/dist/auto-ui/types.js.map +1 -1
  22. package/dist/beam-form.bundle.js +44 -3
  23. package/dist/beam-form.bundle.js.map +2 -2
  24. package/dist/beam.bundle.js +1404 -482
  25. package/dist/beam.bundle.js.map +4 -4
  26. package/dist/capability-negotiator.d.ts +67 -0
  27. package/dist/capability-negotiator.d.ts.map +1 -0
  28. package/dist/capability-negotiator.js +104 -0
  29. package/dist/capability-negotiator.js.map +1 -0
  30. package/dist/channel-manager.d.ts +122 -0
  31. package/dist/channel-manager.d.ts.map +1 -0
  32. package/dist/channel-manager.js +266 -0
  33. package/dist/channel-manager.js.map +1 -0
  34. package/dist/cli/commands/package.d.ts.map +1 -1
  35. package/dist/cli/commands/package.js +25 -7
  36. package/dist/cli/commands/package.js.map +1 -1
  37. package/dist/daemon/client.d.ts.map +1 -1
  38. package/dist/daemon/client.js +12 -0
  39. package/dist/daemon/client.js.map +1 -1
  40. package/dist/daemon/server.js +30 -49
  41. package/dist/daemon/server.js.map +1 -1
  42. package/dist/daemon/worker-manager.d.ts.map +1 -1
  43. package/dist/daemon/worker-manager.js +21 -7
  44. package/dist/daemon/worker-manager.js.map +1 -1
  45. package/dist/loader.d.ts +4 -1
  46. package/dist/loader.d.ts.map +1 -1
  47. package/dist/loader.js +73 -11
  48. package/dist/loader.js.map +1 -1
  49. package/dist/marketplace-manager.d.ts +6 -0
  50. package/dist/marketplace-manager.d.ts.map +1 -1
  51. package/dist/marketplace-manager.js +161 -58
  52. package/dist/marketplace-manager.js.map +1 -1
  53. package/dist/namespace-migration.d.ts +1 -0
  54. package/dist/namespace-migration.d.ts.map +1 -1
  55. package/dist/namespace-migration.js +86 -0
  56. package/dist/namespace-migration.js.map +1 -1
  57. package/dist/resource-server.d.ts +105 -0
  58. package/dist/resource-server.d.ts.map +1 -0
  59. package/dist/resource-server.js +723 -0
  60. package/dist/resource-server.js.map +1 -0
  61. package/dist/serv/auth/jwt.d.ts +2 -0
  62. package/dist/serv/auth/jwt.d.ts.map +1 -1
  63. package/dist/serv/auth/jwt.js +11 -5
  64. package/dist/serv/auth/jwt.js.map +1 -1
  65. package/dist/serv/vault/token-vault.d.ts +2 -0
  66. package/dist/serv/vault/token-vault.d.ts.map +1 -1
  67. package/dist/serv/vault/token-vault.js +6 -0
  68. package/dist/serv/vault/token-vault.js.map +1 -1
  69. package/dist/server.d.ts +20 -149
  70. package/dist/server.d.ts.map +1 -1
  71. package/dist/server.js +232 -1217
  72. package/dist/server.js.map +1 -1
  73. package/dist/shared/audit.d.ts.map +1 -1
  74. package/dist/shared/audit.js +7 -0
  75. package/dist/shared/audit.js.map +1 -1
  76. package/dist/shared/security.d.ts +10 -0
  77. package/dist/shared/security.d.ts.map +1 -1
  78. package/dist/shared/security.js +27 -0
  79. package/dist/shared/security.js.map +1 -1
  80. package/dist/task-executor.d.ts +69 -0
  81. package/dist/task-executor.d.ts.map +1 -0
  82. package/dist/task-executor.js +182 -0
  83. package/dist/task-executor.js.map +1 -0
  84. package/dist/types/photon-instance.d.ts +50 -0
  85. package/dist/types/photon-instance.d.ts.map +1 -0
  86. package/dist/types/photon-instance.js +9 -0
  87. package/dist/types/photon-instance.js.map +1 -0
  88. package/dist/types/server-types.d.ts +61 -0
  89. package/dist/types/server-types.d.ts.map +1 -0
  90. package/dist/types/server-types.js +8 -0
  91. package/dist/types/server-types.js.map +1 -0
  92. package/package.json +2 -2
@@ -0,0 +1,67 @@
1
+ /**
2
+ * CapabilityNegotiator — client capability detection and negotiation
3
+ *
4
+ * Encapsulates the logic for detecting what an MCP client supports
5
+ * (UI rendering, elicitation, etc.) based on the initialize handshake.
6
+ *
7
+ * The MCP SDK's Zod schema strips unknown fields like `extensions`
8
+ * (protocol 2025-11-25+), so we also capture raw capabilities from
9
+ * the JSON-RPC initialize message before Zod parsing occurs.
10
+ */
11
+ import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
12
+ export declare class CapabilityNegotiator {
13
+ /**
14
+ * Raw client capabilities captured from the initialize request BEFORE Zod parsing.
15
+ *
16
+ * The MCP SDK uses Zod to validate incoming requests, which strips unknown fields
17
+ * from ClientCapabilities. Notably, `extensions` (protocol 2025-11-25+) is not in
18
+ * the SDK's Zod schema yet, so `getClientCapabilities()` returns an object without
19
+ * it. Real clients like Claude Desktop and ChatGPT send UI capability under
20
+ * `extensions`, not `experimental`. We intercept the raw JSON-RPC message to
21
+ * capture the full capabilities before Zod strips them.
22
+ *
23
+ * Key: Server instance → Value: raw capabilities object from initialize request
24
+ */
25
+ private rawClientCapabilities;
26
+ /**
27
+ * Store raw capabilities for a server instance.
28
+ * Called from the transport message interceptor.
29
+ */
30
+ setRawCapabilities(server: Server, capabilities: Record<string, any>): void;
31
+ /**
32
+ * Check if client supports MCP Apps UI (structuredContent + _meta.ui)
33
+ *
34
+ * Looks for the "io.modelcontextprotocol/ui" capability in the client's
35
+ * initialize handshake. Any MCP client that advertises this capability
36
+ * gets rich UI responses — Claude Desktop, ChatGPT, MCPJam, etc.
37
+ *
38
+ * The capability may appear under `experimental` (older SDK types) or
39
+ * `extensions` (protocol version 2025-11-25+). We check both so it
40
+ * just works regardless of which field the client uses.
41
+ *
42
+ * Beam is special-cased because it's our own SSE transport where the
43
+ * capability is implicit.
44
+ */
45
+ supportsUI(server: Server): boolean;
46
+ /**
47
+ * Check if client supports elicitation
48
+ *
49
+ * Elicitation is a client capability declared during initialization.
50
+ * The server can use elicitInput() when the client supports it.
51
+ */
52
+ supportsElicitation(server: Server): boolean;
53
+ /**
54
+ * Intercept a transport to capture raw client capabilities before Zod strips them.
55
+ *
56
+ * The MCP SDK's Zod schema for ClientCapabilities doesn't include `extensions`
57
+ * (protocol 2025-11-25+), so getClientCapabilities() returns an object without it.
58
+ * We intercept the transport's onmessage to capture the raw `initialize` request
59
+ * and store capabilities before Zod parsing occurs.
60
+ *
61
+ * @param onMessage Optional additional message interceptor (e.g. for channel permissions)
62
+ */
63
+ interceptTransportForRawCapabilities(transport: {
64
+ onmessage?: (...args: any[]) => void;
65
+ }, targetServer: Server, onMessage?: (message: any) => void): void;
66
+ }
67
+ //# sourceMappingURL=capability-negotiator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capability-negotiator.d.ts","sourceRoot":"","sources":["../src/capability-negotiator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAIxE,qBAAa,oBAAoB;IAC/B;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,qBAAqB,CAA8C;IAE3E;;;OAGG;IACH,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI;IAI3E;;;;;;;;;;;;;OAaG;IACH,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAqBnC;;;;;OAKG;IACH,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAW5C;;;;;;;;;OASG;IACH,oCAAoC,CAClC,SAAS,EAAE;QAAE,SAAS,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;KAAE,EACnD,YAAY,EAAE,MAAM,EACpB,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,IAAI,GACjC,IAAI;CAcR"}
@@ -0,0 +1,104 @@
1
+ /**
2
+ * CapabilityNegotiator — client capability detection and negotiation
3
+ *
4
+ * Encapsulates the logic for detecting what an MCP client supports
5
+ * (UI rendering, elicitation, etc.) based on the initialize handshake.
6
+ *
7
+ * The MCP SDK's Zod schema strips unknown fields like `extensions`
8
+ * (protocol 2025-11-25+), so we also capture raw capabilities from
9
+ * the JSON-RPC initialize message before Zod parsing occurs.
10
+ */
11
+ const MCP_UI_CAPABILITY = 'io.modelcontextprotocol/ui';
12
+ export class CapabilityNegotiator {
13
+ /**
14
+ * Raw client capabilities captured from the initialize request BEFORE Zod parsing.
15
+ *
16
+ * The MCP SDK uses Zod to validate incoming requests, which strips unknown fields
17
+ * from ClientCapabilities. Notably, `extensions` (protocol 2025-11-25+) is not in
18
+ * the SDK's Zod schema yet, so `getClientCapabilities()` returns an object without
19
+ * it. Real clients like Claude Desktop and ChatGPT send UI capability under
20
+ * `extensions`, not `experimental`. We intercept the raw JSON-RPC message to
21
+ * capture the full capabilities before Zod strips them.
22
+ *
23
+ * Key: Server instance → Value: raw capabilities object from initialize request
24
+ */
25
+ rawClientCapabilities = new WeakMap();
26
+ /**
27
+ * Store raw capabilities for a server instance.
28
+ * Called from the transport message interceptor.
29
+ */
30
+ setRawCapabilities(server, capabilities) {
31
+ this.rawClientCapabilities.set(server, capabilities);
32
+ }
33
+ /**
34
+ * Check if client supports MCP Apps UI (structuredContent + _meta.ui)
35
+ *
36
+ * Looks for the "io.modelcontextprotocol/ui" capability in the client's
37
+ * initialize handshake. Any MCP client that advertises this capability
38
+ * gets rich UI responses — Claude Desktop, ChatGPT, MCPJam, etc.
39
+ *
40
+ * The capability may appear under `experimental` (older SDK types) or
41
+ * `extensions` (protocol version 2025-11-25+). We check both so it
42
+ * just works regardless of which field the client uses.
43
+ *
44
+ * Beam is special-cased because it's our own SSE transport where the
45
+ * capability is implicit.
46
+ */
47
+ supportsUI(server) {
48
+ // Check SDK-parsed capabilities (works for `experimental` which is in the Zod schema)
49
+ const capabilities = server.getClientCapabilities();
50
+ if (capabilities?.experimental?.[MCP_UI_CAPABILITY]) {
51
+ return true;
52
+ }
53
+ // Check raw capabilities captured before Zod parsing (needed for `extensions`
54
+ // which the SDK's Zod schema strips — Claude Desktop and ChatGPT use this field)
55
+ const raw = this.rawClientCapabilities.get(server);
56
+ if (raw?.extensions?.[MCP_UI_CAPABILITY]) {
57
+ return true;
58
+ }
59
+ // Beam is our own transport — UI support is implicit
60
+ const clientInfo = server.getClientVersion();
61
+ if (clientInfo?.name === 'beam')
62
+ return true;
63
+ return false;
64
+ }
65
+ /**
66
+ * Check if client supports elicitation
67
+ *
68
+ * Elicitation is a client capability declared during initialization.
69
+ * The server can use elicitInput() when the client supports it.
70
+ */
71
+ supportsElicitation(server) {
72
+ const capabilities = server.getClientCapabilities();
73
+ if (!capabilities) {
74
+ return false;
75
+ }
76
+ // Check for elicitation capability (MCP 2025-06 spec)
77
+ return !!capabilities.elicitation;
78
+ }
79
+ /**
80
+ * Intercept a transport to capture raw client capabilities before Zod strips them.
81
+ *
82
+ * The MCP SDK's Zod schema for ClientCapabilities doesn't include `extensions`
83
+ * (protocol 2025-11-25+), so getClientCapabilities() returns an object without it.
84
+ * We intercept the transport's onmessage to capture the raw `initialize` request
85
+ * and store capabilities before Zod parsing occurs.
86
+ *
87
+ * @param onMessage Optional additional message interceptor (e.g. for channel permissions)
88
+ */
89
+ interceptTransportForRawCapabilities(transport, targetServer, onMessage) {
90
+ const origOnMessage = transport.onmessage;
91
+ transport.onmessage = (message, extra) => {
92
+ // Capture raw capabilities from initialize request
93
+ if (message?.method === 'initialize' && message?.params) {
94
+ if (message.params.capabilities) {
95
+ this.rawClientCapabilities.set(targetServer, message.params.capabilities);
96
+ }
97
+ }
98
+ // Call additional interceptor if provided
99
+ onMessage?.(message);
100
+ origOnMessage?.(message, extra);
101
+ };
102
+ }
103
+ }
104
+ //# sourceMappingURL=capability-negotiator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capability-negotiator.js","sourceRoot":"","sources":["../src/capability-negotiator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,MAAM,iBAAiB,GAAG,4BAA4B,CAAC;AAEvD,MAAM,OAAO,oBAAoB;IAC/B;;;;;;;;;;;OAWG;IACK,qBAAqB,GAAG,IAAI,OAAO,EAA+B,CAAC;IAE3E;;;OAGG;IACH,kBAAkB,CAAC,MAAc,EAAE,YAAiC;QAClE,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACvD,CAAC;IAED;;;;;;;;;;;;;OAaG;IACH,UAAU,CAAC,MAAc;QACvB,sFAAsF;QACtF,MAAM,YAAY,GAAG,MAAM,CAAC,qBAAqB,EAAyB,CAAC;QAC3E,IAAI,YAAY,EAAE,YAAY,EAAE,CAAC,iBAAiB,CAAC,EAAE,CAAC;YACpD,OAAO,IAAI,CAAC;QACd,CAAC;QAED,8EAA8E;QAC9E,iFAAiF;QACjF,MAAM,GAAG,GAAG,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACnD,IAAI,GAAG,EAAE,UAAU,EAAE,CAAC,iBAAiB,CAAC,EAAE,CAAC;YACzC,OAAO,IAAI,CAAC;QACd,CAAC;QAED,qDAAqD;QACrD,MAAM,UAAU,GAAG,MAAM,CAAC,gBAAgB,EAAE,CAAC;QAC7C,IAAI,UAAU,EAAE,IAAI,KAAK,MAAM;YAAE,OAAO,IAAI,CAAC;QAE7C,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;;;;OAKG;IACH,mBAAmB,CAAC,MAAc;QAChC,MAAM,YAAY,GAAG,MAAM,CAAC,qBAAqB,EAAE,CAAC;QAEpD,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,OAAO,KAAK,CAAC;QACf,CAAC;QAED,sDAAsD;QACtD,OAAO,CAAC,CAAC,YAAY,CAAC,WAAW,CAAC;IACpC,CAAC;IAED;;;;;;;;;OASG;IACH,oCAAoC,CAClC,SAAmD,EACnD,YAAoB,EACpB,SAAkC;QAElC,MAAM,aAAa,GAAG,SAAS,CAAC,SAAS,CAAC;QAC1C,SAAS,CAAC,SAAS,GAAG,CAAC,OAAY,EAAE,KAAW,EAAE,EAAE;YAClD,mDAAmD;YACnD,IAAI,OAAO,EAAE,MAAM,KAAK,YAAY,IAAI,OAAO,EAAE,MAAM,EAAE,CAAC;gBACxD,IAAI,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC;oBAChC,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,YAAY,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;gBAC5E,CAAC;YACH,CAAC;YACD,0CAA0C;YAC1C,SAAS,EAAE,CAAC,OAAO,CAAC,CAAC;YACrB,aAAa,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QAClC,CAAC,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,122 @@
1
+ /**
2
+ * ChannelManager — encapsulates channel/pub-sub logic extracted from PhotonServer.
3
+ *
4
+ * Handles:
5
+ * - Channel capability declaration for MCP server init
6
+ * - Channel notification methods (per-target)
7
+ * - Permission request/response flow
8
+ * - Publishing channel events to daemon
9
+ * - Subscribing to daemon channels and forwarding messages to MCP clients
10
+ * - Cleanup of daemon subscriptions
11
+ */
12
+ export type ChannelPermissionRequest = {
13
+ request_id: string;
14
+ tool_name: string;
15
+ description: string;
16
+ input_preview: string;
17
+ };
18
+ export type ChannelPermissionResponse = {
19
+ request_id: string;
20
+ behavior: 'allow' | 'deny';
21
+ };
22
+ /** Channel-specific options extracted from PhotonServerOptions */
23
+ export interface ChannelOptions {
24
+ channelMode?: boolean;
25
+ channelName?: string;
26
+ channelTargets?: string[];
27
+ channelInstructions?: string;
28
+ }
29
+ /**
30
+ * Callback interface for sending notifications back to MCP clients.
31
+ * PhotonServer implements this so ChannelManager stays decoupled.
32
+ */
33
+ export interface ChannelNotificationSink {
34
+ /** Send a notification to the primary (STDIO) server */
35
+ sendNotification(notification: {
36
+ method: string;
37
+ params: any;
38
+ }): Promise<void>;
39
+ /** Send a notification to all SSE sessions */
40
+ sendNotificationToAllSessions(notification: {
41
+ method: string;
42
+ params: any;
43
+ }): Promise<void>;
44
+ /** Get the photon instance for permission dispatch */
45
+ getPhotonInstance(): any;
46
+ }
47
+ export declare class ChannelManager {
48
+ private channelUnsubscribers;
49
+ private daemonName;
50
+ private options;
51
+ private workingDir?;
52
+ private sink;
53
+ private log;
54
+ constructor(opts: {
55
+ channelOptions: ChannelOptions;
56
+ workingDir?: string;
57
+ sink: ChannelNotificationSink;
58
+ log: (level: string, message: string, data?: Record<string, unknown>) => void;
59
+ });
60
+ /** Whether channel mode is active */
61
+ get isChannelMode(): boolean;
62
+ /** The current daemon name (set after daemon startup) */
63
+ get currentDaemonName(): string | null;
64
+ /** Set the daemon name (called after daemon startup in PhotonServer) */
65
+ setDaemonName(name: string | null): void;
66
+ /**
67
+ * Returns the server name to use.
68
+ * In channel mode, uses the channel name; otherwise defaults to 'photon-mcp'.
69
+ */
70
+ getServerName(): string;
71
+ /**
72
+ * Returns extra capabilities to merge into the MCP Server capabilities object.
73
+ * Produces the experimental channel entries for each declared target.
74
+ */
75
+ getExtraCapabilities(): Record<string, any>;
76
+ /**
77
+ * Returns extra server constructor options (e.g. instructions) for channel mode.
78
+ */
79
+ getExtraServerOptions(): Record<string, any>;
80
+ /**
81
+ * Get the notification methods for all declared channel targets.
82
+ * Each target (e.g. 'claude') maps to `notifications/{target}/channel`.
83
+ */
84
+ private getChannelNotificationMethods;
85
+ /**
86
+ * Handle a permission request from the client (e.g. Claude Code asking "Allow tool X?").
87
+ * Forwards to the photon instance via channel._dispatchPermission().
88
+ */
89
+ handlePermissionRequest(params: any): void;
90
+ /**
91
+ * Send a permission response back to the client.
92
+ * Called by the photon instance (via this.channel.respond) when the user approves/denies.
93
+ */
94
+ respondToPermission(response: ChannelPermissionResponse): void;
95
+ /**
96
+ * Publish a channel event to the daemon for cross-process pub/sub.
97
+ * Called from output handlers whenever an emit has a `channel` field.
98
+ */
99
+ publishIfChannel(emit: any): void;
100
+ /**
101
+ * Subscribe to daemon channels for cross-process notifications.
102
+ * In channel mode, handleChannelMessage intercepts 'channel-push' events
103
+ * and translates them to notifications/claude/channel for the connected client.
104
+ */
105
+ subscribeToChannels(): Promise<void>;
106
+ /**
107
+ * Handle incoming channel messages and forward as MCP notifications.
108
+ * Routes channel-permission-response and channel-push messages,
109
+ * and forwards everything else as standard MCP notifications with _photon data.
110
+ */
111
+ private handleChannelMessage;
112
+ /**
113
+ * Check if an incoming message is a channel permission request and handle it.
114
+ * Returns true if it was handled (caller should not process further).
115
+ */
116
+ interceptPermissionRequest(message: any): boolean;
117
+ /**
118
+ * Unsubscribe from all daemon channels. Called during server shutdown.
119
+ */
120
+ cleanup(): void;
121
+ }
122
+ //# sourceMappingURL=channel-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel-manager.d.ts","sourceRoot":"","sources":["../src/channel-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAMH,MAAM,MAAM,wBAAwB,GAAG;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,OAAO,GAAG,MAAM,CAAC;CAC5B,CAAC;AAEF,kEAAkE;AAClE,MAAM,WAAW,cAAc;IAC7B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;GAGG;AACH,MAAM,WAAW,uBAAuB;IACtC,wDAAwD;IACxD,gBAAgB,CAAC,YAAY,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,GAAG,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/E,8CAA8C;IAC9C,6BAA6B,CAAC,YAAY,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,GAAG,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5F,sDAAsD;IACtD,iBAAiB,IAAI,GAAG,CAAC;CAC1B;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,oBAAoB,CAAyB;IACrD,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,UAAU,CAAC,CAAS;IAC5B,OAAO,CAAC,IAAI,CAA0B;IACtC,OAAO,CAAC,GAAG,CAA2E;gBAE1E,IAAI,EAAE;QAChB,cAAc,EAAE,cAAc,CAAC;QAC/B,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,IAAI,EAAE,uBAAuB,CAAC;QAC9B,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;KAC/E;IAOD,qCAAqC;IACrC,IAAI,aAAa,IAAI,OAAO,CAE3B;IAED,yDAAyD;IACzD,IAAI,iBAAiB,IAAI,MAAM,GAAG,IAAI,CAErC;IAED,wEAAwE;IACxE,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAQxC;;;OAGG;IACH,aAAa,IAAI,MAAM;IAMvB;;;OAGG;IACH,oBAAoB,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IAY3C;;OAEG;IACH,qBAAqB,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IAS5C;;;OAGG;IACH,OAAO,CAAC,6BAA6B;IASrC;;;OAGG;IACH,uBAAuB,CAAC,MAAM,EAAE,GAAG,GAAG,IAAI;IAe1C;;;OAGG;IACH,mBAAmB,CAAC,QAAQ,EAAE,yBAAyB,GAAG,IAAI;IAuB9D;;;OAGG;IACH,gBAAgB,CAAC,IAAI,EAAE,GAAG,GAAG,IAAI;IAWjC;;;;OAIG;IACG,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC;IAmB1C;;;;OAIG;YACW,oBAAoB;IA0ElC;;;OAGG;IACH,0BAA0B,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO;IAYjD;;OAEG;IACH,OAAO,IAAI,IAAI;CAUhB"}
@@ -0,0 +1,266 @@
1
+ /**
2
+ * ChannelManager — encapsulates channel/pub-sub logic extracted from PhotonServer.
3
+ *
4
+ * Handles:
5
+ * - Channel capability declaration for MCP server init
6
+ * - Channel notification methods (per-target)
7
+ * - Permission request/response flow
8
+ * - Publishing channel events to daemon
9
+ * - Subscribing to daemon channels and forwarding messages to MCP clients
10
+ * - Cleanup of daemon subscriptions
11
+ */
12
+ import { subscribeChannel, publishToChannel } from './daemon/client.js';
13
+ import { getErrorMessage } from './shared/error-handler.js';
14
+ export class ChannelManager {
15
+ channelUnsubscribers = [];
16
+ daemonName = null;
17
+ options;
18
+ workingDir;
19
+ sink;
20
+ log;
21
+ constructor(opts) {
22
+ this.options = opts.channelOptions;
23
+ this.workingDir = opts.workingDir;
24
+ this.sink = opts.sink;
25
+ this.log = opts.log;
26
+ }
27
+ /** Whether channel mode is active */
28
+ get isChannelMode() {
29
+ return !!this.options.channelMode;
30
+ }
31
+ /** The current daemon name (set after daemon startup) */
32
+ get currentDaemonName() {
33
+ return this.daemonName;
34
+ }
35
+ /** Set the daemon name (called after daemon startup in PhotonServer) */
36
+ setDaemonName(name) {
37
+ this.daemonName = name;
38
+ }
39
+ // ---------------------------------------------------------------------------
40
+ // MCP Server Init Helpers
41
+ // ---------------------------------------------------------------------------
42
+ /**
43
+ * Returns the server name to use.
44
+ * In channel mode, uses the channel name; otherwise defaults to 'photon-mcp'.
45
+ */
46
+ getServerName() {
47
+ return this.options.channelMode && this.options.channelName
48
+ ? this.options.channelName
49
+ : 'photon-mcp';
50
+ }
51
+ /**
52
+ * Returns extra capabilities to merge into the MCP Server capabilities object.
53
+ * Produces the experimental channel entries for each declared target.
54
+ */
55
+ getExtraCapabilities() {
56
+ if (!this.options.channelMode || !this.options.channelTargets?.length)
57
+ return {};
58
+ return {
59
+ experimental: Object.fromEntries(this.options.channelTargets.flatMap((t) => [
60
+ [`${t}/channel`, {}],
61
+ [`${t}/channel/permission`, {}],
62
+ ])),
63
+ };
64
+ }
65
+ /**
66
+ * Returns extra server constructor options (e.g. instructions) for channel mode.
67
+ */
68
+ getExtraServerOptions() {
69
+ if (!this.options.channelMode || !this.options.channelInstructions)
70
+ return {};
71
+ return { instructions: this.options.channelInstructions };
72
+ }
73
+ // ---------------------------------------------------------------------------
74
+ // Notification Methods
75
+ // ---------------------------------------------------------------------------
76
+ /**
77
+ * Get the notification methods for all declared channel targets.
78
+ * Each target (e.g. 'claude') maps to `notifications/{target}/channel`.
79
+ */
80
+ getChannelNotificationMethods() {
81
+ const targets = this.options.channelTargets || [];
82
+ return targets.map((t) => `notifications/${t}/channel`);
83
+ }
84
+ // ---------------------------------------------------------------------------
85
+ // Permission Flow
86
+ // ---------------------------------------------------------------------------
87
+ /**
88
+ * Handle a permission request from the client (e.g. Claude Code asking "Allow tool X?").
89
+ * Forwards to the photon instance via channel._dispatchPermission().
90
+ */
91
+ handlePermissionRequest(params) {
92
+ if (!params?.request_id || !params?.tool_name)
93
+ return;
94
+ const request = {
95
+ request_id: params.request_id,
96
+ tool_name: params.tool_name,
97
+ description: params.description || '',
98
+ input_preview: params.input_preview || '',
99
+ };
100
+ this.log('info', `Permission request: ${request.tool_name} (${request.request_id})`);
101
+ const instance = this.sink.getPhotonInstance();
102
+ if (instance?.channel?._dispatchPermission) {
103
+ instance.channel._dispatchPermission(request);
104
+ }
105
+ }
106
+ /**
107
+ * Send a permission response back to the client.
108
+ * Called by the photon instance (via this.channel.respond) when the user approves/denies.
109
+ */
110
+ respondToPermission(response) {
111
+ const targets = this.options.channelTargets || [];
112
+ for (const target of targets) {
113
+ const notification = {
114
+ method: `notifications/${target}/channel/permission`,
115
+ params: {
116
+ request_id: response.request_id,
117
+ behavior: response.behavior,
118
+ },
119
+ };
120
+ this.sink.sendNotification(notification).catch((e) => {
121
+ this.log('debug', 'Permission response failed', { error: getErrorMessage(e) });
122
+ });
123
+ this.sink.sendNotificationToAllSessions(notification).catch((e) => {
124
+ this.log('debug', `Failed to send permission to SSE sessions: ${getErrorMessage(e)}`);
125
+ });
126
+ }
127
+ }
128
+ // ---------------------------------------------------------------------------
129
+ // Publishing
130
+ // ---------------------------------------------------------------------------
131
+ /**
132
+ * Publish a channel event to the daemon for cross-process pub/sub.
133
+ * Called from output handlers whenever an emit has a `channel` field.
134
+ */
135
+ publishIfChannel(emit) {
136
+ if (!this.daemonName || !emit?.channel)
137
+ return;
138
+ publishToChannel(this.daemonName, emit.channel, emit, this.workingDir).catch((e) => {
139
+ this.log('debug', `Failed to publish channel event to daemon: ${e?.message || e}`);
140
+ });
141
+ }
142
+ // ---------------------------------------------------------------------------
143
+ // Subscribing & Message Handling
144
+ // ---------------------------------------------------------------------------
145
+ /**
146
+ * Subscribe to daemon channels for cross-process notifications.
147
+ * In channel mode, handleChannelMessage intercepts 'channel-push' events
148
+ * and translates them to notifications/claude/channel for the connected client.
149
+ */
150
+ async subscribeToChannels() {
151
+ if (!this.daemonName)
152
+ return;
153
+ try {
154
+ const unsubscribe = await subscribeChannel(this.daemonName, `${this.daemonName}:*`, (message) => {
155
+ void this.handleChannelMessage(message);
156
+ }, { workingDir: this.workingDir });
157
+ this.channelUnsubscribers.push(unsubscribe);
158
+ this.log('info', `Subscribed to daemon channel: ${this.daemonName}:*`);
159
+ }
160
+ catch (error) {
161
+ this.log('warn', `Failed to subscribe to daemon: ${getErrorMessage(error)}`);
162
+ }
163
+ }
164
+ /**
165
+ * Handle incoming channel messages and forward as MCP notifications.
166
+ * Routes channel-permission-response and channel-push messages,
167
+ * and forwards everything else as standard MCP notifications with _photon data.
168
+ */
169
+ async handleChannelMessage(message) {
170
+ if (!message || typeof message !== 'object')
171
+ return;
172
+ const msg = message;
173
+ if (process.env.PHOTON_DEBUG_EVENTS === '1') {
174
+ console.error(`[PHOTON-SERVER] Received daemon message on ${String(msg.channel)}: event=${String(msg.event)}`);
175
+ }
176
+ // Channel permission responses — photon called this.channel.respond()
177
+ if (this.options.channelMode && String(msg.channel).endsWith(':channel-permission-response')) {
178
+ const data = msg.data;
179
+ if (data?.request_id && data?.behavior) {
180
+ this.respondToPermission({
181
+ request_id: data.request_id,
182
+ behavior: data.behavior,
183
+ });
184
+ }
185
+ return;
186
+ }
187
+ // Channel events — translate to client-specific channel notifications.
188
+ if (this.options.channelMode && String(msg.channel).endsWith(':channel-push')) {
189
+ const pushData = msg.data;
190
+ const methods = this.getChannelNotificationMethods();
191
+ if (methods.length === 0)
192
+ return;
193
+ const content = typeof pushData?.content === 'string' ? pushData.content : '';
194
+ const meta = pushData?.meta || {};
195
+ try {
196
+ for (const method of methods) {
197
+ const notification = { method, params: { content, meta } };
198
+ await this.sink.sendNotification(notification);
199
+ await this.sink.sendNotificationToAllSessions(notification);
200
+ }
201
+ }
202
+ catch (e) {
203
+ this.log('debug', 'Channel notification failed', { error: getErrorMessage(e) });
204
+ }
205
+ return;
206
+ }
207
+ // Standard notification with embedded photon data
208
+ const payload = {
209
+ method: 'ui/notifications/host-context-changed',
210
+ params: {
211
+ _photon: {
212
+ photon: this.daemonName,
213
+ channel: msg.channel,
214
+ event: msg.event,
215
+ data: msg.data,
216
+ },
217
+ },
218
+ };
219
+ try {
220
+ if (process.env.PHOTON_DEBUG_EVENTS === '1') {
221
+ console.error(`[PHOTON-SERVER] Sending notification to MCP clients...`);
222
+ }
223
+ await this.sink.sendNotification(payload);
224
+ if (process.env.PHOTON_DEBUG_EVENTS === '1') {
225
+ console.error(`[PHOTON-SERVER] Notification sent successfully`);
226
+ }
227
+ }
228
+ catch (e) {
229
+ console.error(`[PHOTON-SERVER-ERROR] Notification send failed: ${getErrorMessage(e)}`);
230
+ this.log('debug', 'Notification send failed', { error: getErrorMessage(e) });
231
+ }
232
+ // Also send to SSE sessions
233
+ await this.sink.sendNotificationToAllSessions(payload).catch((e) => {
234
+ this.log('debug', 'Session notification failed', { error: getErrorMessage(e) });
235
+ });
236
+ }
237
+ /**
238
+ * Check if an incoming message is a channel permission request and handle it.
239
+ * Returns true if it was handled (caller should not process further).
240
+ */
241
+ interceptPermissionRequest(message) {
242
+ if (this.options.channelMode && message?.method?.endsWith('/channel/permission_request')) {
243
+ this.handlePermissionRequest(message.params);
244
+ return true;
245
+ }
246
+ return false;
247
+ }
248
+ // ---------------------------------------------------------------------------
249
+ // Cleanup
250
+ // ---------------------------------------------------------------------------
251
+ /**
252
+ * Unsubscribe from all daemon channels. Called during server shutdown.
253
+ */
254
+ cleanup() {
255
+ for (const unsubscribe of this.channelUnsubscribers) {
256
+ try {
257
+ unsubscribe();
258
+ }
259
+ catch {
260
+ /* ignore */
261
+ }
262
+ }
263
+ this.channelUnsubscribers = [];
264
+ }
265
+ }
266
+ //# sourceMappingURL=channel-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel-manager.js","sourceRoot":"","sources":["../src/channel-manager.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"package.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/package.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA+LzC;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAunB9D"}
1
+ {"version":3,"file":"package.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/package.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA+LzC;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA8oB9D"}
@@ -485,7 +485,7 @@ export function registerPackageCommands(program) {
485
485
  tableData.push({
486
486
  name: mcpName,
487
487
  local: info.local || '-',
488
- remote: info.remote || 'local only',
488
+ remote: info.remote || 'local editable copy',
489
489
  status: STATUS.UNKNOWN,
490
490
  });
491
491
  }
@@ -645,8 +645,9 @@ export function registerPackageCommands(program) {
645
645
  program
646
646
  .command('fork')
647
647
  .argument('<name>', 'Photon name to fork/own')
648
- .description('Take ownership of an installed photon (removes marketplace tracking)')
649
- .action(async (name, _options, command) => {
648
+ .option('--as <newName>', 'New local photon name (required when forking an already-local photon)')
649
+ .description('Create a local editable copy or take ownership of a tracked photon')
650
+ .action(async (name, options, command) => {
650
651
  try {
651
652
  const { printSuccess, printError } = await import('../../cli-formatter.js');
652
653
  const workingDir = getDefaultContext().baseDir;
@@ -662,7 +663,7 @@ export function registerPackageCommands(program) {
662
663
  }
663
664
  }
664
665
  choices.push('Create new GitHub repository');
665
- choices.push('Local only (remove marketplace tracking)');
666
+ choices.push('Local editable copy');
666
667
  const rl = createReadline();
667
668
  console.error(`\nWhere do you want to fork ${name}?\n`);
668
669
  choices.forEach((c, i) => console.error(` [${i + 1}] ${c}`));
@@ -676,7 +677,7 @@ export function registerPackageCommands(program) {
676
677
  printError('Invalid choice');
677
678
  process.exit(1);
678
679
  }
679
- let forkOptions;
680
+ let forkOptions = {};
680
681
  if (choiceIdx < targets.length) {
681
682
  // Push to existing marketplace repo
682
683
  forkOptions = { targetRepo: targets[choiceIdx].repo };
@@ -694,8 +695,25 @@ export function registerPackageCommands(program) {
694
695
  }
695
696
  forkOptions = { createRepo: repoName.trim() };
696
697
  }
697
- // else: local only, no options
698
- const result = await manager.forkPhoton(name, workingDir, forkOptions);
698
+ if (options.as?.trim()) {
699
+ forkOptions.newName = options.as.trim();
700
+ }
701
+ let result = await manager.forkPhoton(name, workingDir, forkOptions);
702
+ if (!result.success && result.requiresName && !forkOptions.newName) {
703
+ const rl3 = createReadline();
704
+ const suggested = result.suggestedName ? ` [${result.suggestedName}]` : '';
705
+ const newName = await new Promise((resolve) => {
706
+ rl3.question(`New local photon name${suggested}: `, resolve);
707
+ });
708
+ rl3.close();
709
+ const resolvedName = newName.trim() || result.suggestedName;
710
+ if (!resolvedName) {
711
+ printError('A new local name is required');
712
+ process.exit(1);
713
+ }
714
+ forkOptions.newName = resolvedName;
715
+ result = await manager.forkPhoton(name, workingDir, forkOptions);
716
+ }
699
717
  if (result.success) {
700
718
  printSuccess(result.message);
701
719
  }