@skillfm/local 2.2.0 → 2.5.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 (101) hide show
  1. package/README.md +12 -0
  2. package/dist/doctor.js.map +1 -1
  3. package/dist/guard/cli.d.ts.map +1 -1
  4. package/dist/guard/cli.js.map +1 -1
  5. package/dist/harness/kernels/deny-pipeline.js +1 -1
  6. package/dist/harness/kernels/deny-pipeline.js.map +1 -1
  7. package/dist/harness/writers.d.ts.map +1 -1
  8. package/dist/harness/writers.js +2 -0
  9. package/dist/harness/writers.js.map +1 -1
  10. package/dist/index.js +29 -3
  11. package/dist/index.js.map +1 -1
  12. package/dist/mcp-stdio/api-client.d.ts.map +1 -1
  13. package/dist/mcp-stdio/api-client.js +3 -0
  14. package/dist/mcp-stdio/api-client.js.map +1 -1
  15. package/dist/mcp-stdio/render-flow.d.ts.map +1 -1
  16. package/dist/mcp-stdio/render-flow.js.map +1 -1
  17. package/dist/mcp-stdio/request-context.d.ts.map +1 -1
  18. package/dist/mcp-stdio/request-context.js +1 -0
  19. package/dist/mcp-stdio/request-context.js.map +1 -1
  20. package/dist/mcp-stdio/server.d.ts.map +1 -1
  21. package/dist/mcp-stdio/server.js +118 -17
  22. package/dist/mcp-stdio/server.js.map +1 -1
  23. package/dist/mcp-stdio/sse-progress-client.js.map +1 -1
  24. package/dist/skill-installer/bundle-fetcher.d.ts +40 -0
  25. package/dist/skill-installer/bundle-fetcher.d.ts.map +1 -0
  26. package/dist/skill-installer/bundle-fetcher.js +133 -0
  27. package/dist/skill-installer/bundle-fetcher.js.map +1 -0
  28. package/dist/skill-installer/errors.d.ts +12 -0
  29. package/dist/skill-installer/errors.d.ts.map +1 -0
  30. package/dist/skill-installer/errors.js +42 -0
  31. package/dist/skill-installer/errors.js.map +1 -0
  32. package/dist/skill-installer/index.d.ts +20 -0
  33. package/dist/skill-installer/index.d.ts.map +1 -0
  34. package/dist/skill-installer/index.js +193 -0
  35. package/dist/skill-installer/index.js.map +1 -0
  36. package/dist/skill-installer/lockfile.d.ts +8 -0
  37. package/dist/skill-installer/lockfile.d.ts.map +1 -0
  38. package/dist/skill-installer/lockfile.js +52 -0
  39. package/dist/skill-installer/lockfile.js.map +1 -0
  40. package/dist/skill-installer/npm-installer.d.ts +16 -0
  41. package/dist/skill-installer/npm-installer.d.ts.map +1 -0
  42. package/dist/skill-installer/npm-installer.js +83 -0
  43. package/dist/skill-installer/npm-installer.js.map +1 -0
  44. package/dist/skill-installer/paths.d.ts +4 -0
  45. package/dist/skill-installer/paths.d.ts.map +1 -0
  46. package/dist/skill-installer/paths.js +16 -0
  47. package/dist/skill-installer/paths.js.map +1 -0
  48. package/dist/skill-installer/tar-extractor.d.ts +15 -0
  49. package/dist/skill-installer/tar-extractor.d.ts.map +1 -0
  50. package/dist/skill-installer/tar-extractor.js +56 -0
  51. package/dist/skill-installer/tar-extractor.js.map +1 -0
  52. package/dist/skill-md/template.js +2 -2
  53. package/dist/skill-runner/cli.d.ts +4 -0
  54. package/dist/skill-runner/cli.d.ts.map +1 -0
  55. package/dist/skill-runner/cli.js +81 -0
  56. package/dist/skill-runner/cli.js.map +1 -0
  57. package/dist/skill-runner/discovery.d.ts +3 -0
  58. package/dist/skill-runner/discovery.d.ts.map +1 -0
  59. package/dist/skill-runner/discovery.js +108 -0
  60. package/dist/skill-runner/discovery.js.map +1 -0
  61. package/dist/skill-runner/index.d.ts +9 -0
  62. package/dist/skill-runner/index.d.ts.map +1 -0
  63. package/dist/skill-runner/index.js +100 -0
  64. package/dist/skill-runner/index.js.map +1 -0
  65. package/dist/skill-runner/registry.d.ts +11 -0
  66. package/dist/skill-runner/registry.d.ts.map +1 -0
  67. package/dist/skill-runner/registry.js +79 -0
  68. package/dist/skill-runner/registry.js.map +1 -0
  69. package/dist/skill-runner/spawner.d.ts +14 -0
  70. package/dist/skill-runner/spawner.d.ts.map +1 -0
  71. package/dist/skill-runner/spawner.js +85 -0
  72. package/dist/skill-runner/spawner.js.map +1 -0
  73. package/dist/skill-runner/types.d.ts +62 -0
  74. package/dist/skill-runner/types.d.ts.map +1 -0
  75. package/dist/skill-runner/types.js +6 -0
  76. package/dist/skill-runner/types.js.map +1 -0
  77. package/dist/skill-tunnel/cli.d.ts +5 -0
  78. package/dist/skill-tunnel/cli.d.ts.map +1 -0
  79. package/dist/skill-tunnel/cli.js +205 -0
  80. package/dist/skill-tunnel/cli.js.map +1 -0
  81. package/dist/skill-tunnel/client.d.ts +56 -0
  82. package/dist/skill-tunnel/client.d.ts.map +1 -0
  83. package/dist/skill-tunnel/client.js +260 -0
  84. package/dist/skill-tunnel/client.js.map +1 -0
  85. package/dist/skill-tunnel/handshake.d.ts +35 -0
  86. package/dist/skill-tunnel/handshake.d.ts.map +1 -0
  87. package/dist/skill-tunnel/handshake.js +61 -0
  88. package/dist/skill-tunnel/handshake.js.map +1 -0
  89. package/dist/skill-tunnel/heartbeat.d.ts +34 -0
  90. package/dist/skill-tunnel/heartbeat.d.ts.map +1 -0
  91. package/dist/skill-tunnel/heartbeat.js +86 -0
  92. package/dist/skill-tunnel/heartbeat.js.map +1 -0
  93. package/dist/skill-tunnel/local-bridge.d.ts +30 -0
  94. package/dist/skill-tunnel/local-bridge.d.ts.map +1 -0
  95. package/dist/skill-tunnel/local-bridge.js +224 -0
  96. package/dist/skill-tunnel/local-bridge.js.map +1 -0
  97. package/dist/skill-tunnel/reconnect.d.ts +21 -0
  98. package/dist/skill-tunnel/reconnect.d.ts.map +1 -0
  99. package/dist/skill-tunnel/reconnect.js +72 -0
  100. package/dist/skill-tunnel/reconnect.js.map +1 -0
  101. package/package.json +5 -2
