@messenger-agent/messenger-agent 0.24.0-alpha.2 → 0.24.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/app.js CHANGED
@@ -1,17 +1 @@
1
- import { Hono } from "hono";
2
- import { logger as honoLogger } from "hono/logger";
3
- import { agentAuthMiddleware } from "@messenger-agent/shared/agent-auth";
4
- import { createAgentFileRoutes } from "@messenger-agent/shared/agent-files";
5
- import { logger } from "@messenger-agent/shared/logger";
6
- import { createAgentWorkspaceRoutes } from "@messenger-agent/shared/workspace-routes";
7
- import { appConfig } from "./config.js";
8
- export function createApp(config = appConfig) {
9
- const app = new Hono();
10
- app.use(honoLogger((message, ...rest) => logger.info(message, ...rest)));
11
- app.get("/health", (c) => c.json({ status: "ok", role: "messenger-agent" }));
12
- app.use("/*", agentAuthMiddleware(config.authTokens));
13
- app.route("/v1/files", createAgentFileRoutes());
14
- app.route("/v1/workspaces", createAgentWorkspaceRoutes({ config, textMaxBytes: config.textMaxBytes }));
15
- return app;
16
- }
17
- export default createApp();
1
+ import{Hono as s}from"hono";import{logger as a}from"hono/logger";import{agentAuthMiddleware as p}from"@messenger-agent/shared/agent-auth";import{createAgentFileRoutes as m}from"@messenger-agent/shared/agent-files";import{logger as n}from"@messenger-agent/shared/logger";import{createAgentWorkspaceRoutes as u}from"@messenger-agent/shared/workspace-routes";import{appConfig as g}from"./config.js";function i(t=g){const e=new s;return e.use(a((o,...r)=>n.info(o,...r))),e.get("/health",o=>o.json({status:"ok",role:"messenger-agent"})),e.use("/*",p(t.authTokens)),e.route("/v1/files",m()),e.route("/v1/workspaces",u({config:t,textMaxBytes:t.textMaxBytes})),e}var d=i();export{i as createApp,d as default};
package/dist/config.js CHANGED
@@ -1,78 +1 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import { parse } from "yaml";
3
- import { z } from "zod";
4
- import { normalizeOptional, parseAgentAuthConfig, parseAgentWorkspacesConfig, } from "@messenger-agent/shared/agent-config";
5
- import { DEFAULT_TEXT_MAX_BYTES } from "@messenger-agent/shared/workspace-files";
6
- import { logger, normalizeLogLevel, setLogFile, setLogLevel } from "@messenger-agent/shared/logger";
7
- const DEFAULT_TUNNEL_SERVER_URL = "wss://m.elevo.vip/agent-bridge/tunnel";
8
- const RawConfigSchema = z
9
- .object({
10
- log_level: z.string().optional(),
11
- log_file: z.string().optional(),
12
- port: z.coerce.number().int().positive().optional(),
13
- messenger: z
14
- .object({
15
- port: z.coerce.number().int().positive().optional(),
16
- log_file: z.string().optional(),
17
- })
18
- .optional(),
19
- auth_tokens: z.record(z.string(), z.string()).optional(),
20
- workspaces: z
21
- .array(z.object({ id: z.string().optional(), name: z.string().optional(), path: z.string().optional() }))
22
- .optional(),
23
- workspace_file_text_max_bytes: z.coerce.number().int().positive().optional(),
24
- tunnel: z
25
- .object({
26
- enabled: z.boolean().optional(),
27
- server_url: z.string().optional(),
28
- tunnel_id: z.string().optional(),
29
- token: z.string().optional(),
30
- reconnect_initial_ms: z.number().int().positive().optional(),
31
- reconnect_max_ms: z.number().int().positive().optional(),
32
- heartbeat_interval_ms: z.number().int().positive().optional(),
33
- })
34
- .optional(),
35
- })
36
- .loose();
37
- export function loadMessengerAgentConfig(configPath = process.env.MESSENGER_AGENT_CONFIG_PATH ?? process.env.AGENT_CONFIG_PATH ?? "./agent-config.yaml") {
38
- let parsedYaml = {};
39
- if (existsSync(configPath)) {
40
- try {
41
- parsedYaml = parse(readFileSync(configPath, "utf8"));
42
- }
43
- catch (err) {
44
- logger.error(`Failed to parse messenger-agent config at ${configPath}:`, err);
45
- }
46
- }
47
- else {
48
- logger.warn(`Messenger-agent config file not found at ${configPath}`);
49
- }
50
- const parsed = RawConfigSchema.safeParse(parsedYaml ?? {});
51
- if (!parsed.success)
52
- logger.error(`Invalid messenger-agent config at ${configPath}:`, parsed.error.issues);
53
- const data = parsed.success ? parsed.data : {};
54
- const logLevel = normalizeLogLevel(data.log_level);
55
- const logFile = normalizeOptional(data.messenger?.log_file) ?? normalizeOptional(data.log_file);
56
- const enabled = data.tunnel?.enabled ?? false;
57
- setLogLevel(logLevel);
58
- setLogFile(logFile);
59
- return {
60
- configPath,
61
- logLevel,
62
- logFile,
63
- port: data.messenger?.port ?? data.port ?? 8301,
64
- textMaxBytes: data.workspace_file_text_max_bytes ?? DEFAULT_TEXT_MAX_BYTES,
65
- tunnel: {
66
- enabled,
67
- serverUrl: normalizeOptional(data.tunnel?.server_url) ?? (enabled ? DEFAULT_TUNNEL_SERVER_URL : undefined),
68
- tunnelId: normalizeOptional(data.tunnel?.tunnel_id),
69
- token: normalizeOptional(data.tunnel?.token),
70
- reconnectInitialMs: data.tunnel?.reconnect_initial_ms ?? 1000,
71
- reconnectMaxMs: data.tunnel?.reconnect_max_ms ?? 30000,
72
- heartbeatIntervalMs: data.tunnel?.heartbeat_interval_ms ?? 30000,
73
- },
74
- ...parseAgentAuthConfig(data),
75
- ...parseAgentWorkspacesConfig(data),
76
- };
77
- }
78
- export const appConfig = loadMessengerAgentConfig();
1
+ import{existsSync as g,readFileSync as _}from"node:fs";import{parse as m}from"yaml";import{z as e}from"zod";import{normalizeOptional as o,parseAgentAuthConfig as u,parseAgentWorkspacesConfig as f}from"@messenger-agent/shared/agent-config";import{DEFAULT_TEXT_MAX_BYTES as v}from"@messenger-agent/shared/workspace-files";import{logger as i,normalizeLogLevel as b,setLogFile as d,setLogLevel as x}from"@messenger-agent/shared/logger";const E="wss://m.elevo.vip/agent-bridge/tunnel",A=e.object({log_level:e.string().optional(),log_file:e.string().optional(),port:e.coerce.number().int().positive().optional(),messenger:e.object({port:e.coerce.number().int().positive().optional(),log_file:e.string().optional()}).optional(),auth_tokens:e.record(e.string(),e.string()).optional(),workspaces:e.array(e.object({id:e.string().optional(),name:e.string().optional(),path:e.string().optional()})).optional(),workspace_file_text_max_bytes:e.coerce.number().int().positive().optional(),tunnel:e.object({enabled:e.boolean().optional(),server_url:e.string().optional(),tunnel_id:e.string().optional(),token:e.string().optional(),reconnect_initial_ms:e.number().int().positive().optional(),reconnect_max_ms:e.number().int().positive().optional(),heartbeat_interval_ms:e.number().int().positive().optional()}).optional()}).loose();function L(t=process.env.MESSENGER_AGENT_CONFIG_PATH??process.env.AGENT_CONFIG_PATH??"./agent-config.yaml"){let s={};if(g(t))try{s=m(_(t,"utf8"))}catch(c){i.error(`Failed to parse messenger-agent config at ${t}:`,c)}else i.warn(`Messenger-agent config file not found at ${t}`);const r=A.safeParse(s??{});r.success||i.error(`Invalid messenger-agent config at ${t}:`,r.error.issues);const n=r.success?r.data:{},l=b(n.log_level),a=o(n.messenger?.log_file)??o(n.log_file),p=n.tunnel?.enabled??!1;return x(l),d(a),{configPath:t,logLevel:l,logFile:a,port:n.messenger?.port??n.port??8301,textMaxBytes:n.workspace_file_text_max_bytes??v,tunnel:{enabled:p,serverUrl:o(n.tunnel?.server_url)??(p?E:void 0),tunnelId:o(n.tunnel?.tunnel_id),token:o(n.tunnel?.token),reconnectInitialMs:n.tunnel?.reconnect_initial_ms??1e3,reconnectMaxMs:n.tunnel?.reconnect_max_ms??3e4,heartbeatIntervalMs:n.tunnel?.heartbeat_interval_ms??3e4},...u(n),...f(n)}}const N=L();export{N as appConfig,L as loadMessengerAgentConfig};
package/dist/index.js CHANGED
@@ -1,18 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import { serve } from "@hono/node-server";
3
- import app from "./app.js";
4
- import { appConfig } from "./config.js";
5
- import { WorkspaceTunnelClient } from "./tunnel-client.js";
6
- import { logger } from "@messenger-agent/shared/logger";
7
- const tunnelClient = appConfig.tunnel.enabled ? new WorkspaceTunnelClient(appConfig) : undefined;
8
- if (tunnelClient) {
9
- tunnelClient.start().catch((err) => logger.error("[WorkspaceTunnel] stopped unexpectedly:", err));
10
- }
11
- else {
12
- serve({ fetch: app.fetch, port: appConfig.port }, () => {
13
- logger.info(`messenger-agent listening on http://0.0.0.0:${appConfig.port}`);
14
- });
15
- }
16
- const stop = () => tunnelClient?.stop();
17
- process.once("SIGINT", stop);
18
- process.once("SIGTERM", stop);
2
+ import{serve as p}from"@hono/node-server";import s from"./app.js";import{appConfig as e}from"./config.js";import{WorkspaceTunnelClient as i}from"./tunnel-client.js";import{logger as n}from"@messenger-agent/shared/logger";const o=e.tunnel.enabled?new i(e):void 0;o?o.start().catch(r=>n.error("[WorkspaceTunnel] stopped unexpectedly:",r)):p({fetch:s.fetch,port:e.port},()=>{n.info(`messenger-agent listening on http://0.0.0.0:${e.port}`)});const t=()=>o?.stop();process.once("SIGINT",t),process.once("SIGTERM",t);
@@ -1,159 +1 @@
1
- import { createRequire } from "node:module";
2
- import WebSocket from "ws";
3
- import { logger } from "@messenger-agent/shared/logger";
4
- import { createAgentFileRoutes } from "@messenger-agent/shared/agent-files";
5
- import { createPrefixedRouteRequest, createWorkspaceRouteRequest, isAgentFileHttpTunnelRequest, isWorkspaceHttpTunnelRequest, sendHttpTunnelResponse, } from "@messenger-agent/shared/tunnel-http";
6
- import { CODING_AGENT_TUNNEL_DUPLICATE_CLOSE_CODE, CODING_AGENT_TUNNEL_PROTOCOL_VERSION, } from "@messenger-agent/shared/tunnel-protocol";
7
- import { createAgentWorkspaceRoutes } from "@messenger-agent/shared/workspace-routes";
8
- import { appConfig } from "./config.js";
9
- const clientVersion = createRequire(import.meta.url)("../package.json").version;
10
- const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
11
- function parseServerMessage(raw) {
12
- try {
13
- const value = JSON.parse(raw.toString());
14
- return value && typeof value === "object" && "type" in value ? value : undefined;
15
- }
16
- catch {
17
- return undefined;
18
- }
19
- }
20
- export class WorkspaceTunnelClient {
21
- config;
22
- stopped = false;
23
- socket;
24
- running = new Map();
25
- workspaceRoutes;
26
- fileRoutes = createAgentFileRoutes();
27
- constructor(config = appConfig) {
28
- this.config = config;
29
- this.workspaceRoutes = createAgentWorkspaceRoutes({ config, textMaxBytes: config.textMaxBytes });
30
- }
31
- async start() {
32
- if (!this.config.tunnel.enabled)
33
- return;
34
- if (!this.config.tunnel.serverUrl || !this.config.tunnel.tunnelId || !this.config.tunnel.token) {
35
- throw new Error("Workspace tunnel requires server_url, tunnel_id, and token when enabled");
36
- }
37
- let backoffMs = this.config.tunnel.reconnectInitialMs;
38
- while (!this.stopped) {
39
- try {
40
- await this.connectOnce();
41
- backoffMs = this.config.tunnel.reconnectInitialMs;
42
- }
43
- catch (err) {
44
- if (!this.stopped)
45
- logger.warn("[WorkspaceTunnel] connection failed:", err);
46
- }
47
- if (!this.stopped) {
48
- await delay(backoffMs);
49
- backoffMs = Math.min(backoffMs * 2, this.config.tunnel.reconnectMaxMs);
50
- }
51
- }
52
- }
53
- stop() {
54
- this.stopped = true;
55
- for (const controller of this.running.values())
56
- controller.abort();
57
- this.running.clear();
58
- this.socket?.close();
59
- }
60
- connectOnce() {
61
- return new Promise((resolve, reject) => {
62
- const ws = new WebSocket(this.config.tunnel.serverUrl, {
63
- headers: { Authorization: `Bearer ${this.config.tunnel.token}` },
64
- });
65
- this.socket = ws;
66
- let heartbeatTimer;
67
- let settled = false;
68
- const cleanup = () => {
69
- if (heartbeatTimer)
70
- clearInterval(heartbeatTimer);
71
- if (this.socket === ws)
72
- this.socket = undefined;
73
- for (const controller of this.running.values())
74
- controller.abort(new Error("Workspace tunnel closed"));
75
- this.running.clear();
76
- };
77
- ws.on("open", () => {
78
- this.send({
79
- type: "hello",
80
- protocolVersion: CODING_AGENT_TUNNEL_PROTOCOL_VERSION,
81
- tunnelId: this.config.tunnel.tunnelId,
82
- agentType: "workspace",
83
- capabilities: ["workspace.files", "workspace.git", "workspace.tasks"],
84
- clientVersion,
85
- workspaces: [...this.config.workspaces.values()]
86
- .map(({ id, name }) => ({ id, name }))
87
- .sort((left, right) => left.id.localeCompare(right.id)),
88
- });
89
- heartbeatTimer = setInterval(() => this.send({ type: "heartbeat" }), this.config.tunnel.heartbeatIntervalMs);
90
- logger.info(`[WorkspaceTunnel] connected to ${this.config.tunnel.serverUrl} as ${this.config.tunnel.tunnelId}`);
91
- });
92
- ws.on("message", (raw) => {
93
- const message = parseServerMessage(raw);
94
- if (!message || message.type === "heartbeat")
95
- return;
96
- if (message.type === "cancel") {
97
- this.running.get(message.id)?.abort();
98
- }
99
- else if (message.type === "request") {
100
- void this.handleRequest(message);
101
- }
102
- });
103
- ws.once("error", (err) => {
104
- if (!settled) {
105
- settled = true;
106
- cleanup();
107
- reject(err);
108
- }
109
- });
110
- ws.once("close", (code, reason) => {
111
- cleanup();
112
- if (code === CODING_AGENT_TUNNEL_DUPLICATE_CLOSE_CODE || code === 1008) {
113
- this.stopped = true;
114
- logger.warn(`[WorkspaceTunnel] connection rejected with code ${code}; reconnect disabled until service restart: ${reason.toString()}`);
115
- }
116
- if (!settled) {
117
- settled = true;
118
- resolve();
119
- }
120
- });
121
- });
122
- }
123
- send(message) {
124
- if (this.socket?.readyState === WebSocket.OPEN)
125
- this.socket.send(JSON.stringify(message));
126
- }
127
- async handleRequest(message) {
128
- const isFileRequest = isAgentFileHttpTunnelRequest(message);
129
- if (!isFileRequest && !isWorkspaceHttpTunnelRequest(message)) {
130
- this.send({
131
- type: "response.error",
132
- id: message.id,
133
- code: "unsupported_request",
134
- errorText: `Unsupported workspace tunnel request: ${message.method} ${message.path}`,
135
- });
136
- return;
137
- }
138
- const controller = new AbortController();
139
- this.running.set(message.id, controller);
140
- try {
141
- const request = isFileRequest
142
- ? createPrefixedRouteRequest(message, "/v1/files", controller.signal)
143
- : createWorkspaceRouteRequest(message, controller.signal);
144
- const response = await (isFileRequest ? this.fileRoutes : this.workspaceRoutes).fetch(request);
145
- await sendHttpTunnelResponse(message.id, response, (responseMessage) => this.send(responseMessage));
146
- }
147
- catch (err) {
148
- this.send({
149
- type: "response.error",
150
- id: message.id,
151
- code: "request_failed",
152
- errorText: err instanceof Error ? err.message : "Workspace tunnel request failed",
153
- });
154
- }
155
- finally {
156
- this.running.delete(message.id);
157
- }
158
- }
159
- }
1
+ import{createRequire as p}from"node:module";import u from"ws";import{logger as l}from"@messenger-agent/shared/logger";import{createAgentFileRoutes as f}from"@messenger-agent/shared/agent-files";import{createPrefixedRouteRequest as h,createWorkspaceRouteRequest as d,isAgentFileHttpTunnelRequest as k,isWorkspaceHttpTunnelRequest as g,sendHttpTunnelResponse as w}from"@messenger-agent/shared/tunnel-http";import{CODING_AGENT_TUNNEL_DUPLICATE_CLOSE_CODE as y,CODING_AGENT_TUNNEL_PROTOCOL_VERSION as R}from"@messenger-agent/shared/tunnel-protocol";import{createAgentWorkspaceRoutes as m}from"@messenger-agent/shared/workspace-routes";import{appConfig as T}from"./config.js";const v=p(import.meta.url)("../package.json").version,b=c=>new Promise(e=>setTimeout(e,c));function q(c){try{const e=JSON.parse(c.toString());return e&&typeof e=="object"&&"type"in e?e:void 0}catch{return}}class x{config;stopped=!1;socket;running=new Map;workspaceRoutes;fileRoutes=f();constructor(e=T){this.config=e,this.workspaceRoutes=m({config:e,textMaxBytes:e.textMaxBytes})}async start(){if(!this.config.tunnel.enabled)return;if(!this.config.tunnel.serverUrl||!this.config.tunnel.tunnelId||!this.config.tunnel.token)throw new Error("Workspace tunnel requires server_url, tunnel_id, and token when enabled");let e=this.config.tunnel.reconnectInitialMs;for(;!this.stopped;){try{await this.connectOnce(),e=this.config.tunnel.reconnectInitialMs}catch(o){this.stopped||l.warn("[WorkspaceTunnel] connection failed:",o)}this.stopped||(await b(e),e=Math.min(e*2,this.config.tunnel.reconnectMaxMs))}}stop(){this.stopped=!0;for(const e of this.running.values())e.abort();this.running.clear(),this.socket?.close()}connectOnce(){return new Promise((e,o)=>{const r=new u(this.config.tunnel.serverUrl,{headers:{Authorization:`Bearer ${this.config.tunnel.token}`}});this.socket=r;let s,i=!1;const a=()=>{s&&clearInterval(s),this.socket===r&&(this.socket=void 0);for(const t of this.running.values())t.abort(new Error("Workspace tunnel closed"));this.running.clear()};r.on("open",()=>{this.send({type:"hello",protocolVersion:R,tunnelId:this.config.tunnel.tunnelId,agentType:"workspace",capabilities:["workspace.files","workspace.git","workspace.tasks"],clientVersion:v,workspaces:[...this.config.workspaces.values()].map(({id:t,name:n})=>({id:t,name:n})).sort((t,n)=>t.id.localeCompare(n.id))}),s=setInterval(()=>this.send({type:"heartbeat"}),this.config.tunnel.heartbeatIntervalMs),l.info(`[WorkspaceTunnel] connected to ${this.config.tunnel.serverUrl} as ${this.config.tunnel.tunnelId}`)}),r.on("message",t=>{const n=q(t);!n||n.type==="heartbeat"||(n.type==="cancel"?this.running.get(n.id)?.abort():n.type==="request"&&this.handleRequest(n))}),r.once("error",t=>{i||(i=!0,a(),o(t))}),r.once("close",(t,n)=>{a(),(t===y||t===1008)&&(this.stopped=!0,l.warn(`[WorkspaceTunnel] connection rejected with code ${t}; reconnect disabled until service restart: ${n.toString()}`)),i||(i=!0,e())})})}send(e){this.socket?.readyState===u.OPEN&&this.socket.send(JSON.stringify(e))}async handleRequest(e){const o=k(e);if(!o&&!g(e)){this.send({type:"response.error",id:e.id,code:"unsupported_request",errorText:`Unsupported workspace tunnel request: ${e.method} ${e.path}`});return}const r=new AbortController;this.running.set(e.id,r);try{const s=o?h(e,"/v1/files",r.signal):d(e,r.signal),i=await(o?this.fileRoutes:this.workspaceRoutes).fetch(s);await w(e.id,i,a=>this.send(a))}catch(s){this.send({type:"response.error",id:e.id,code:"request_failed",errorText:s instanceof Error?s.message:"Workspace tunnel request failed"})}finally{this.running.delete(e.id)}}}export{x as WorkspaceTunnelClient};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@messenger-agent/messenger-agent",
3
- "version": "0.24.0-alpha.2",
3
+ "version": "0.24.0-alpha.3",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -20,7 +20,7 @@
20
20
  "ws": "^8.18.3",
21
21
  "yaml": "^2.9.0",
22
22
  "zod": "^4.4.3",
23
- "@messenger-agent/shared": "0.24.0-alpha.2"
23
+ "@messenger-agent/shared": "0.24.0-alpha.3"
24
24
  },
25
25
  "devDependencies": {
26
26
  "@types/ws": "^8.18.1"