@@ -0,0 +1,260 @@
1
+ // M2 SkillTunnelClient — WebSocket client + JSON-RPC dispatch
2
+ //
3
+ // 职责:
4
+ // - 连 brain (wss://api.skillfm.ai/v1/skill-tunnel) 带 TunnelHandshakeHeaders
5
+ // - 收 brain 发来的 skill.* JSON-RPC request → dispatch 到 LocalBridge → HTTP 回转 127.0.0.1
6
+ // - 主动发 tunnel.heartbeat / tunnel.skill_update(pending map 等 response)
7
+ // - 断线按 close code 决定是否重连;非致命走 ReconnectScheduler
8
+ //
9
+ // BYOK 纪律:
10
+ // - 全程 cookie / playwright profile 留本节点,client 只做"brain→本地 loopback"桥接
11
+ // - 凭证仅 agent_token / authSecret,存 ~/.skillfm/config.json(调用方 CLI 负责读)
12
+ //
13
+ // 反向契约:
14
+ // 输入: LocalSkillRegistryEntry[] 来自 M1 readRegistry()
15
+ // 输出: 对 brain 返回 JsonRpcSuccessResponse | JsonRpcErrorResponse
16
+ // 上游缺数据: 若 localSkills 为空 → 仍 connect,brain 侧 handshake 校验时决定 accept/reject(我们不假设)
17
+ // 下游假设我: handshake 头齐 9 个 + signature 正确
18
+ import { randomUUID } from 'node:crypto';
19
+ import { WebSocket } from 'ws';
20
+ import { TUNNEL_CONSTANTS, TUNNEL_SUBPROTOCOL, TUNNEL_ERROR_CODES, } from '@skillfm/contracts/skill-tunnel';
21
+ import { buildHandshakeHeaders, computeRpcMetaHmac } from './handshake.js';
22
+ import { HeartbeatLoop } from './heartbeat.js';
23
+ import { LocalBridge } from './local-bridge.js';
24
+ import { ReconnectScheduler, isFatalCloseCode } from './reconnect.js';
25
+ export class SkillTunnelClient {
26
+ opts;
27
+ bridge;
28
+ reconnect;
29
+ heartbeat;
30
+ ws = null;
31
+ status = 'disconnected';
32
+ lastConnectedAt = null;
33
+ stopped = false;
34
+ pending = new Map();
35
+ constructor(opts) {
36
+ this.opts = opts;
37
+ const endpointMap = new Map(opts.localSkills.map((s) => [s.slug, s.endpoint]));
38
+ this.bridge = new LocalBridge({
39
+ resolveEndpoint: (slug) => endpointMap.get(slug) ?? null,
40
+ skillAuthSecret: opts.authSecret,
41
+ fetch: opts.fetch,
42
+ });
43
+ this.reconnect = new ReconnectScheduler();
44
+ this.heartbeat = new HeartbeatLoop({
45
+ buildStatusSnapshot: () => this.buildHeartbeatStatus(),
46
+ sendHeartbeat: async (params) => {
47
+ await this.sendClientRequest('tunnel.heartbeat', params);
48
+ },
49
+ onTimeout: () => this.handleHeartbeatTimeout(),
50
+ });
51
+ }
52
+ async start() {
53
+ this.stopped = false;
54
+ await this.connectOnce();
55
+ }
56
+ async stop() {
57
+ this.stopped = true;
58
+ this.reconnect.stop();
59
+ this.heartbeat.stop();
60
+ for (const [, p] of this.pending) {
61
+ clearTimeout(p.timer);
62
+ p.reject(new Error('tunnel stopped'));
63
+ }
64
+ this.pending.clear();
65
+ if (this.ws && this.ws.readyState !== WebSocket.CLOSED) {
66
+ try {
67
+ this.ws.close(1000, 'graceful');
68
+ }
69
+ catch {
70
+ // ignore
71
+ }
72
+ }
73
+ this.setStatus('disconnected');
74
+ }
75
+ status$() {
76
+ return {
77
+ connected: this.status === 'connected',
78
+ lastConnectedAt: this.lastConnectedAt,
79
+ reconnectAttempts: this.reconnect.attempts,
80
+ };
81
+ }
82
+ /** 主动上报 skill lifecycle 事件 */
83
+ async sendSkillUpdate(params) {
84
+ await this.sendClientRequest('tunnel.skill_update', params);
85
+ }
86
+ // ==========================================================================
87
+ // 内部: 连接 / 事件
88
+ // ==========================================================================
89
+ async connectOnce() {
90
+ if (this.stopped)
91
+ return;
92
+ this.setStatus('connecting');
93
+ const { headers } = buildHandshakeHeaders({
94
+ agentToken: this.opts.agentToken,
95
+ authSecret: this.opts.authSecret,
96
+ localSkills: this.opts.localSkills,
97
+ skillfmLocalVersion: this.opts.skillfmLocalVersion,
98
+ });
99
+ const Ctor = this.opts.webSocketCtor ?? WebSocket;
100
+ // ws 的 headers 类型是 OutgoingHttpHeaders (index signature);TunnelHandshakeHeaders 是
101
+ // 强类型 9 字段对象,需要 cast 到宽类型才能传给 ws 构造器。
102
+ const ws = new Ctor(this.opts.brainUrl, [TUNNEL_SUBPROTOCOL], {
103
+ headers: headers,
104
+ });
105
+ this.ws = ws;
106
+ ws.on('open', () => this.onOpen());
107
+ ws.on('message', (raw) => this.onMessage(raw));
108
+ ws.on('error', (err) => this.onError(err));
109
+ ws.on('close', (code, reason) => this.onClose(code, reason));
110
+ }
111
+ onOpen() {
112
+ this.lastConnectedAt = Date.now();
113
+ this.reconnect.reset();
114
+ this.heartbeat.start();
115
+ this.setStatus('connected');
116
+ }
117
+ onError(err) {
118
+ // ws 的 'error' 事件在 close 前先触发,真正的重连判断放 close 里
119
+ console.error('[skill-tunnel] ws error:', err.message);
120
+ }
121
+ onClose(code, reason) {
122
+ this.heartbeat.stop();
123
+ for (const [, p] of this.pending) {
124
+ clearTimeout(p.timer);
125
+ p.reject(new Error(`ws closed (code=${code})`));
126
+ }
127
+ this.pending.clear();
128
+ this.setStatus('disconnected');
129
+ if (this.stopped)
130
+ return;
131
+ if (isFatalCloseCode(code)) {
132
+ console.error(`[skill-tunnel] fatal close code ${code}: ${reason.toString('utf-8')} — no reconnect`);
133
+ return;
134
+ }
135
+ this.reconnect.schedule(() => {
136
+ void this.connectOnce();
137
+ });
138
+ }
139
+ onMessage(raw) {
140
+ let frame;
141
+ try {
142
+ frame = JSON.parse(raw.toString());
143
+ }
144
+ catch {
145
+ console.error('[skill-tunnel] non-JSON frame dropped');
146
+ return;
147
+ }
148
+ // Response (brain 回我们主动发出的 heartbeat 等)
149
+ if ('result' in frame || 'error' in frame) {
150
+ this.handleResponse(frame);
151
+ return;
152
+ }
153
+ // Request (brain → agent, skill.*)
154
+ if ('method' in frame && 'id' in frame) {
155
+ void this.handleServerRequest(frame);
156
+ }
157
+ }
158
+ async handleServerRequest(req) {
159
+ const response = await this.bridge.dispatch(req.id, req.method, req.params);
160
+ this.sendFrame(response);
161
+ }
162
+ handleResponse(res) {
163
+ const p = this.pending.get(res.id);
164
+ if (!p) {
165
+ // heartbeat ack 可能没在 pending(fire-and-forget),更新 ack 时间就好
166
+ if ('result' in res) {
167
+ this.heartbeat.onAck();
168
+ }
169
+ return;
170
+ }
171
+ this.pending.delete(res.id);
172
+ clearTimeout(p.timer);
173
+ if ('error' in res) {
174
+ p.reject(new Error(`rpc error ${res.error.code}: ${res.error.message}`));
175
+ }
176
+ else {
177
+ this.heartbeat.onAck();
178
+ p.resolve(res.result);
179
+ }
180
+ }
181
+ // ==========================================================================
182
+ // 内部: 发送
183
+ // ==========================================================================
184
+ sendFrame(frame) {
185
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN)
186
+ return;
187
+ try {
188
+ this.ws.send(JSON.stringify(frame));
189
+ }
190
+ catch (err) {
191
+ console.error('[skill-tunnel] send failed:', err.message);
192
+ }
193
+ }
194
+ async sendClientRequest(method, params) {
195
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
196
+ throw new Error('tunnel not connected');
197
+ }
198
+ const id = randomUUID();
199
+ const req = {
200
+ jsonrpc: '2.0',
201
+ id,
202
+ method,
203
+ params,
204
+ meta: {
205
+ hmac: computeRpcMetaHmac(id, method, params, this.opts.authSecret),
206
+ },
207
+ };
208
+ return new Promise((resolve, reject) => {
209
+ const timer = setTimeout(() => {
210
+ this.pending.delete(id);
211
+ reject(new Error(`rpc timeout ${method}`));
212
+ }, TUNNEL_CONSTANTS.RPC_REQUEST_TIMEOUT_MS);
213
+ if (typeof timer.unref === 'function') {
214
+ timer.unref();
215
+ }
216
+ this.pending.set(id, {
217
+ resolve: (v) => resolve(v),
218
+ reject,
219
+ timer,
220
+ });
221
+ this.sendFrame(req);
222
+ });
223
+ }
224
+ // ==========================================================================
225
+ // Heartbeat 工具
226
+ // ==========================================================================
227
+ buildHeartbeatStatus() {
228
+ const out = {};
229
+ const now = Date.now();
230
+ for (const s of this.opts.localSkills) {
231
+ out[s.slug] = {
232
+ pid: s.server_pid,
233
+ rss_mb: 0, // M1 registry 暂不存内存; 后续可通过 process.memoryUsage 读
234
+ healthy: true,
235
+ last_check_at: s.last_health_at ?? now,
236
+ };
237
+ }
238
+ return out;
239
+ }
240
+ handleHeartbeatTimeout() {
241
+ console.error('[skill-tunnel] heartbeat timeout — reconnecting');
242
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
243
+ try {
244
+ this.ws.close(TUNNEL_CONSTANTS.HEARTBEAT_INTERVAL_MS, 'heartbeat timeout');
245
+ }
246
+ catch {
247
+ // ignore
248
+ }
249
+ }
250
+ // close 事件会触发 reconnect 调度
251
+ }
252
+ setStatus(s) {
253
+ if (this.status === s)
254
+ return;
255
+ this.status = s;
256
+ this.opts.onStatusChange?.(s);
257
+ }
258
+ }
259
+ export { TUNNEL_ERROR_CODES };
260
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/skill-tunnel/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import type { TunnelHandshakeHeaders } from '@skillfm/contracts/skill-tunnel';
2
+ import type { LocalSkillRegistryEntry } from '../skill-runner/types.js';
3
+ export interface BuildHandshakeInput {
4
+ agentToken: string;
5
+ /** SKILLFM_SKILL_AUTH_SECRET — HMAC 共享密钥 */
6
+ authSecret: string;
7
+ /** 本地已跑的 skill daemon 列表(来自 M1 readRegistry)*/
8
+ localSkills: LocalSkillRegistryEntry[];
9
+ /** @skillfm/local 运行时版本 */
10
+ skillfmLocalVersion: string;
11
+ }
12
+ export interface HandshakeArtifact {
13
+ headers: TunnelHandshakeHeaders;
14
+ /** 本次 nonce(保留供上层重放检测/调试;正式请求不传)*/
15
+ nonce: string;
16
+ }
17
+ /** 生成 16-byte nonce hex(契约 §5.1 16 byte hex = 32 字符)*/
18
+ export declare function generateNonce(): string;
19
+ export declare function computeAgentId(agentToken: string): string;
20
+ export declare function computeTunnelSignature(agentToken: string, nonce: string, authSecret: string): string;
21
+ /** 组装 `slug@version,slug@version` */
22
+ export declare function formatLocalSkills(skills: LocalSkillRegistryEntry[]): string;
23
+ /** 组装 `slug=http://127.0.0.1:port,slug=http://127.0.0.1:port` */
24
+ export declare function formatLocalSkillEndpoints(skills: LocalSkillRegistryEntry[]): string;
25
+ /**
26
+ * 构造一次握手所需的全部 9 个头。
27
+ * 每次 connect / reconnect 必须调用一次(nonce 随机)。
28
+ */
29
+ export declare function buildHandshakeHeaders(input: BuildHandshakeInput): HandshakeArtifact;
30
+ /**
31
+ * JSON-RPC meta.hmac 签名: HMAC-SHA256(id || method || JSON.stringify(params), secret)
32
+ * DELTA §5.4
33
+ */
34
+ export declare function computeRpcMetaHmac(id: string, method: string, params: unknown, authSecret: string): string;
35
+ //# sourceMappingURL=handshake.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handshake.d.ts","sourceRoot":"","sources":["../../src/skill-tunnel/handshake.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AAC9E,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AAExE,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,4CAA4C;IAC5C,UAAU,EAAE,MAAM,CAAC;IACnB,+CAA+C;IAC/C,WAAW,EAAE,uBAAuB,EAAE,CAAC;IACvC,2BAA2B;IAC3B,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,sBAAsB,CAAC;IAChC,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAC;CACf;AAED,uDAAuD;AACvD,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,GACjB,MAAM,CAIR;AAED,qCAAqC;AACrC,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,uBAAuB,EAAE,GAAG,MAAM,CAE3E;AAED,iEAAiE;AACjE,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,uBAAuB,EAAE,GAChC,MAAM,CAER;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,mBAAmB,GACzB,iBAAiB,CAoBnB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,OAAO,EACf,UAAU,EAAE,MAAM,GACjB,MAAM,CAGR"}
@@ -0,0 +1,61 @@
1
+ // M2 SkillTunnelClient — handshake 头构造 & 签名
2
+ //
3
+ // 对应契约: shared/contracts/envelope-v2/skill-tunnel.ts `TunnelHandshakeHeaders`
4
+ // 9 个必填头:
5
+ // x-agent-token, x-agent-id, x-local-skills, x-local-skill-endpoints,
6
+ // x-tunnel-nonce, x-tunnel-signature, x-skillfm-local-version
7
+ //
8
+ // 签名算法 (DELTA §5.1):
9
+ // x-agent-id = SHA-256(agent_token) hex
10
+ // x-tunnel-signature = HMAC-SHA256(agent_token || nonce, SKILLFM_SKILL_AUTH_SECRET) hex
11
+ //
12
+ // 重放防护: 每次 connect (含 reconnect) 必须重新生成 nonce。
13
+ import { createHash, createHmac, randomBytes } from 'node:crypto';
14
+ /** 生成 16-byte nonce hex(契约 §5.1 16 byte hex = 32 字符)*/
15
+ export function generateNonce() {
16
+ return randomBytes(16).toString('hex');
17
+ }
18
+ export function computeAgentId(agentToken) {
19
+ return createHash('sha256').update(agentToken).digest('hex');
20
+ }
21
+ export function computeTunnelSignature(agentToken, nonce, authSecret) {
22
+ return createHmac('sha256', authSecret)
23
+ .update(agentToken + nonce)
24
+ .digest('hex');
25
+ }
26
+ /** 组装 `slug@version,slug@version` */
27
+ export function formatLocalSkills(skills) {
28
+ return skills.map((s) => `${s.slug}@${s.version}`).join(',');
29
+ }
30
+ /** 组装 `slug=http://127.0.0.1:port,slug=http://127.0.0.1:port` */
31
+ export function formatLocalSkillEndpoints(skills) {
32
+ return skills.map((s) => `${s.slug}=${s.endpoint}`).join(',');
33
+ }
34
+ /**
35
+ * 构造一次握手所需的全部 9 个头。
36
+ * 每次 connect / reconnect 必须调用一次(nonce 随机)。
37
+ */
38
+ export function buildHandshakeHeaders(input) {
39
+ const nonce = generateNonce();
40
+ const agentId = computeAgentId(input.agentToken);
41
+ const signature = computeTunnelSignature(input.agentToken, nonce, input.authSecret);
42
+ const headers = {
43
+ 'x-agent-token': input.agentToken,
44
+ 'x-agent-id': agentId,
45
+ 'x-local-skills': formatLocalSkills(input.localSkills),
46
+ 'x-local-skill-endpoints': formatLocalSkillEndpoints(input.localSkills),
47
+ 'x-tunnel-nonce': nonce,
48
+ 'x-tunnel-signature': signature,
49
+ 'x-skillfm-local-version': input.skillfmLocalVersion,
50
+ };
51
+ return { headers, nonce };
52
+ }
53
+ /**
54
+ * JSON-RPC meta.hmac 签名: HMAC-SHA256(id || method || JSON.stringify(params), secret)
55
+ * DELTA §5.4
56
+ */
57
+ export function computeRpcMetaHmac(id, method, params, authSecret) {
58
+ const payload = id + method + JSON.stringify(params ?? {});
59
+ return createHmac('sha256', authSecret).update(payload).digest('hex');
60
+ }
61
+ //# sourceMappingURL=handshake.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handshake.js","sourceRoot":"","sources":["../../src/skill-tunnel/handshake.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAC5C,EAAE;AACF,8EAA8E;AAC9E,UAAU;AACV,wEAAwE;AACxE,gEAAgE;AAChE,EAAE;AACF,qBAAqB;AACrB,0CAA0C;AAC1C,0FAA0F;AAC1F,EAAE;AACF,+CAA+C;AAE/C,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAoBlE,uDAAuD;AACvD,MAAM,UAAU,aAAa;IAC3B,OAAO,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,UAAkB;IAC/C,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC/D,CAAC;AAED,MAAM,UAAU,sBAAsB,CACpC,UAAkB,EAClB,KAAa,EACb,UAAkB;IAElB,OAAO,UAAU,CAAC,QAAQ,EAAE,UAAU,CAAC;SACpC,MAAM,CAAC,UAAU,GAAG,KAAK,CAAC;SAC1B,MAAM,CAAC,KAAK,CAAC,CAAC;AACnB,CAAC;AAED,qCAAqC;AACrC,MAAM,UAAU,iBAAiB,CAAC,MAAiC;IACjE,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC/D,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,yBAAyB,CACvC,MAAiC;IAEjC,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAChE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CACnC,KAA0B;IAE1B,MAAM,KAAK,GAAG,aAAa,EAAE,CAAC;IAC9B,MAAM,OAAO,GAAG,cAAc,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IACjD,MAAM,SAAS,GAAG,sBAAsB,CACtC,KAAK,CAAC,UAAU,EAChB,KAAK,EACL,KAAK,CAAC,UAAU,CACjB,CAAC;IAEF,MAAM,OAAO,GAA2B;QACtC,eAAe,EAAE,KAAK,CAAC,UAAU;QACjC,YAAY,EAAE,OAAO;QACrB,gBAAgB,EAAE,iBAAiB,CAAC,KAAK,CAAC,WAAW,CAAC;QACtD,yBAAyB,EAAE,yBAAyB,CAAC,KAAK,CAAC,WAAW,CAAC;QACvE,gBAAgB,EAAE,KAAK;QACvB,oBAAoB,EAAE,SAAS;QAC/B,yBAAyB,EAAE,KAAK,CAAC,mBAAmB;KACrD,CAAC;IAEF,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAC5B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAChC,EAAU,EACV,MAAc,EACd,MAAe,EACf,UAAkB;IAElB,MAAM,OAAO,GAAG,EAAE,GAAG,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;IAC3D,OAAO,UAAU,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACxE,CAAC"}
@@ -0,0 +1,34 @@
1
+ import type { HeartbeatTunnelParams } from '@skillfm/contracts/skill-tunnel';
2
+ export interface HeartbeatOptions {
3
+ /** 构造每次 heartbeat 的 local_skill_status 快照 */
4
+ buildStatusSnapshot: () => HeartbeatTunnelParams['local_skill_status'];
5
+ /** 发 heartbeat request(由 client 负责 JSON-RPC 封装 + WS send)*/
6
+ sendHeartbeat: (params: HeartbeatTunnelParams) => Promise<void>;
7
+ /** 心跳超时回调(调用方通常触发重连)*/
8
+ onTimeout: () => void;
9
+ /** 可选: 测试时注入 clock */
10
+ nowMs?: () => number;
11
+ /** 可选: 覆盖 interval(默认 25s)*/
12
+ intervalMs?: number;
13
+ /** 可选: 覆盖 timeout(默认 60s)*/
14
+ timeoutMs?: number;
15
+ }
16
+ export declare class HeartbeatLoop {
17
+ private readonly opts;
18
+ private interval;
19
+ private timeoutTimer;
20
+ private lastAckAt;
21
+ private started;
22
+ constructor(opts: HeartbeatOptions);
23
+ start(): void;
24
+ stop(): void;
25
+ /** 收到 heartbeat ack (JSON-RPC success response) —— 上层 client 调用 */
26
+ onAck(): void;
27
+ /** 暴露给测试: 手动触发一次 tick */
28
+ tick(): Promise<void>;
29
+ /** 暴露给测试: 手动触发一次超时检查 */
30
+ checkTimeout(): void;
31
+ private scheduleTimeoutCheck;
32
+ get lastAck(): number;
33
+ }
34
+ //# sourceMappingURL=heartbeat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"heartbeat.d.ts","sourceRoot":"","sources":["../../src/skill-tunnel/heartbeat.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AAE7E,MAAM,WAAW,gBAAgB;IAC/B,6CAA6C;IAC7C,mBAAmB,EAAE,MAAM,qBAAqB,CAAC,oBAAoB,CAAC,CAAC;IACvE,4DAA4D;IAC5D,aAAa,EAAE,CAAC,MAAM,EAAE,qBAAqB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,uBAAuB;IACvB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,sBAAsB;IACtB,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC;IACrB,6BAA6B;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4BAA4B;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAmB;IACxC,OAAO,CAAC,QAAQ,CAA+B;IAC/C,OAAO,CAAC,YAAY,CAA+B;IACnD,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,OAAO,CAAS;gBAEZ,IAAI,EAAE,gBAAgB;IAKlC,KAAK,IAAI,IAAI;IAgBb,IAAI,IAAI,IAAI;IAYZ,mEAAmE;IACnE,KAAK,IAAI,IAAI;IAIb,yBAAyB;IACnB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAY3B,wBAAwB;IACxB,YAAY,IAAI,IAAI;IAQpB,OAAO,CAAC,oBAAoB;IAY5B,IAAI,OAAO,IAAI,MAAM,CAEpB;CACF"}
@@ -0,0 +1,86 @@
1
+ // M2 SkillTunnelClient — Heartbeat 调度
2
+ //
3
+ // 行为 (DELTA §5 + TUNNEL_CONSTANTS):
4
+ // - 每 HEARTBEAT_INTERVAL_MS (25s) 主动发 tunnel.heartbeat JSON-RPC request
5
+ // - 记录 lastHeartbeatAckAt
6
+ // - 若 (now - lastHeartbeatAckAt) > HEARTBEAT_TIMEOUT_MS (60s) → 触发 onTimeout 回调
7
+ // - local_skill_status 快照从外部注入(来自 registry + isAlive)
8
+ import { TUNNEL_CONSTANTS } from '@skillfm/contracts/skill-tunnel';
9
+ export class HeartbeatLoop {
10
+ opts;
11
+ interval = null;
12
+ timeoutTimer = null;
13
+ lastAckAt;
14
+ started = false;
15
+ constructor(opts) {
16
+ this.opts = opts;
17
+ this.lastAckAt = (opts.nowMs ?? Date.now)();
18
+ }
19
+ start() {
20
+ if (this.started)
21
+ return;
22
+ this.started = true;
23
+ this.lastAckAt = (this.opts.nowMs ?? Date.now)();
24
+ const intervalMs = this.opts.intervalMs ?? TUNNEL_CONSTANTS.HEARTBEAT_INTERVAL_MS;
25
+ // 立即触发一次(可选,这里延到 intervalMs 后,避免 connect 瞬间抢跑)
26
+ this.interval = setInterval(() => {
27
+ void this.tick();
28
+ }, intervalMs);
29
+ if (typeof this.interval.unref === 'function') {
30
+ this.interval.unref();
31
+ }
32
+ // 启动 timeout 巡检
33
+ this.scheduleTimeoutCheck();
34
+ }
35
+ stop() {
36
+ this.started = false;
37
+ if (this.interval) {
38
+ clearInterval(this.interval);
39
+ this.interval = null;
40
+ }
41
+ if (this.timeoutTimer) {
42
+ clearTimeout(this.timeoutTimer);
43
+ this.timeoutTimer = null;
44
+ }
45
+ }
46
+ /** 收到 heartbeat ack (JSON-RPC success response) —— 上层 client 调用 */
47
+ onAck() {
48
+ this.lastAckAt = (this.opts.nowMs ?? Date.now)();
49
+ }
50
+ /** 暴露给测试: 手动触发一次 tick */
51
+ async tick() {
52
+ try {
53
+ const now = (this.opts.nowMs ?? Date.now)();
54
+ await this.opts.sendHeartbeat({
55
+ ts: now,
56
+ local_skill_status: this.opts.buildStatusSnapshot(),
57
+ });
58
+ }
59
+ catch {
60
+ // 发送失败不打爆,让 timeout 检测兜底
61
+ }
62
+ }
63
+ /** 暴露给测试: 手动触发一次超时检查 */
64
+ checkTimeout() {
65
+ const now = (this.opts.nowMs ?? Date.now)();
66
+ const timeoutMs = this.opts.timeoutMs ?? TUNNEL_CONSTANTS.HEARTBEAT_TIMEOUT_MS;
67
+ if (now - this.lastAckAt > timeoutMs) {
68
+ this.opts.onTimeout();
69
+ }
70
+ }
71
+ scheduleTimeoutCheck() {
72
+ const timeoutMs = this.opts.timeoutMs ?? TUNNEL_CONSTANTS.HEARTBEAT_TIMEOUT_MS;
73
+ // 每 (timeoutMs / 4) 检查一次
74
+ const pollMs = Math.max(1_000, Math.floor(timeoutMs / 4));
75
+ this.timeoutTimer = setInterval(() => {
76
+ this.checkTimeout();
77
+ }, pollMs);
78
+ if (typeof this.timeoutTimer.unref === 'function') {
79
+ this.timeoutTimer.unref();
80
+ }
81
+ }
82
+ get lastAck() {
83
+ return this.lastAckAt;
84
+ }
85
+ }
86
+ //# sourceMappingURL=heartbeat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"heartbeat.js","sourceRoot":"","sources":["../../src/skill-tunnel/heartbeat.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import type { JsonRpcErrorResponse, JsonRpcSuccessResponse } from '@skillfm/contracts/skill-tunnel';
2
+ export interface LocalBridgeDeps {
3
+ /** 通过 slug 查本地 endpoint */
4
+ resolveEndpoint: (slug: string) => string | null;
5
+ /** 与本地 skill daemon 共享的 HMAC secret(§3 auth)。
6
+ * 若给定,则在发往 127.0.0.1 的 HTTP 请求上加 X-Skill-Auth header。*/
7
+ skillAuthSecret?: string;
8
+ /** 单次 HTTP 请求超时(ms),默认 30s */
9
+ fetchTimeoutMs?: number;
10
+ /** 可注入,方便测试(默认 globalThis.fetch)*/
11
+ fetch?: typeof fetch;
12
+ }
13
+ export declare class LocalBridge {
14
+ private readonly deps;
15
+ constructor(deps: LocalBridgeDeps);
16
+ /**
17
+ * 把 server→agent 的 JSON-RPC request 转发到本地 skill daemon。
18
+ * 返回 JsonRpcSuccessResponse 或 JsonRpcErrorResponse(本地不可达等)。
19
+ * 异常不抛出,一律封装成 error response —— 上层 client 按 id 回传。
20
+ */
21
+ dispatch(id: string, method: string, params: unknown): Promise<JsonRpcSuccessResponse | JsonRpcErrorResponse>;
22
+ private isKnownServerMethod;
23
+ private handleSkillRun;
24
+ private handleToolInvoke;
25
+ private handleSkillPost;
26
+ private handleSkillGet;
27
+ private httpJson;
28
+ private errorResponse;
29
+ }
30
+ //# sourceMappingURL=local-bridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-bridge.d.ts","sourceRoot":"","sources":["../../src/skill-tunnel/local-bridge.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EACV,oBAAoB,EACpB,sBAAsB,EAIvB,MAAM,iCAAiC,CAAC;AAIzC,MAAM,WAAW,eAAe;IAC9B,2BAA2B;IAC3B,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IACjD;6DACyD;IACzD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,8BAA8B;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mCAAmC;IACnC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACtB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAkB;gBAE3B,IAAI,EAAE,eAAe;IAIjC;;;;OAIG;IACG,QAAQ,CACZ,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,OAAO,GACd,OAAO,CAAC,sBAAsB,GAAG,oBAAoB,CAAC;IAyDzD,OAAO,CAAC,mBAAmB;YAYb,cAAc;YAkBd,gBAAgB;YA2BhB,eAAe;YAmBf,cAAc;YAmBd,QAAQ;IAsEtB,OAAO,CAAC,aAAa;CAYtB"}