shennian 0.2.119 → 0.2.121
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/publish-build-manifest.json +26 -21
- package/dist/src/channels/base.d.ts +2 -0
- package/dist/src/channels/registry.d.ts +1 -0
- package/dist/src/channels/registry.js +1 -1
- package/dist/src/channels/runtime.d.ts +5 -0
- package/dist/src/channels/runtime.js +2 -2
- package/dist/src/channels/secret-registry.d.ts +1 -0
- package/dist/src/channels/secret-registry.js +1 -1
- package/dist/src/channels/wechat-channel/runner.d.ts +1 -0
- package/dist/src/channels/wechat-channel/runner.js +1 -1
- package/dist/src/channels/wechat-rpa-session-sync.d.ts +24 -0
- package/dist/src/channels/wechat-rpa-session-sync.js +1 -0
- package/dist/src/commands/helper-runtime-official-download.js +1 -1
- package/dist/src/commands/runtime.d.ts +7 -0
- package/dist/src/commands/runtime.js +1 -1
- package/dist/src/config/index.js +1 -1
- package/dist/src/manager/runtime.d.ts +5 -0
- package/dist/src/manager/runtime.js +14 -14
- package/dist/src/session/handlers/chat.js +2 -2
- package/dist/src/session/handlers/wechat-helper-runtime.js +1 -1
- package/package.json +2 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"jsMinified": true,
|
|
4
4
|
"minifier": "esbuild",
|
|
5
|
-
"fileCount":
|
|
5
|
+
"fileCount": 135,
|
|
6
6
|
"files": [
|
|
7
7
|
{
|
|
8
8
|
"file": "bin/shennian.js",
|
|
@@ -146,8 +146,8 @@
|
|
|
146
146
|
},
|
|
147
147
|
{
|
|
148
148
|
"file": "src/channels/registry.js",
|
|
149
|
-
"beforeBytes":
|
|
150
|
-
"afterBytes":
|
|
149
|
+
"beforeBytes": 1569,
|
|
150
|
+
"afterBytes": 705
|
|
151
151
|
},
|
|
152
152
|
{
|
|
153
153
|
"file": "src/channels/reply-split.js",
|
|
@@ -156,13 +156,13 @@
|
|
|
156
156
|
},
|
|
157
157
|
{
|
|
158
158
|
"file": "src/channels/runtime.js",
|
|
159
|
-
"beforeBytes":
|
|
160
|
-
"afterBytes":
|
|
159
|
+
"beforeBytes": 33505,
|
|
160
|
+
"afterBytes": 17867
|
|
161
161
|
},
|
|
162
162
|
{
|
|
163
163
|
"file": "src/channels/secret-registry.js",
|
|
164
|
-
"beforeBytes":
|
|
165
|
-
"afterBytes":
|
|
164
|
+
"beforeBytes": 1738,
|
|
165
|
+
"afterBytes": 722
|
|
166
166
|
},
|
|
167
167
|
{
|
|
168
168
|
"file": "src/channels/websocket.js",
|
|
@@ -286,8 +286,8 @@
|
|
|
286
286
|
},
|
|
287
287
|
{
|
|
288
288
|
"file": "src/channels/wechat-channel/runner.js",
|
|
289
|
-
"beforeBytes":
|
|
290
|
-
"afterBytes":
|
|
289
|
+
"beforeBytes": 16219,
|
|
290
|
+
"afterBytes": 7723
|
|
291
291
|
},
|
|
292
292
|
{
|
|
293
293
|
"file": "src/channels/wechat-channel/runtime.js",
|
|
@@ -324,6 +324,11 @@
|
|
|
324
324
|
"beforeBytes": 18089,
|
|
325
325
|
"afterBytes": 9740
|
|
326
326
|
},
|
|
327
|
+
{
|
|
328
|
+
"file": "src/channels/wechat-rpa-session-sync.js",
|
|
329
|
+
"beforeBytes": 6139,
|
|
330
|
+
"afterBytes": 2201
|
|
331
|
+
},
|
|
327
332
|
{
|
|
328
333
|
"file": "src/channels/wechat-rpa.js",
|
|
329
334
|
"beforeBytes": 55678,
|
|
@@ -366,8 +371,8 @@
|
|
|
366
371
|
},
|
|
367
372
|
{
|
|
368
373
|
"file": "src/commands/helper-runtime-official-download.js",
|
|
369
|
-
"beforeBytes":
|
|
370
|
-
"afterBytes":
|
|
374
|
+
"beforeBytes": 12611,
|
|
375
|
+
"afterBytes": 6719
|
|
371
376
|
},
|
|
372
377
|
{
|
|
373
378
|
"file": "src/commands/manager.js",
|
|
@@ -386,8 +391,8 @@
|
|
|
386
391
|
},
|
|
387
392
|
{
|
|
388
393
|
"file": "src/commands/runtime.js",
|
|
389
|
-
"beforeBytes":
|
|
390
|
-
"afterBytes":
|
|
394
|
+
"beforeBytes": 39493,
|
|
395
|
+
"afterBytes": 21665
|
|
391
396
|
},
|
|
392
397
|
{
|
|
393
398
|
"file": "src/commands/tools.js",
|
|
@@ -446,8 +451,8 @@
|
|
|
446
451
|
},
|
|
447
452
|
{
|
|
448
453
|
"file": "src/config/index.js",
|
|
449
|
-
"beforeBytes":
|
|
450
|
-
"afterBytes":
|
|
454
|
+
"beforeBytes": 1702,
|
|
455
|
+
"afterBytes": 1036
|
|
451
456
|
},
|
|
452
457
|
{
|
|
453
458
|
"file": "src/daemon-log.js",
|
|
@@ -521,8 +526,8 @@
|
|
|
521
526
|
},
|
|
522
527
|
{
|
|
523
528
|
"file": "src/manager/runtime.js",
|
|
524
|
-
"beforeBytes":
|
|
525
|
-
"afterBytes":
|
|
529
|
+
"beforeBytes": 65033,
|
|
530
|
+
"afterBytes": 33175
|
|
526
531
|
},
|
|
527
532
|
{
|
|
528
533
|
"file": "src/native-fusion/config.js",
|
|
@@ -586,8 +591,8 @@
|
|
|
586
591
|
},
|
|
587
592
|
{
|
|
588
593
|
"file": "src/session/handlers/chat.js",
|
|
589
|
-
"beforeBytes":
|
|
590
|
-
"afterBytes":
|
|
594
|
+
"beforeBytes": 31287,
|
|
595
|
+
"afterBytes": 14096
|
|
591
596
|
},
|
|
592
597
|
{
|
|
593
598
|
"file": "src/session/handlers/control.js",
|
|
@@ -621,8 +626,8 @@
|
|
|
621
626
|
},
|
|
622
627
|
{
|
|
623
628
|
"file": "src/session/handlers/wechat-helper-runtime.js",
|
|
624
|
-
"beforeBytes":
|
|
625
|
-
"afterBytes":
|
|
629
|
+
"beforeBytes": 7107,
|
|
630
|
+
"afterBytes": 3846
|
|
626
631
|
},
|
|
627
632
|
{
|
|
628
633
|
"file": "src/session/manager.js",
|
|
@@ -13,6 +13,7 @@ export type ExternalChannelConfig = {
|
|
|
13
13
|
modelId?: string | null;
|
|
14
14
|
enabled: boolean;
|
|
15
15
|
secretRef: string;
|
|
16
|
+
managedBy?: 'session-sync';
|
|
16
17
|
};
|
|
17
18
|
export type ExternalChannelView = {
|
|
18
19
|
id: string;
|
|
@@ -56,6 +57,7 @@ export type ExternalChannelView = {
|
|
|
56
57
|
wechatRpaPreflightChecks?: ExternalChannelSessionStatus['wechatRpaPreflightChecks'];
|
|
57
58
|
wechatRpaServerDecisionAvailable?: boolean | null;
|
|
58
59
|
wechatRpaPrivacyConsentAccepted?: boolean | null;
|
|
60
|
+
managedBy?: 'session-sync';
|
|
59
61
|
};
|
|
60
62
|
export type ExternalMessageEvent = {
|
|
61
63
|
type: 'external.message';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import t from"node:fs";import s from"node:path";import{resolveShennianPath as a}from"../config/index.js";function n(){return a("channels.json")}class f{list(){try{return JSON.parse(t.readFileSync(n(),"utf-8")).channels??[]}catch{return[]}}get(e){return this.list().find(r=>r.id===e)}upsert(e){const r=this.list().filter(i=>i.id!==e.id);r.push(e),t.mkdirSync(s.dirname(n()),{recursive:!0}),t.writeFileSync(n(),JSON.stringify({channels:r},null,2))}replaceAll(e){t.mkdirSync(s.dirname(n()),{recursive:!0}),t.writeFileSync(n(),JSON.stringify({channels:e},null,2))}remove(e){const r=this.list(),i=r.filter(l=>l.id!==e);return i.length===r.length?!1:(this.replaceAll(i),!0)}}export{f as ChannelConfigRegistry};
|
|
@@ -122,7 +122,12 @@ export declare class ChannelRuntime {
|
|
|
122
122
|
deferInitialPoll?: boolean;
|
|
123
123
|
privacyConsentAccepted?: boolean;
|
|
124
124
|
flowScriptPath?: string;
|
|
125
|
+
managedBy?: 'session-sync';
|
|
125
126
|
}): Promise<ExternalChannelView>;
|
|
127
|
+
listChannelIdsManagedBy(managedBy: 'session-sync'): string[];
|
|
128
|
+
deleteManagerWeChatRpaChannel(channelId: string): Promise<{
|
|
129
|
+
removed: boolean;
|
|
130
|
+
}>;
|
|
126
131
|
private findConflictingWeChatRpaBinding;
|
|
127
132
|
}
|
|
128
133
|
export type ExternalReplySendPlanItem = {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import M from"node:crypto";import u from"node:fs";import h from"node:path";import{ChannelConfigRegistry as v}from"./registry.js";import{ChannelSecretRegistry as N}from"./secret-registry.js";import{WeComChannelAdapter as D}from"./wecom.js";import{WeChatRpaChannelAdapter as B}from"./wechat-rpa.js";import{ExternalWebSocketChannelAdapter as x}from"./websocket.js";import{splitExternalReplyText as E}from"./reply-split.js";class J{onExternalMessage;createReplyTarget;configs=new v;secrets=new N;adapters=new Map;completedReplyKeys=new Map;recentMessages=new Map;constructor(e,t,n={}){this.onExternalMessage=e,this.createReplyTarget=t;const o=new D(a=>this.ingest({...a,type:"external.message"}));this.adapters.set(o.type,o);const r=new x(a=>this.ingest({...a,type:"external.message"}));this.adapters.set(r.type,r);const i=new B(a=>this.ingest(a),{createProductRunner:n.createWeChatRpaProductRunner,automationLane:n.weChatAutomationLane});this.adapters.set(i.type,i)}async start(){for(const e of this.configs.list().filter(t=>t.enabled))await this.adapters.get(e.type)?.connect(e).catch(()=>{})}async stop(){for(const e of this.configs.list())await this.adapters.get(e.type)?.disconnect(e).catch(()=>{})}ingest(e){const t=this.findRecentMessageDuplicate(e);if(t)return t;const n=this.configs.get(e.channelId),o=e.managerSessionId??n?.sessionId??n?.managerSessionId;if(!o)throw new Error(`No session bound for channel ${e.channelId}`);const r=e.replyTarget||this.createReplyTarget({managerSessionId:o,channelId:e.channelId,conversationId:e.conversationId,messageId:e.messageId}),i={...e,replyTarget:r};return this.onExternalMessage(o,i),this.recordRecentMessage(i),i}async reply(e){const t=this.configs.get(e.channelId);if(!t)return{ok:!1,error:`Unknown channel: ${e.channelId}`};if((t.sessionId??t.managerSessionId)!==e.managerSessionId)return{ok:!1,error:"Channel is not bound to this session"};const n=this.adapters.get(t.type);if(!n)return{ok:!1,error:`Unsupported channel type: ${t.type}`};try{const o=F(t.type,e);if(!o.length)return{ok:!1,error:"Reply text or attachment is required"};let r=!1;for(const i of o){const a=i.idempotencyKey;if(a&&this.isReplyCompleted(t,e.conversationId,a))continue;const d=await n.send(t,{...e,...i});if(d?.status==="queued"){r=!0;continue}if(d?.status==="manual-review")return{ok:!1,error:d.reason||"Reply requires manual review before retry"};a&&this.markReplyCompleted(t,e.conversationId,a)}return r?{ok:!0,pending:!0}:{ok:!0}}catch(o){return{ok:!1,error:o instanceof Error?o.message:String(o)}}}isReplyCompleted(e,t,n){return this.loadReplyCompletionSet(e).has(A(e.id,t,n))}markReplyCompleted(e,t,n){const o=this.loadReplyCompletionSet(e);o.add(A(e.id,t,n));try{$(e.workDir,o)}catch{}}loadReplyCompletionSet(e){const t=h.resolve(e.workDir),n=this.completedReplyKeys.get(t);if(n)return n;const o=T(e.workDir);return this.completedReplyKeys.set(t,o),o}async getDefaultReplyTarget(e){const t=this.configs.list().find(r=>(r.sessionId??r.managerSessionId)===e&&r.enabled);if(!t)throw new Error("No enabled external channel is bound to this session");const n=this.adapters.get(t.type);if(!n?.defaultConversation)throw new Error(`External channel ${t.type} has no default conversation`);const o=await n.defaultConversation(t);return{channelId:t.id,conversationId:o.conversationId}}getManagerChannel(e,t,n={}){const o=this.configs.list().filter(d=>(d.sessionId??d.managerSessionId)===e&&d.type===t),r=o.find(d=>d.enabled)??o.at(-1);if(!r)return null;const i=this.secrets.get(r.secretRef),a=this.adapters.get(r.type)?.runtimeStatus?.(r)??{};return{id:r.id,type:r.type,name:r.name,sessionId:r.sessionId??r.managerSessionId,managerSessionId:r.managerSessionId,workDir:r.workDir,agentType:r.agentType,agentSessionId:r.agentSessionId,modelId:r.modelId,enabled:r.enabled,wsUrl:i?.wsUrl??"",token:n.includeSecret?i?.token??"":"",tokenConfigured:!!i?.token,canReply:!!i?.canReply,systemPrompt:typeof i?.systemPrompt=="string"?i.systemPrompt:"",...C(i),...a}}getChannelById(e,t={}){const n=this.configs.get(e);if(!n)return null;const o=this.secrets.get(n.secretRef),r=this.adapters.get(n.type)?.runtimeStatus?.(n)??{};return{id:n.id,type:n.type,name:n.name,sessionId:n.sessionId??n.managerSessionId,managerSessionId:n.managerSessionId,workDir:n.workDir,agentType:n.agentType,agentSessionId:n.agentSessionId,modelId:n.modelId,enabled:n.enabled,wsUrl:o?.wsUrl??"",token:t.includeSecret?o?.token??"":"",tokenConfigured:!!o?.token,canReply:!!o?.canReply,systemPrompt:typeof o?.systemPrompt=="string"?o.systemPrompt:"",...C(o),...r}}getChannelStatusById(e){const t=this.configs.get(e);if(!t)return null;const n=this.secrets.get(t.secretRef),o=this.adapters.get(t.type)?.runtimeStatus?.(t)??{};return{configured:!0,connected:y(t,n),type:t.type,channelId:t.id,name:t.name,canReply:!!n?.canReply,systemPrompt:typeof n?.systemPrompt=="string"?n.systemPrompt:"",...w(n),...o}}getManagerChannelStatus(e){const t=this.configs.list().find(r=>(r.sessionId??r.managerSessionId)===e&&r.enabled);if(!t)return null;const n=this.secrets.get(t.secretRef),o=this.adapters.get(t.type)?.runtimeStatus?.(t)??{};return{configured:!0,connected:y(t,n),type:t.type,channelId:t.id,name:t.name,canReply:!!n?.canReply,systemPrompt:typeof n?.systemPrompt=="string"?n.systemPrompt:"",...w(n),...o}}listManagerChannelStatuses(){return this.configs.list().filter(e=>e.enabled).map(e=>({managerSessionId:e.sessionId??e.managerSessionId,status:this.getManagerChannelStatus(e.sessionId??e.managerSessionId)})).filter(e=>!!e.status)}listManagerExternalChannels(e){return this.configs.list().filter(t=>t.enabled&&(t.sessionId??t.managerSessionId)===e).map(t=>{const n=this.secrets.get(t.secretRef),o=this.adapters.get(t.type)?.runtimeStatus?.(t)??{};return{configured:!0,connected:y(t,n),type:t.type,channelId:t.id,name:t.name,canReply:!!n?.canReply,systemPrompt:typeof n?.systemPrompt=="string"?n.systemPrompt:"",...w(n),...o}})}async syncManagerWeChatRpaChannel(e){const t=this.configs.list().find(a=>a.enabled&&a.type==="wechat-rpa"&&(a.sessionId??a.managerSessionId)===e);if(!t)throw new Error("No enabled WeChat RPA channel is bound to this session");const n=this.adapters.get(t.type);if(!n?.syncNow)throw new Error("WeChat RPA channel does not support manual sync");const o=Date.now()-120*1e3,r=await n.syncNow(t)??[],i=K(r,this.getRecentMessages(t.id,o));return{channel:this.getManagerChannel(e,"wechat-rpa",{includeSecret:!0}),messages:r,recentMessages:i}}async cancelManagerWeChatRpaOutbound(e){const t=this.configs.list().find(r=>r.enabled&&r.type==="wechat-rpa"&&(r.sessionId??r.managerSessionId)===e.managerSessionId);if(!t)throw new Error("No enabled WeChat RPA channel is bound to this session");const n=this.adapters.get(t.type);if(!n?.cancelOutbound)throw new Error("WeChat RPA channel does not support outbound cancellation");const o=await n.cancelOutbound(t,{idempotencyKey:e.idempotencyKey,replyId:e.replyId,reason:e.reason});return{cancelled:o.cancelled,status:o.status,channel:this.getManagerChannel(e.managerSessionId,"wechat-rpa",{includeSecret:!0})}}recordRecentMessage(e){const t=this.recentMessages.get(e.channelId)??[];this.findRecentMessageDuplicate(e)||(t.push(e),this.recentMessages.set(e.channelId,t.slice(-50)))}findRecentMessageDuplicate(e){const t=I(e);return t?(this.recentMessages.get(e.channelId)??[]).find(o=>I(o)===t)??null:null}getRecentMessages(e,t){return(this.recentMessages.get(e)??[]).filter(o=>{const r=Date.parse(o.receivedAt);return!Number.isFinite(r)||r>=t})}async upsertManagerChannel(e){const t=this.configs.get(e.id),n=this.configs.list(),o=e.sessionId||e.managerSessionId,r={id:e.id,type:e.type,name:e.name?.trim()||t?.name||"\u5916\u90E8\u6D88\u606F\u901A\u9053",sessionId:o,managerSessionId:o,workDir:e.workDir,agentType:e.agentType||t?.agentType,agentSessionId:e.agentSessionId??t?.agentSessionId??null,modelId:e.modelId??t?.modelId??null,enabled:e.enabled,secretRef:t?.secretRef||`channel:${e.id}`},i=this.secrets.get(r.secretRef),a=e.wsUrl?.trim()||i?.wsUrl||"",d=e.token?.trim()||i?.token||"",p=e.canReply??i?.canReply??!1,m=e.systemPrompt??(typeof i?.systemPrompt=="string"?i.systemPrompt:"");if(r.enabled&&(!a||!d))throw new Error("WebSocket \u5730\u5740\u548C Token \u5FC5\u586B");const f=n.filter(l=>l.id!==r.id).map(l=>(l.sessionId??l.managerSessionId)===o&&l.type===e.type?{...l,enabled:!1}:l);f.push(r),this.configs.replaceAll(f),(a||d)&&this.secrets.upsert(r.secretRef,{type:"websocket",wsUrl:a,token:d,canReply:p,systemPrompt:m});const c=this.adapters.get(r.type);for(const l of n)(l.sessionId??l.managerSessionId)===o&&l.type===e.type&&l.enabled&&await this.adapters.get(l.type)?.disconnect(l).catch(()=>{});return r.enabled&&c?.connect(r).catch(()=>{}),this.getManagerChannel(o,e.type,{includeSecret:!0})}async upsertManagerWeChatRpaChannel(e){const t=this.configs.get(e.id),n=this.configs.list(),o=e.sessionId||e.managerSessionId,r=g(e.groups);if(e.enabled&&!r.length)throw new Error("WeChat RPA \u81F3\u5C11\u9700\u8981\u914D\u7F6E\u4E00\u4E2A\u7FA4");if(e.enabled&&r.length>1)throw new Error("WeChat RPA \u6BCF\u4E2A\u5BF9\u8BDD\u53EA\u80FD\u7ED1\u5B9A\u4E00\u4E2A\u7FA4");if(e.enabled){const c=this.findConflictingWeChatRpaBinding({excludeChannelId:e.id,boundSessionId:o,groupNames:r.map(l=>l.name)});if(c)throw new Error(`\u5FAE\u4FE1\u5BF9\u8BDD\u300C${c.groupName}\u300D\u5DF2\u88AB\u53E6\u4E00\u4E2A\u667A\u80FD\u4F53\u7ED1\u5B9A(\u901A\u9053 ${c.channelName}),\u540C\u4E00\u4E2A\u5BF9\u8BDD\u53EA\u80FD\u7ED1\u5B9A\u4E00\u6B21\u3002\u8BF7\u5148\u5728\u539F\u667A\u80FD\u4F53\u89E3\u7ED1,\u518D\u91CD\u65B0\u7ED1\u5B9A\u3002`)}const i={id:e.id,type:"wechat-rpa",name:e.name?.trim()||t?.name||"\u672C\u673A\u5FAE\u4FE1 RPA",sessionId:o,managerSessionId:o,workDir:e.workDir,agentType:e.agentType||t?.agentType,agentSessionId:e.agentSessionId??t?.agentSessionId??null,modelId:e.modelId??t?.modelId??null,enabled:e.enabled,secretRef:t?.secretRef||`channel:${e.id}`},a=this.secrets.get(i.secretRef),d=e.source||(a?.source==="wechat-channel"||a?.source==="macos-probe"||a?.source==="fixture-jsonl"||a?.source==="macos-flow"||a?.source==="windows-visual-flow"||a?.source==="wechat-rpa-lab"?a.source:W());if(e.enabled&&d==="windows-visual-flow")throw new Error("\u65E7 Windows visual flow \u5DF2\u5F52\u6863\uFF1BmacOS \u548C Windows \u6B63\u5F0F\u901A\u9053\u90FD\u4F7F\u7528 wechat-channel");const p=e.privacyConsentAccepted??!!a?.privacyConsentAccepted;if(e.enabled&&!p)throw new Error("\u542F\u7528\u524D\u9700\u8981\u786E\u8BA4\u5FAE\u4FE1\u901A\u9053\u6570\u636E\u4E0E\u9690\u79C1\u6388\u6743");const m=n.filter(c=>c.id!==i.id).map(c=>(c.sessionId??c.managerSessionId)===o&&c.type==="wechat-rpa"?{...c,enabled:!1}:c);m.push(i),this.configs.replaceAll(m),this.secrets.upsert(i.secretRef,{type:"wechat-rpa",source:d,groups:r,pollIntervalMs:R(e.pollIntervalMs,a?.pollIntervalMs),recentLimit:R(e.recentLimit,a?.recentLimit),idleSeconds:R(e.idleSeconds,a?.idleSeconds),forceForeground:e.forceForeground??(a?.forceForeground===void 0?!0:!!a.forceForeground),noRestore:e.noRestore??(a?.noRestore===void 0?!0:!!a.noRestore),downloadAttachments:e.downloadAttachments??(a?.downloadAttachments===void 0?!0:!!a.downloadAttachments),downloadAttachmentsDir:e.downloadAttachmentsDir?.trim()||b(a?.downloadAttachmentsDir),selfNickname:e.selfNickname?.trim()||b(a?.selfNickname),selfTriggerMarker:e.selfTriggerMarker?.trim()||void 0,deferInitialPoll:e.deferInitialPoll===!0?!0:void 0,privacyConsentAccepted:p,flowScriptPath:e.flowScriptPath?.trim()||b(a?.flowScriptPath),canReply:e.canReply??a?.canReply??!1,systemPrompt:e.systemPrompt??(typeof a?.systemPrompt=="string"?a.systemPrompt:"")});const f=this.adapters.get(i.type);for(const c of n)(c.sessionId??c.managerSessionId)===o&&c.type==="wechat-rpa"&&c.enabled&&await this.adapters.get(c.type)?.disconnect(c).catch(()=>{});return i.enabled&&f?.connect(i).catch(c=>{console.error(`[wechat-rpa] connect failed id=${i.id}: ${c instanceof Error?c.message:String(c)}`)}),this.getManagerChannel(o,"wechat-rpa",{includeSecret:!0})}findConflictingWeChatRpaBinding(e){const t=new Set(e.groupNames);if(!t.size)return null;for(const n of this.configs.list()){if(n.type!=="wechat-rpa"||!n.enabled||n.id===e.excludeChannelId||(n.sessionId??n.managerSessionId)===e.boundSessionId)continue;const o=this.secrets.get(n.secretRef),r=g(Array.isArray(o?.groups)?o.groups:[]);for(const i of r)if(t.has(i.name))return{channelId:n.id,channelName:n.name||n.id,groupName:i.name}}return null}}function y(s,e){return!e||e.type!==s.type?!1:s.type==="wechat-rpa"?!0:s.type==="websocket"?!!(e.wsUrl&&e.token):s.type==="wecom"?!!(e.token||e.botId||e.secret):!!e.token}function C(s){return!s||s.type!=="wechat-rpa"?{}:{wechatRpaSource:typeof s.source=="string"?s.source:"",wechatRpaGroups:g(Array.isArray(s.groups)?s.groups:[]),pollIntervalMs:Number.isFinite(s.pollIntervalMs)?Number(s.pollIntervalMs):void 0,recentLimit:Number.isFinite(s.recentLimit)?Number(s.recentLimit):void 0,idleSeconds:Number.isFinite(s.idleSeconds)?Number(s.idleSeconds):void 0,forceForeground:s.forceForeground===void 0?!0:!!s.forceForeground,noRestore:s.noRestore===void 0?void 0:!!s.noRestore,downloadAttachments:s.downloadAttachments===void 0?!0:!!s.downloadAttachments,downloadAttachmentsDir:typeof s.downloadAttachmentsDir=="string"?s.downloadAttachmentsDir:"",selfNickname:typeof s.selfNickname=="string"?s.selfNickname:"",wechatRpaPrivacyConsentAccepted:!!s.privacyConsentAccepted,wechatRpaServerDecisionAvailable:!0,wechatRpaPreflightChecks:k(s)}}function w(s){return!s||s.type!=="wechat-rpa"?{}:{wechatRpaSource:typeof s.source=="string"?s.source:null,wechatRpaGroups:g(Array.isArray(s.groups)?s.groups:[]),pollIntervalMs:Number.isFinite(s.pollIntervalMs)?Number(s.pollIntervalMs):null,recentLimit:Number.isFinite(s.recentLimit)?Number(s.recentLimit):null,idleSeconds:Number.isFinite(s.idleSeconds)?Number(s.idleSeconds):null,forceForeground:s.forceForeground===void 0?!0:!!s.forceForeground,noRestore:s.noRestore===void 0?null:!!s.noRestore,downloadAttachments:s.downloadAttachments===void 0?!0:!!s.downloadAttachments,downloadAttachmentsDir:typeof s.downloadAttachmentsDir=="string"?s.downloadAttachmentsDir:null,selfNickname:typeof s.selfNickname=="string"?s.selfNickname:null,wechatRpaPrivacyConsentAccepted:!!s.privacyConsentAccepted,wechatRpaServerDecisionAvailable:!0,wechatRpaPreflightChecks:k(s)}}function g(s){const e=new Set,t=[];for(const n of s){const o=String(n?.name||"").replace(/\s+/g," ").trim();!o||e.has(o)||(e.add(o),t.push({name:o}))}return t}function W(){return"wechat-channel"}function k(s){const e=[];if(s.lastHelperPermissions&&typeof s.lastHelperPermissions=="object"){const t=s.lastHelperPermissions;e.push({code:"wechat_window_unavailable",ok:t.wechatWindowAvailable!==!1,severity:"blocking",message:t.wechatWindowAvailable===!1?"\u5FAE\u4FE1\u7A97\u53E3\u4E0D\u53EF\u7528\uFF0C\u8BF7\u6253\u5F00\u5E76\u767B\u5F55\u5FAE\u4FE1\u540E\u518D\u8BD5\u3002":"\u5FAE\u4FE1\u7A97\u53E3\u53EF\u7528\u3002"})}return e.push({code:"platform_unsupported",ok:s.source!=="windows-visual-flow",severity:"blocking",message:s.source==="windows-visual-flow"?"\u65E7 Windows visual flow \u5DF2\u5F52\u6863\uFF0C\u8BF7\u6539\u7528 wechat-channel\u3002":"\u5F53\u524D\u914D\u7F6E\u4F7F\u7528\u8DE8\u5E73\u53F0\u5FAE\u4FE1\u901A\u9053\u3002"}),e.push({code:"privacy_consent_required",ok:!!s.privacyConsentAccepted,severity:"blocking",message:s.privacyConsentAccepted?"\u5DF2\u786E\u8BA4\u5FAE\u4FE1\u901A\u9053\u6570\u636E\u4E0E\u9690\u79C1\u6388\u6743\u3002":"\u542F\u7528\u524D\u9700\u8981\u786E\u8BA4\u5FAE\u4FE1\u901A\u9053\u6570\u636E\u4E0E\u9690\u79C1\u6388\u6743\u3002"}),e.push({code:"server_decision_unavailable",ok:!0,severity:"blocking",message:"\u670D\u52A1\u7AEF\u5224\u65AD\u80FD\u529B\u53EF\u7528\u3002"}),e}function F(s,e){const t=E(e.text);if(!t.length&&!e.attachment)return[];if(s==="wechat-rpa"&&e.attachment&&t.length<=1)return[{text:t[0]??"",attachment:e.attachment,idempotencyKey:e.idempotencyKey}];const n=t.map((o,r)=>({text:o,attachment:void 0,idempotencyKey:t.length>1&&e.idempotencyKey?`${e.idempotencyKey}:${r+1}`:e.idempotencyKey}));return e.attachment&&n.push({text:"",attachment:e.attachment,idempotencyKey:t.length&&e.idempotencyKey?`${e.idempotencyKey}:attachment`:e.idempotencyKey}),n}function K(...s){const e=new Set,t=[];for(const n of s.flat()){const o=I(n);if(!o){t.push(n);continue}e.has(o)||(e.add(o),t.push(n))}return t}function I(s){const e=S(s.channelId),t=S(s.conversationId),n=S(s.messageId);return!e||!t||!n?null:`${e}
|
|
1
|
+
import M from"node:crypto";import u from"node:fs";import h from"node:path";import{ChannelConfigRegistry as P}from"./registry.js";import{ChannelSecretRegistry as N}from"./secret-registry.js";import{WeComChannelAdapter as B}from"./wecom.js";import{WeChatRpaChannelAdapter as D}from"./wechat-rpa.js";import{ExternalWebSocketChannelAdapter as x}from"./websocket.js";import{splitExternalReplyText as W}from"./reply-split.js";class J{onExternalMessage;createReplyTarget;configs=new P;secrets=new N;adapters=new Map;completedReplyKeys=new Map;recentMessages=new Map;constructor(e,t,n={}){this.onExternalMessage=e,this.createReplyTarget=t;const o=new B(r=>this.ingest({...r,type:"external.message"}));this.adapters.set(o.type,o);const a=new x(r=>this.ingest({...r,type:"external.message"}));this.adapters.set(a.type,a);const i=new D(r=>this.ingest(r),{createProductRunner:n.createWeChatRpaProductRunner,automationLane:n.weChatAutomationLane});this.adapters.set(i.type,i)}async start(){for(const e of this.configs.list().filter(t=>t.enabled))await this.adapters.get(e.type)?.connect(e).catch(()=>{})}async stop(){for(const e of this.configs.list())await this.adapters.get(e.type)?.disconnect(e).catch(()=>{})}ingest(e){const t=this.findRecentMessageDuplicate(e);if(t)return t;const n=this.configs.get(e.channelId),o=e.managerSessionId??n?.sessionId??n?.managerSessionId;if(!o)throw new Error(`No session bound for channel ${e.channelId}`);const a=e.replyTarget||this.createReplyTarget({managerSessionId:o,channelId:e.channelId,conversationId:e.conversationId,messageId:e.messageId}),i={...e,replyTarget:a};return this.onExternalMessage(o,i),this.recordRecentMessage(i),i}async reply(e){const t=this.configs.get(e.channelId);if(!t)return{ok:!1,error:`Unknown channel: ${e.channelId}`};if((t.sessionId??t.managerSessionId)!==e.managerSessionId)return{ok:!1,error:"Channel is not bound to this session"};const n=this.adapters.get(t.type);if(!n)return{ok:!1,error:`Unsupported channel type: ${t.type}`};try{const o=F(t.type,e);if(!o.length)return{ok:!1,error:"Reply text or attachment is required"};let a=!1;for(const i of o){const r=i.idempotencyKey;if(r&&this.isReplyCompleted(t,e.conversationId,r))continue;const d=await n.send(t,{...e,...i});if(d?.status==="queued"){a=!0;continue}if(d?.status==="manual-review")return{ok:!1,error:d.reason||"Reply requires manual review before retry"};r&&this.markReplyCompleted(t,e.conversationId,r)}return a?{ok:!0,pending:!0}:{ok:!0}}catch(o){return{ok:!1,error:o instanceof Error?o.message:String(o)}}}isReplyCompleted(e,t,n){return this.loadReplyCompletionSet(e).has(A(e.id,t,n))}markReplyCompleted(e,t,n){const o=this.loadReplyCompletionSet(e);o.add(A(e.id,t,n));try{$(e.workDir,o)}catch{}}loadReplyCompletionSet(e){const t=h.resolve(e.workDir),n=this.completedReplyKeys.get(t);if(n)return n;const o=T(e.workDir);return this.completedReplyKeys.set(t,o),o}async getDefaultReplyTarget(e){const t=this.configs.list().find(a=>(a.sessionId??a.managerSessionId)===e&&a.enabled);if(!t)throw new Error("No enabled external channel is bound to this session");const n=this.adapters.get(t.type);if(!n?.defaultConversation)throw new Error(`External channel ${t.type} has no default conversation`);const o=await n.defaultConversation(t);return{channelId:t.id,conversationId:o.conversationId}}getManagerChannel(e,t,n={}){const o=this.configs.list().filter(d=>(d.sessionId??d.managerSessionId)===e&&d.type===t),a=o.find(d=>d.enabled)??o.at(-1);if(!a)return null;const i=this.secrets.get(a.secretRef),r=this.adapters.get(a.type)?.runtimeStatus?.(a)??{};return{id:a.id,type:a.type,name:a.name,sessionId:a.sessionId??a.managerSessionId,managerSessionId:a.managerSessionId,workDir:a.workDir,agentType:a.agentType,agentSessionId:a.agentSessionId,modelId:a.modelId,enabled:a.enabled,wsUrl:i?.wsUrl??"",token:n.includeSecret?i?.token??"":"",tokenConfigured:!!i?.token,canReply:!!i?.canReply,systemPrompt:typeof i?.systemPrompt=="string"?i.systemPrompt:"",...C(i),...r}}getChannelById(e,t={}){const n=this.configs.get(e);if(!n)return null;const o=this.secrets.get(n.secretRef),a=this.adapters.get(n.type)?.runtimeStatus?.(n)??{};return{id:n.id,type:n.type,name:n.name,sessionId:n.sessionId??n.managerSessionId,managerSessionId:n.managerSessionId,workDir:n.workDir,agentType:n.agentType,agentSessionId:n.agentSessionId,modelId:n.modelId,enabled:n.enabled,wsUrl:o?.wsUrl??"",token:t.includeSecret?o?.token??"":"",tokenConfigured:!!o?.token,canReply:!!o?.canReply,systemPrompt:typeof o?.systemPrompt=="string"?o.systemPrompt:"",...C(o),...a,managedBy:n.managedBy}}getChannelStatusById(e){const t=this.configs.get(e);if(!t)return null;const n=this.secrets.get(t.secretRef),o=this.adapters.get(t.type)?.runtimeStatus?.(t)??{};return{configured:!0,connected:y(t,n),type:t.type,channelId:t.id,name:t.name,canReply:!!n?.canReply,systemPrompt:typeof n?.systemPrompt=="string"?n.systemPrompt:"",...w(n),...o}}getManagerChannelStatus(e){const t=this.configs.list().find(a=>(a.sessionId??a.managerSessionId)===e&&a.enabled);if(!t)return null;const n=this.secrets.get(t.secretRef),o=this.adapters.get(t.type)?.runtimeStatus?.(t)??{};return{configured:!0,connected:y(t,n),type:t.type,channelId:t.id,name:t.name,canReply:!!n?.canReply,systemPrompt:typeof n?.systemPrompt=="string"?n.systemPrompt:"",...w(n),...o}}listManagerChannelStatuses(){return this.configs.list().filter(e=>e.enabled).map(e=>({managerSessionId:e.sessionId??e.managerSessionId,status:this.getManagerChannelStatus(e.sessionId??e.managerSessionId)})).filter(e=>!!e.status)}listManagerExternalChannels(e){return this.configs.list().filter(t=>t.enabled&&(t.sessionId??t.managerSessionId)===e).map(t=>{const n=this.secrets.get(t.secretRef),o=this.adapters.get(t.type)?.runtimeStatus?.(t)??{};return{configured:!0,connected:y(t,n),type:t.type,channelId:t.id,name:t.name,canReply:!!n?.canReply,systemPrompt:typeof n?.systemPrompt=="string"?n.systemPrompt:"",...w(n),...o}})}async syncManagerWeChatRpaChannel(e){const t=this.configs.list().find(r=>r.enabled&&r.type==="wechat-rpa"&&(r.sessionId??r.managerSessionId)===e);if(!t)throw new Error("No enabled WeChat RPA channel is bound to this session");const n=this.adapters.get(t.type);if(!n?.syncNow)throw new Error("WeChat RPA channel does not support manual sync");const o=Date.now()-120*1e3,a=await n.syncNow(t)??[],i=K(a,this.getRecentMessages(t.id,o));return{channel:this.getManagerChannel(e,"wechat-rpa",{includeSecret:!0}),messages:a,recentMessages:i}}async cancelManagerWeChatRpaOutbound(e){const t=this.configs.list().find(a=>a.enabled&&a.type==="wechat-rpa"&&(a.sessionId??a.managerSessionId)===e.managerSessionId);if(!t)throw new Error("No enabled WeChat RPA channel is bound to this session");const n=this.adapters.get(t.type);if(!n?.cancelOutbound)throw new Error("WeChat RPA channel does not support outbound cancellation");const o=await n.cancelOutbound(t,{idempotencyKey:e.idempotencyKey,replyId:e.replyId,reason:e.reason});return{cancelled:o.cancelled,status:o.status,channel:this.getManagerChannel(e.managerSessionId,"wechat-rpa",{includeSecret:!0})}}recordRecentMessage(e){const t=this.recentMessages.get(e.channelId)??[];this.findRecentMessageDuplicate(e)||(t.push(e),this.recentMessages.set(e.channelId,t.slice(-50)))}findRecentMessageDuplicate(e){const t=I(e);return t?(this.recentMessages.get(e.channelId)??[]).find(o=>I(o)===t)??null:null}getRecentMessages(e,t){return(this.recentMessages.get(e)??[]).filter(o=>{const a=Date.parse(o.receivedAt);return!Number.isFinite(a)||a>=t})}async upsertManagerChannel(e){const t=this.configs.get(e.id),n=this.configs.list(),o=e.sessionId||e.managerSessionId,a={id:e.id,type:e.type,name:e.name?.trim()||t?.name||"\u5916\u90E8\u6D88\u606F\u901A\u9053",sessionId:o,managerSessionId:o,workDir:e.workDir,agentType:e.agentType||t?.agentType,agentSessionId:e.agentSessionId??t?.agentSessionId??null,modelId:e.modelId??t?.modelId??null,enabled:e.enabled,secretRef:t?.secretRef||`channel:${e.id}`},i=this.secrets.get(a.secretRef),r=e.wsUrl?.trim()||i?.wsUrl||"",d=e.token?.trim()||i?.token||"",p=e.canReply??i?.canReply??!1,m=e.systemPrompt??(typeof i?.systemPrompt=="string"?i.systemPrompt:"");if(a.enabled&&(!r||!d))throw new Error("WebSocket \u5730\u5740\u548C Token \u5FC5\u586B");const f=n.filter(l=>l.id!==a.id).map(l=>(l.sessionId??l.managerSessionId)===o&&l.type===e.type?{...l,enabled:!1}:l);f.push(a),this.configs.replaceAll(f),(r||d)&&this.secrets.upsert(a.secretRef,{type:"websocket",wsUrl:r,token:d,canReply:p,systemPrompt:m});const c=this.adapters.get(a.type);for(const l of n)(l.sessionId??l.managerSessionId)===o&&l.type===e.type&&l.enabled&&await this.adapters.get(l.type)?.disconnect(l).catch(()=>{});return a.enabled&&c?.connect(a).catch(()=>{}),this.getManagerChannel(o,e.type,{includeSecret:!0})}async upsertManagerWeChatRpaChannel(e){const t=this.configs.get(e.id),n=this.configs.list(),o=e.sessionId||e.managerSessionId,a=g(e.groups);if(e.enabled&&!a.length)throw new Error("WeChat RPA \u81F3\u5C11\u9700\u8981\u914D\u7F6E\u4E00\u4E2A\u7FA4");if(e.enabled&&a.length>1)throw new Error("WeChat RPA \u6BCF\u4E2A\u5BF9\u8BDD\u53EA\u80FD\u7ED1\u5B9A\u4E00\u4E2A\u7FA4");if(e.enabled){const c=this.findConflictingWeChatRpaBinding({excludeChannelId:e.id,boundSessionId:o,groupNames:a.map(l=>l.name)});if(c)throw new Error(`\u5FAE\u4FE1\u5BF9\u8BDD\u300C${c.groupName}\u300D\u5DF2\u88AB\u53E6\u4E00\u4E2A\u667A\u80FD\u4F53\u7ED1\u5B9A(\u901A\u9053 ${c.channelName}),\u540C\u4E00\u4E2A\u5BF9\u8BDD\u53EA\u80FD\u7ED1\u5B9A\u4E00\u6B21\u3002\u8BF7\u5148\u5728\u539F\u667A\u80FD\u4F53\u89E3\u7ED1,\u518D\u91CD\u65B0\u7ED1\u5B9A\u3002`)}const i={id:e.id,type:"wechat-rpa",name:e.name?.trim()||t?.name||"\u672C\u673A\u5FAE\u4FE1 RPA",sessionId:o,managerSessionId:o,workDir:e.workDir,agentType:e.agentType||t?.agentType,agentSessionId:e.agentSessionId??t?.agentSessionId??null,modelId:e.modelId??t?.modelId??null,enabled:e.enabled,secretRef:t?.secretRef||`channel:${e.id}`,managedBy:e.managedBy??t?.managedBy},r=this.secrets.get(i.secretRef),d=e.source||(r?.source==="wechat-channel"||r?.source==="macos-probe"||r?.source==="fixture-jsonl"||r?.source==="macos-flow"||r?.source==="windows-visual-flow"||r?.source==="wechat-rpa-lab"?r.source:E());if(e.enabled&&d==="windows-visual-flow")throw new Error("\u65E7 Windows visual flow \u5DF2\u5F52\u6863\uFF1BmacOS \u548C Windows \u6B63\u5F0F\u901A\u9053\u90FD\u4F7F\u7528 wechat-channel");const p=e.privacyConsentAccepted??!!r?.privacyConsentAccepted;if(e.enabled&&!p)throw new Error("\u542F\u7528\u524D\u9700\u8981\u786E\u8BA4\u5FAE\u4FE1\u901A\u9053\u6570\u636E\u4E0E\u9690\u79C1\u6388\u6743");const m=n.filter(c=>c.id!==i.id).map(c=>(c.sessionId??c.managerSessionId)===o&&c.type==="wechat-rpa"?{...c,enabled:!1}:c);m.push(i),this.configs.replaceAll(m),this.secrets.upsert(i.secretRef,{type:"wechat-rpa",source:d,groups:a,pollIntervalMs:R(e.pollIntervalMs,r?.pollIntervalMs),recentLimit:R(e.recentLimit,r?.recentLimit),idleSeconds:R(e.idleSeconds,r?.idleSeconds),forceForeground:e.forceForeground??(r?.forceForeground===void 0?!0:!!r.forceForeground),noRestore:e.noRestore??(r?.noRestore===void 0?!0:!!r.noRestore),downloadAttachments:e.downloadAttachments??(r?.downloadAttachments===void 0?!0:!!r.downloadAttachments),downloadAttachmentsDir:e.downloadAttachmentsDir?.trim()||b(r?.downloadAttachmentsDir),selfNickname:e.selfNickname?.trim()||b(r?.selfNickname),selfTriggerMarker:e.selfTriggerMarker?.trim()||void 0,deferInitialPoll:e.deferInitialPoll===!0?!0:void 0,privacyConsentAccepted:p,flowScriptPath:e.flowScriptPath?.trim()||b(r?.flowScriptPath),canReply:e.canReply??r?.canReply??!1,systemPrompt:e.systemPrompt??(typeof r?.systemPrompt=="string"?r.systemPrompt:"")});const f=this.adapters.get(i.type);for(const c of n)(c.sessionId??c.managerSessionId)===o&&c.type==="wechat-rpa"&&c.enabled&&await this.adapters.get(c.type)?.disconnect(c).catch(()=>{});return i.enabled&&f?.connect(i).catch(c=>{console.error(`[wechat-rpa] connect failed id=${i.id}: ${c instanceof Error?c.message:String(c)}`)}),this.getManagerChannel(o,"wechat-rpa",{includeSecret:!0})}listChannelIdsManagedBy(e){return this.configs.list().filter(t=>t.managedBy===e).map(t=>t.id)}async deleteManagerWeChatRpaChannel(e){const t=this.configs.get(e);if(!t)return{removed:!1};t.enabled&&await this.adapters.get(t.type)?.disconnect(t).catch(()=>{});const n=this.configs.remove(e);return t.secretRef&&this.secrets.remove(t.secretRef),{removed:n}}findConflictingWeChatRpaBinding(e){const t=new Set(e.groupNames);if(!t.size)return null;for(const n of this.configs.list()){if(n.type!=="wechat-rpa"||!n.enabled||n.id===e.excludeChannelId||(n.sessionId??n.managerSessionId)===e.boundSessionId)continue;const o=this.secrets.get(n.secretRef),a=g(Array.isArray(o?.groups)?o.groups:[]);for(const i of a)if(t.has(i.name))return{channelId:n.id,channelName:n.name||n.id,groupName:i.name}}return null}}function y(s,e){return!e||e.type!==s.type?!1:s.type==="wechat-rpa"?!0:s.type==="websocket"?!!(e.wsUrl&&e.token):s.type==="wecom"?!!(e.token||e.botId||e.secret):!!e.token}function C(s){return!s||s.type!=="wechat-rpa"?{}:{wechatRpaSource:typeof s.source=="string"?s.source:"",wechatRpaGroups:g(Array.isArray(s.groups)?s.groups:[]),pollIntervalMs:Number.isFinite(s.pollIntervalMs)?Number(s.pollIntervalMs):void 0,recentLimit:Number.isFinite(s.recentLimit)?Number(s.recentLimit):void 0,idleSeconds:Number.isFinite(s.idleSeconds)?Number(s.idleSeconds):void 0,forceForeground:s.forceForeground===void 0?!0:!!s.forceForeground,noRestore:s.noRestore===void 0?void 0:!!s.noRestore,downloadAttachments:s.downloadAttachments===void 0?!0:!!s.downloadAttachments,downloadAttachmentsDir:typeof s.downloadAttachmentsDir=="string"?s.downloadAttachmentsDir:"",selfNickname:typeof s.selfNickname=="string"?s.selfNickname:"",wechatRpaPrivacyConsentAccepted:!!s.privacyConsentAccepted,wechatRpaServerDecisionAvailable:!0,wechatRpaPreflightChecks:k(s)}}function w(s){return!s||s.type!=="wechat-rpa"?{}:{wechatRpaSource:typeof s.source=="string"?s.source:null,wechatRpaGroups:g(Array.isArray(s.groups)?s.groups:[]),pollIntervalMs:Number.isFinite(s.pollIntervalMs)?Number(s.pollIntervalMs):null,recentLimit:Number.isFinite(s.recentLimit)?Number(s.recentLimit):null,idleSeconds:Number.isFinite(s.idleSeconds)?Number(s.idleSeconds):null,forceForeground:s.forceForeground===void 0?!0:!!s.forceForeground,noRestore:s.noRestore===void 0?null:!!s.noRestore,downloadAttachments:s.downloadAttachments===void 0?!0:!!s.downloadAttachments,downloadAttachmentsDir:typeof s.downloadAttachmentsDir=="string"?s.downloadAttachmentsDir:null,selfNickname:typeof s.selfNickname=="string"?s.selfNickname:null,wechatRpaPrivacyConsentAccepted:!!s.privacyConsentAccepted,wechatRpaServerDecisionAvailable:!0,wechatRpaPreflightChecks:k(s)}}function g(s){const e=new Set,t=[];for(const n of s){const o=String(n?.name||"").replace(/\s+/g," ").trim();!o||e.has(o)||(e.add(o),t.push({name:o}))}return t}function E(){return"wechat-channel"}function k(s){const e=[];if(s.lastHelperPermissions&&typeof s.lastHelperPermissions=="object"){const t=s.lastHelperPermissions;e.push({code:"wechat_window_unavailable",ok:t.wechatWindowAvailable!==!1,severity:"blocking",message:t.wechatWindowAvailable===!1?"\u5FAE\u4FE1\u7A97\u53E3\u4E0D\u53EF\u7528\uFF0C\u8BF7\u6253\u5F00\u5E76\u767B\u5F55\u5FAE\u4FE1\u540E\u518D\u8BD5\u3002":"\u5FAE\u4FE1\u7A97\u53E3\u53EF\u7528\u3002"})}return e.push({code:"platform_unsupported",ok:s.source!=="windows-visual-flow",severity:"blocking",message:s.source==="windows-visual-flow"?"\u65E7 Windows visual flow \u5DF2\u5F52\u6863\uFF0C\u8BF7\u6539\u7528 wechat-channel\u3002":"\u5F53\u524D\u914D\u7F6E\u4F7F\u7528\u8DE8\u5E73\u53F0\u5FAE\u4FE1\u901A\u9053\u3002"}),e.push({code:"privacy_consent_required",ok:!!s.privacyConsentAccepted,severity:"blocking",message:s.privacyConsentAccepted?"\u5DF2\u786E\u8BA4\u5FAE\u4FE1\u901A\u9053\u6570\u636E\u4E0E\u9690\u79C1\u6388\u6743\u3002":"\u542F\u7528\u524D\u9700\u8981\u786E\u8BA4\u5FAE\u4FE1\u901A\u9053\u6570\u636E\u4E0E\u9690\u79C1\u6388\u6743\u3002"}),e.push({code:"server_decision_unavailable",ok:!0,severity:"blocking",message:"\u670D\u52A1\u7AEF\u5224\u65AD\u80FD\u529B\u53EF\u7528\u3002"}),e}function F(s,e){const t=W(e.text);if(!t.length&&!e.attachment)return[];if(s==="wechat-rpa"&&e.attachment&&t.length<=1)return[{text:t[0]??"",attachment:e.attachment,idempotencyKey:e.idempotencyKey}];const n=t.map((o,a)=>({text:o,attachment:void 0,idempotencyKey:t.length>1&&e.idempotencyKey?`${e.idempotencyKey}:${a+1}`:e.idempotencyKey}));return e.attachment&&n.push({text:"",attachment:e.attachment,idempotencyKey:t.length&&e.idempotencyKey?`${e.idempotencyKey}:attachment`:e.idempotencyKey}),n}function K(...s){const e=new Set,t=[];for(const n of s.flat()){const o=I(n);if(!o){t.push(n);continue}e.has(o)||(e.add(o),t.push(n))}return t}function I(s){const e=S(s.channelId),t=S(s.conversationId),n=S(s.messageId);return!e||!t||!n?null:`${e}
|
|
2
2
|
${t}
|
|
3
3
|
${n}`}function S(s){return typeof s=="string"?s.trim():""}function A(s,e,t){return M.createHash("sha256").update(`${s}
|
|
4
4
|
${e}
|
|
5
|
-
${t}`).digest("hex").slice(0,32)}function
|
|
5
|
+
${t}`).digest("hex").slice(0,32)}function v(s){return h.join(s,".shennian","external-reply-idempotency.json")}function T(s){try{const e=JSON.parse(u.readFileSync(v(s),"utf8")),t=Array.isArray(e.completed)?e.completed:[];return new Set(t.map(n=>typeof n=="string"?n:n&&typeof n=="object"&&typeof n.key=="string"?n.key:"").filter(Boolean))}catch{return new Set}}function $(s,e){const t=v(s);u.mkdirSync(h.dirname(t),{recursive:!0});const n=Array.from(e).slice(-500);u.writeFileSync(t,JSON.stringify({updatedAt:new Date().toISOString(),completed:n.map(o=>({key:o}))},null,2)),e.clear();for(const o of n)e.add(o)}function R(s,e){const t=Number(s??e);return Number.isFinite(t)&&t>=0?t:void 0}function b(s){return typeof s=="string"&&s.trim()?s.trim():void 0}export{J as ChannelRuntime,F as planExternalReplySends};
|
|
@@ -30,5 +30,6 @@ export declare class ChannelSecretRegistry {
|
|
|
30
30
|
private save;
|
|
31
31
|
get(secretRef: string): ChannelSecretRecord | undefined;
|
|
32
32
|
upsert(secretRef: string, value: Omit<ChannelSecretRecord, 'updatedAt'>): ChannelSecretRecord;
|
|
33
|
+
remove(secretRef: string): void;
|
|
33
34
|
}
|
|
34
35
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import r from"node:fs";import o from"node:path";import{resolveShennianPath as
|
|
1
|
+
import r from"node:fs";import o from"node:path";import{resolveShennianPath as i}from"../config/index.js";function s(){return i("channels.secrets.json")}function a(){return new Date().toISOString()}function u(){return{secrets:{}}}class f{load(){try{return{secrets:JSON.parse(r.readFileSync(s(),"utf-8")).secrets??{}}}catch{return u()}}save(e){r.mkdirSync(o.dirname(s()),{recursive:!0}),r.writeFileSync(s(),JSON.stringify(e,null,2),{mode:384});try{r.chmodSync(s(),384)}catch{}}get(e){return this.load().secrets[e]}upsert(e,t){const n=this.load(),c={...t,updatedAt:a()};return n.secrets[e]=c,this.save(n),c}remove(e){const t=this.load();e in t.secrets&&(delete t.secrets[e],this.save(t))}}export{f as ChannelSecretRegistry};
|
|
@@ -5,6 +5,7 @@ import type { WeChatChannelHelperTransport } from './observer.js';
|
|
|
5
5
|
import { type WeChatChannelObserveApi } from './observer.js';
|
|
6
6
|
import { WeChatChannelScheduler, type WeChatChannelSchedulerTickOptions, type WeChatChannelSchedulerTickResult } from './scheduler.js';
|
|
7
7
|
import { type WeChatAutomationLane } from './automation-lane.js';
|
|
8
|
+
export declare const WECHAT_CHANNEL_UNCHANGED_FINGERPRINT_MAX_SKIP_MS: number;
|
|
8
9
|
export type WeChatChannelProductRunnerOptions = {
|
|
9
10
|
runtime: WeChatChannelRuntime;
|
|
10
11
|
workDir: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import u from"node:path";import h from"node:fs";import{createWeChatChannelApiClient as m}from"./client.js";import{WeChatChannelHelperClient as f}from"./helper-client.js";import{resolveWeChatChannelHelperAsset as k,WECHAT_CHANNEL_HELPER_VERSION as W}from"./helper-assets.js";import{requiredWindowsWeChatChannelHelperCapabilitiesForProfile as c}from"./helper-protocol.js";import{captureWeChatWindow as S,detectWeChatMessageInputPointByServerVision as I,focusKnownWeChatWindow as g,observeWeChatChannelBindingViaHelper as P,openConversationBySearch as A,recognizeWeChatScreenshot as
|
|
1
|
+
import u from"node:path";import h from"node:fs";import{createWeChatChannelApiClient as m}from"./client.js";import{WeChatChannelHelperClient as f}from"./helper-client.js";import{resolveWeChatChannelHelperAsset as k,WECHAT_CHANNEL_HELPER_VERSION as W}from"./helper-assets.js";import{requiredWindowsWeChatChannelHelperCapabilitiesForProfile as c}from"./helper-protocol.js";import{captureWeChatWindow as S,detectWeChatMessageInputPointByServerVision as I,focusKnownWeChatWindow as g,observeWeChatChannelBindingViaHelper as P,openConversationBySearch as A,recognizeWeChatScreenshot as D,selectWeChatWindowForBinding as w}from"./observer.js";import{WeChatChannelScheduler as O}from"./scheduler.js";import{loadWeChatChannelLedger as F}from"./ledger.js";import{buildWeChatConversationFingerprint as M,matchWeChatConversationFingerprints as _}from"./fingerprint.js";import{WeChatChannelOutboundSender as T}from"./outbound-sender.js";import{SerialWeChatAutomationLane as x}from"./automation-lane.js";import{decideWeChatChannelActivityGate as L,nextWeChatChannelActivityRetryAt as H,normalizeWeChatChannelActivitySnapshot as E}from"./human-coordination.js";const N=120*1e3;class R{options;scheduler;helper;helperStarted=!1;lane;constructor(e){this.options=e;const n=e.api??m();this.helper=e.helper??U(e),this.lane=e.lane??new x,this.scheduler=new O({runtime:e.runtime,workDir:e.workDir,api:n,ledgerPath:e.ledgerPath,outboundLedgerPath:e.outboundLedgerPath,onInboundMessages:e.onInboundMessages,activityGate:{canObserve:async(a,i)=>{if(e.runtime.foregroundPolicy==="work")return{ok:!0};const o=await this.helper.request("activity.snapshot",{});if(!o.ok)return{ok:!1,reasonCode:"user_activity_unknown",nextAttemptAt:new Date(i.getTime()+5e3)};const t=L({snapshot:E(o.result),stage:"observe"});return t.ok?{ok:!0}:{ok:!1,reasonCode:t.reasonCode,nextAttemptAt:H(i,t.waitMs)}}},shouldSkipObserve:(a,i)=>this.shouldSkipObserveForUnchangedFingerprint(a,i.foregroundMode),outboundSender:new T({helper:this.helper,platform:e.runtime.policy.platform,activityGatePolicy:e.runtime.foregroundPolicy==="work"?{mouseMovedThresholdMs:0,mouseClickThresholdMs:0,scrollWheelThresholdMs:0,keyDownThresholdMs:0}:void 0,takeoverCheck:e.runtime.foregroundPolicy!=="work",openConversation:async a=>{const i={bindingId:`outbound:${a}`,sessionId:"outbound",conversationDisplayName:a,enabled:!0,allowReply:!0,downloadMedia:!0},o=await w({runtime:e.runtime,binding:i,helper:this.helper,api:n,workDir:e.workDir}),t=await g(this.helper,o),s=await A({helper:this.helper,window:t,settleToBottom:!1,binding:i,runtime:e.runtime,api:n,workDir:e.workDir});if(!s.opened)return{...s,windowId:t.windowId,inputPoint:null};const l=await I({runtime:e.runtime,binding:i,helper:this.helper,api:n,workDir:e.workDir,window:t});return{...s,windowId:t.windowId,inputPoint:l}}}),observeBinding:(a,i)=>P({runtime:e.runtime,binding:a,helper:this.helper,api:n,workDir:e.workDir,localLedgerTailAnchors:q(e.ledgerPath??B(e.workDir,e.runtime.runtimeId),e.runtime.runtimeId,a.bindingId),localOutboundEchoAnchors:i?.localOutboundEchoAnchors,foregroundMode:i?.foregroundMode,onObservationEvidence:({ocr:o})=>{$({statePath:b(e.workDir,e.runtime.runtimeId),runtimeId:e.runtime.runtimeId,binding:a,ocr:o})}})})}async start(){return this.lane.run("wechat.runner.start",async()=>{await this.ensureHelperStarted(),await this.scheduler.start()})}async tick(e){return this.lane.run("wechat.runner.tick",async()=>(await this.ensureHelperStarted(),this.scheduler.tick(e)))}async stop(){return this.lane.run("wechat.runner.stop",async()=>{await this.scheduler.stop(),this.helperStarted&&this.helper.stop&&await this.helper.stop(),this.helperStarted=!1})}async ensureHelperStarted(){this.helperStarted||(this.helper.start&&await this.helper.start(),this.helperStarted=!0)}async shouldSkipObserveForUnchangedFingerprint(e,n="required"){if(this.options.runtime.policy.platform!=="darwin"&&this.options.runtime.policy.platform!=="win32")return{skip:!1};const a=b(this.options.workDir,this.options.runtime.runtimeId),i=p(a,this.options.runtime.runtimeId).bindings[e.bindingId];if(!i?.fingerprint)return{skip:!1};if(j(i))return{skip:!1};try{const o=this.options.api??m(),t=await w({runtime:this.options.runtime,binding:e,helper:this.helper,api:o,workDir:this.options.workDir},{foregroundMode:n}),s=n==="background"?t:await g(this.helper,t),l=await S(this.helper,s.windowId,void 0,s.bounds),y=await D(this.helper,l),d=v({binding:e,ocr:y,prior:i});return!d?.fingerprint||d.fingerprint!==i.fingerprint?{skip:!1}:{skip:!0,reasonCode:"conversation_fingerprint_unchanged",fingerprint:d.fingerprint}}catch{return{skip:!1}}}}function oe(r){return new R(r)}function q(r,e,n,a=3){return(F(r,e).bindings[n]?.recent??[]).slice(-a).map(t=>({stableMessageKey:t.stableMessageKey,senderRole:t.senderRole,kind:t.kind,anchorText:t.anchorText??t.normalizedText??t.textExcerpt??null,anchorMetadata:t.anchorMetadata??null}))}function U(r){if(r.helperClientOptions)return new f({...r.helperClientOptions,requiredCapabilities:r.helperClientOptions.requiredCapabilities??C(r.runtime),guardUnsafeWindowsCommands:r.helperClientOptions.guardUnsafeWindowsCommands??r.runtime.policy.platform==="win32",skipUserActivityGuard:r.helperClientOptions.skipUserActivityGuard??r.runtime.foregroundPolicy==="work",cleanupWindowsOverlays:r.helperClientOptions.cleanupWindowsOverlays??r.runtime.policy.platform==="win32",requestLogger:r.helperClientOptions.requestLogger??r.helperRequestLogger});const e=k();if(!e.ok)throw new Error(`${e.reasonCode}: ${e.message}`);return new f({helperPath:e.helperPath,expectedHelperVersion:e.version||W,requiredCapabilities:C(r.runtime),guardUnsafeWindowsCommands:r.runtime.policy.platform==="win32",skipUserActivityGuard:r.runtime.foregroundPolicy==="work",cleanupWindowsOverlays:r.runtime.policy.platform==="win32",requestLogger:r.helperRequestLogger})}function C(r){if(r.policy.platform==="win32")return r.bindings.some(e=>e.enabled!==!1&&e.allowReply!==!1)?c("send"):r.bindings.some(e=>e.enabled!==!1&&e.downloadMedia!==!1)?c("download"):c("observe")}function B(r,e){return u.join(r,"wechat-channel",`${e}.ledger.json`)}function b(r,e){return u.join(r,"wechat-channel",`${z(e)}.conversation-fingerprints.json`)}function p(r,e){try{const n=JSON.parse(h.readFileSync(r,"utf8"));if(n?.version===1&&n.runtimeId===e&&n.bindings&&typeof n.bindings=="object")return n}catch{}return{version:1,runtimeId:e,bindings:{}}}function G(r,e){h.mkdirSync(u.dirname(r),{recursive:!0}),h.writeFileSync(r,JSON.stringify(e,null,2))}function $(r){const e=v({binding:r.binding,ocr:r.ocr,prior:p(r.statePath,r.runtimeId).bindings[r.binding.bindingId]});if(!e?.fingerprint)return;const n=p(r.statePath,r.runtimeId);n.bindings[r.binding.bindingId]={bindingId:r.binding.bindingId,fingerprint:e.fingerprint,title:e.item.title??null,preview:e.item.preview??null,timeText:e.item.timeText??null,unreadText:e.item.unreadText??null,observedAt:new Date().toISOString()},G(r.statePath,n)}function j(r){const e=Date.parse(r.observedAt||"");return Number.isFinite(e)?Date.now()-e>=N:!0}function v(r){const e=r.ocr.visibleConversationFingerprints??[],n=_({visibleItems:e,targets:[{bindingId:r.binding.bindingId,conversationDisplayName:r.binding.conversationDisplayName,lastConversationFingerprint:r.prior?.fingerprint,lastPreview:r.prior?.preview??void 0}]})[0];if(!n)return null;const a=M(n.item);return a?{item:n.item,fingerprint:a,reason:n.reason}:null}function z(r){return String(r||"").trim().replace(/[^a-zA-Z0-9._-]+/g,"_").replace(/^_+|_+$/g,"")||"wechat-channel-runtime"}export{N as WECHAT_CHANNEL_UNCHANGED_FINGERPRINT_MAX_SKIP_MS,R as WeChatChannelProductRunner,q as buildLocalLedgerTailAnchors,oe as createWeChatChannelProductRunner};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ExternalChannelSessionStatus } from '@shennian/wire';
|
|
2
|
+
import type { ChannelRuntime } from './runtime.js';
|
|
3
|
+
export type WeChatRpaSessionContext = {
|
|
4
|
+
sessionId: string;
|
|
5
|
+
workDir?: string | null;
|
|
6
|
+
agentType?: string | null;
|
|
7
|
+
agentSessionId?: string | null;
|
|
8
|
+
modelId?: string | null;
|
|
9
|
+
externalChannel?: ExternalChannelSessionStatus | null;
|
|
10
|
+
};
|
|
11
|
+
export type WeChatRpaSessionBindingSyncOptions = {
|
|
12
|
+
channelRuntime: Pick<ChannelRuntime, 'upsertManagerWeChatRpaChannel' | 'deleteManagerWeChatRpaChannel' | 'listChannelIdsManagedBy'>;
|
|
13
|
+
getLocalMachineId: () => string;
|
|
14
|
+
listSessions: () => Iterable<WeChatRpaSessionContext>;
|
|
15
|
+
};
|
|
16
|
+
export declare class WeChatRpaSessionBindingSync {
|
|
17
|
+
private options;
|
|
18
|
+
private reconciling;
|
|
19
|
+
constructor(options: WeChatRpaSessionBindingSyncOptions);
|
|
20
|
+
reconcileAll(): Promise<void>;
|
|
21
|
+
reconcileSession(session: WeChatRpaSessionContext): Promise<void>;
|
|
22
|
+
private applyUpsert;
|
|
23
|
+
private toDesiredBinding;
|
|
24
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import l from"node:os";const o="session-sync";class h{options;reconciling=null;constructor(n){this.options=n}async reconcileAll(){for(;this.reconciling;)await this.reconciling;let n=()=>{};this.reconciling=new Promise(t=>{n=t});try{const t=this.options.getLocalMachineId(),e=new Map;for(const a of this.options.listSessions()){const i=this.toDesiredBinding(a,t);i&&e.set(i.channelId,i)}const s=new Set(this.options.channelRuntime.listChannelIdsManagedBy(o));for(const a of e.values())await this.applyUpsert(a);for(const a of s)e.has(a)||await this.options.channelRuntime.deleteManagerWeChatRpaChannel(a).catch(()=>{})}finally{n(),this.reconciling=null}}async reconcileSession(n){for(;this.reconciling;)await this.reconciling;let t=()=>{};this.reconciling=new Promise(e=>{t=e});try{const e=this.options.getLocalMachineId(),s=this.toDesiredBinding(n,e);if(s){await this.applyUpsert(s);return}const a=n.externalChannel?.channelId;a&&this.options.channelRuntime.listChannelIdsManagedBy(o).includes(a)&&await this.options.channelRuntime.deleteManagerWeChatRpaChannel(a).catch(()=>{})}finally{t(),this.reconciling=null}}async applyUpsert(n){await this.options.channelRuntime.upsertManagerWeChatRpaChannel({id:n.channelId,managerSessionId:n.sessionId,sessionId:n.sessionId,workDir:n.workDir,name:n.conversationName,agentType:n.agentType,agentSessionId:n.agentSessionId,modelId:n.modelId,enabled:!0,groups:[{name:n.conversationName}],canReply:n.canReply,source:n.source,downloadAttachments:n.downloadAttachments,privacyConsentAccepted:!0,managedBy:o}).catch(t=>{console.error(`[wechat-rpa-sync] upsert failed channelId=${n.channelId}: ${t instanceof Error?t.message:String(t)}`)})}toDesiredBinding(n,t){const e=n.externalChannel;if(!e||e.type!=="wechat-rpa"||!e.configured||!e.channelId)return null;const s=(e.name??"").trim();return!s||e.machineId&&t&&e.machineId!==t?null:{channelId:e.channelId,sessionId:n.sessionId,conversationName:s,workDir:n.workDir?.trim()||l.homedir(),agentType:n.agentType??void 0,agentSessionId:n.agentSessionId??null,modelId:n.modelId??null,canReply:e.canReply!==!1,downloadAttachments:e.downloadAttachments!==!1,source:"wechat-channel"}}}export{h as WeChatRpaSessionBindingSync};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import U from"node:crypto";import{once as _}from"node:events";import o from"node:fs";import z from"node:os";import h from"node:path";import{spawnSync as y}from"node:child_process";const R="https://videoindustry.oss-cn-hangzhou.aliyuncs.com/shennian/helper-runtime",g="stable",$=
|
|
1
|
+
import U from"node:crypto";import{once as _}from"node:events";import o from"node:fs";import z from"node:os";import h from"node:path";import{spawnSync as y}from"node:child_process";const R="https://videoindustry.oss-cn-hangzhou.aliyuncs.com/shennian/helper-runtime",g="stable",$=5*6e4,I=350*1024*1024;async function X(e){const r=e.env??process.env,n=S(e.channel??r.SHENNIAN_HELPER_RUNTIME_CHANNEL)??g,t=e.manifestUrl??P({env:r,channel:n}),a=e.arch??process.arch,s=A(r.SHENNIAN_HELPER_RUNTIME_DOWNLOAD_TIMEOUT_MS)??e.timeoutMs??$,c=A(r.SHENNIAN_HELPER_RUNTIME_MAX_BYTES)??e.maxBytes??I,m=e.fetchImpl??globalThis.fetch?.bind(globalThis);if(!m)return{ok:!1,reasonCode:"helper_runtime_manifest_unavailable",message:"This Node.js runtime does not provide fetch for official Helper runtime download.",manifestUrl:t};const l=await L({manifestUrl:t,fetchImpl:m,timeoutMs:s});if(!l.ok)return l;const i=x({manifest:l.manifest,platform:e.platform,arch:a});if(!i)return{ok:!1,reasonCode:"helper_runtime_artifact_unavailable",message:`No official Helper runtime artifact is available for ${e.platform}/${a} on channel ${l.manifest.channel}.`,manifestUrl:t};const f=o.mkdtempSync(h.join(e.workRoot??z.tmpdir(),"shennian-helper-runtime-")),d=h.join(f,"helper-runtime.zip"),p=h.join(f,"source");try{await(e.downloadFile??C)({url:i.url,outputPath:d,fetchImpl:m,timeoutMs:s,maxBytes:c})}catch(u){return o.rmSync(f,{recursive:!0,force:!0}),{ok:!1,reasonCode:"helper_runtime_download_failed",message:`Official Helper runtime download failed: ${u instanceof Error?u.message:String(u)}`,manifestUrl:t,artifactUrl:i.url}}const H=o.statSync(d).size,T=O(d);if(H!==i.sizeBytes||T!==i.sha256)return o.rmSync(f,{recursive:!0,force:!0}),{ok:!1,reasonCode:"helper_runtime_artifact_integrity_mismatch",message:"Official Helper runtime artifact failed size or sha256 verification.",manifestUrl:t,artifactUrl:i.url};try{o.mkdirSync(p,{recursive:!0}),await(e.extractZip??M)({zipPath:d,outputDir:p,platform:e.platform}),v(p)}catch(u){return o.rmSync(f,{recursive:!0,force:!0}),{ok:!1,reasonCode:"helper_runtime_artifact_invalid",message:`Official Helper runtime artifact could not be extracted safely: ${u instanceof Error?u.message:String(u)}`,manifestUrl:t,artifactUrl:i.url}}return{ok:!0,sourceDir:p,tempDir:f,cleanup:()=>o.rmSync(f,{recursive:!0,force:!0}),channel:l.manifest.channel,manifestUrl:t,artifactUrl:i.url,artifactSha256:i.sha256,artifactSizeBytes:i.sizeBytes,platform:i.platform,arch:i.arch,helperVersion:i.helperVersion,protocolVersion:i.protocolVersion,minCliVersion:i.minCliVersion}}function P(e={}){const r=e.env??process.env,n=r.SHENNIAN_HELPER_RUNTIME_MANIFEST_URL?.trim();if(n)return n;const t=S(e.channel??r.SHENNIAN_HELPER_RUNTIME_CHANNEL)??g;return`${(r.SHENNIAN_HELPER_RUNTIME_BASE_URL?.trim()||R).replace(/\/+$/g,"")}/${encodeURIComponent(t)}/manifest.json`}function x(e){return N(e.manifest)?e.manifest.artifacts.filter(n=>n.platform===e.platform).find(n=>D(n.arch,e.arch))??null:null}async function L(e){try{const r=new AbortController,n=setTimeout(()=>r.abort(),e.timeoutMs);try{const t=await e.fetchImpl(e.manifestUrl,{signal:r.signal});if(!t.ok)throw new Error(`HTTP ${t.status}`);const a=JSON.parse(await t.text());if(!N(a))throw new Error("manifest schema is invalid");return{ok:!0,manifest:a}}finally{clearTimeout(n)}}catch(r){return{ok:!1,reasonCode:"helper_runtime_manifest_unavailable",message:`Official Helper runtime manifest is unavailable: ${r instanceof Error?r.message:String(r)}`,manifestUrl:e.manifestUrl}}}async function C(e){const r=new AbortController,n=setTimeout(()=>r.abort(),e.timeoutMs);let t=null;try{const a=await e.fetchImpl(e.url,{signal:r.signal});if(!a.ok)throw new Error(`HTTP ${a.status}`);if(!a.body)throw new Error("response body is empty");t=o.createWriteStream(e.outputPath,{flags:"wx"});const s=a.body.getReader();let c=0;for(;;){const{done:m,value:l}=await s.read();if(m)break;const i=Buffer.from(l);if(c+=i.byteLength,c>e.maxBytes)throw new Error(`download exceeded ${e.maxBytes} bytes`);t.write(i)||await _(t,"drain")}t.end(),await _(t,"finish")}finally{clearTimeout(n),t&&!t.closed&&t.destroy()}}async function M(e){if(k(e.zipPath),process.platform==="win32"){E("powershell.exe",["-NoProfile","-ExecutionPolicy","Bypass","-Command",`Expand-Archive -LiteralPath ${b(e.zipPath)} -DestinationPath ${b(e.outputDir)} -Force`]);return}if(e.platform==="darwin"&&w("ditto")){E("ditto",["-x","-k",e.zipPath,e.outputDir]);return}E("unzip",["-q",e.zipPath,"-d",e.outputDir])}function k(e){const r=w("unzip")?"unzip":w("tar")?"tar":null;if(!r)return;const n=r==="unzip"?y(r,["-Z","-1",e],{encoding:"utf8"}):y(r,["-tf",e],{encoding:"utf8"});if(n.status!==0)throw new Error(`unable to inspect zip entries with ${r}`);for(const t of n.stdout.split(/\r?\n/u).filter(Boolean)){const a=t.replace(/\\/g,"/");if(a.startsWith("/")||a.includes("../")||a===".."||/^[A-Za-z]:\//u.test(a))throw new Error(`unsafe zip entry: ${t}`)}}function v(e){const r=o.realpathSync(e),n=[e];for(;n.length>0;){const t=n.pop();if(t)for(const a of o.readdirSync(t,{withFileTypes:!0})){const s=h.join(t,a.name),c=o.realpathSync(s);if(c!==r&&!c.startsWith(`${r}${h.sep}`))throw new Error(`extracted path escapes target directory: ${s}`);a.isDirectory()&&n.push(s)}}}function N(e){if(!e||typeof e!="object"||Array.isArray(e))return!1;const r=e;return r.schemaVersion!==1||r.kind!=="shennian-helper-runtime-index"||!r.channel||typeof r.channel!="string"||!r.generatedAt||typeof r.generatedAt!="string"||!Array.isArray(r.artifacts)?!1:r.artifacts.every(B)}function B(e){if(!e||typeof e!="object"||Array.isArray(e))return!1;const r=e;return(r.platform==="darwin"||r.platform==="win32")&&typeof r.arch=="string"&&typeof r.helperVersion=="string"&&Number.isInteger(r.protocolVersion)&&typeof r.minCliVersion=="string"&&typeof r.url=="string"&&typeof r.sizeBytes=="number"&&Number.isInteger(r.sizeBytes)&&r.sizeBytes>0&&typeof r.sha256=="string"&&/^[a-f0-9]{64}$/iu.test(r.sha256)}function D(e,r){const n=e.toLowerCase(),t=r.toLowerCase();return n===t||n==="universal"||n==="all"||n===`win-${t}`||n===`darwin-${t}`}function S(e){const r=e?.trim();return!r||!/^[a-z0-9][a-z0-9._-]{0,63}$/iu.test(r)?null:r}function A(e){if(!e)return null;const r=Number(e);return Number.isInteger(r)&&r>0?r:null}function O(e){return U.createHash("sha256").update(o.readFileSync(e)).digest("hex")}function w(e){return y(e,["--version"],{stdio:"ignore"}).status===0}function E(e,r){const n=y(e,r,{stdio:"ignore"});if(n.status!==0)throw new Error(`${e} exited ${n.status}`)}function b(e){return`'${e.replace(/'/g,"''")}'`}export{X as downloadOfficialHelperRuntimePackage,P as getOfficialHelperRuntimeManifestUrl,x as selectOfficialHelperRuntimeArtifact};
|
|
@@ -33,6 +33,9 @@ export type HelperRuntimeInstallResult = {
|
|
|
33
33
|
artifactSha256: string;
|
|
34
34
|
artifactSizeBytes: number;
|
|
35
35
|
arch: string;
|
|
36
|
+
helperVersion: string;
|
|
37
|
+
protocolVersion: number;
|
|
38
|
+
minCliVersion: string;
|
|
36
39
|
};
|
|
37
40
|
message: string;
|
|
38
41
|
} | {
|
|
@@ -78,13 +81,16 @@ export type HelperRuntimeStatusResult = {
|
|
|
78
81
|
};
|
|
79
82
|
type RuntimeIo = {
|
|
80
83
|
existsSync?: typeof fs.existsSync;
|
|
84
|
+
lstatSync?: typeof fs.lstatSync;
|
|
81
85
|
readFileSync?: typeof fs.readFileSync;
|
|
86
|
+
realpathSync?: typeof fs.realpathSync;
|
|
82
87
|
mkdirSync?: typeof fs.mkdirSync;
|
|
83
88
|
rmSync?: typeof fs.rmSync;
|
|
84
89
|
cpSync?: typeof fs.cpSync;
|
|
85
90
|
renameSync?: typeof fs.renameSync;
|
|
86
91
|
chmodSync?: typeof fs.chmodSync;
|
|
87
92
|
homedir?: () => string;
|
|
93
|
+
stopMacHelperProcesses?: (targetDir: string) => void;
|
|
88
94
|
stopWindowsHelperProcesses?: () => void;
|
|
89
95
|
};
|
|
90
96
|
export declare function registerRuntimeCommand(program: Command): void;
|
|
@@ -107,6 +113,7 @@ export declare function installHelperRuntimeWithOfficialDownload(input?: {
|
|
|
107
113
|
env?: NodeJS.ProcessEnv;
|
|
108
114
|
homedir?: string;
|
|
109
115
|
force?: boolean;
|
|
116
|
+
preferOfficialDownload?: boolean;
|
|
110
117
|
io?: RuntimeIo;
|
|
111
118
|
officialDownload?: false | Partial<HelperRuntimeOfficialDownloadOptions>;
|
|
112
119
|
}): Promise<HelperRuntimeInstallResult>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import f from"node:fs";import y from"node:os";import t from"node:path";import{spawnSync as A}from"node:child_process";import v from"chalk";import{getDefaultWeChatHelperRuntimeRoot as D,readWeChatChannelHelperRuntimePackageManifest as w,resolveWeChatChannelHelperAsset as C,SHENNIAN_HELPER_RUNTIME_DIR_ENV as E,WECHAT_CHANNEL_HELPER_DIR_ENV as O}from"../channels/wechat-channel/helper-assets.js";import{downloadOfficialHelperRuntimePackage as $}from"./helper-runtime-official-download.js";import{compareVersions as x,getCurrentVersion as H}from"../upgrade/engine.js";import{SerialWeChatAutomationLane as U}from"../channels/wechat-channel/automation-lane.js";const u="helper-runtime-package.json";function ce(e){const r=e.command("runtime").description("Install, repair, and inspect Shennian local runtimes");r.command("status").description("Print local Shennian runtime status").option("--json","Output JSON",!1).option("--platform <platform>","Platform override: darwin or win32").action(o=>{const n=V({platform:o.platform});P(n,!!o.json),n.ok||(process.exitCode=1)}),r.command("install").description("Install a Shennian runtime component").argument("<component>","Runtime component, currently only: helper").option("--source <path>","Helper runtime package/source directory").option("--channel <channel>","Official Helper runtime channel: stable or next").option("--manifest-url <url>","Official Helper runtime manifest URL override").option("--no-official-download","Do not fall back to Shennian official Helper runtime download").option("--json","Output JSON",!1).option("--platform <platform>","Platform override: darwin or win32").action(async(o,n)=>{const a=o==="helper"?await b({source:n.source,platform:n.platform,officialDownload:n.officialDownload===!1?!1:{channel:n.channel,manifestUrl:n.manifestUrl}}):M(o);P(a,!!n.json),a.ok||(process.exitCode=1)}),r.command("repair").description("Repair a Shennian runtime component").argument("[component]","Runtime component, currently only: helper","helper").option("--source <path>","Helper runtime package/source directory").option("--channel <channel>","Official Helper runtime channel: stable or next").option("--manifest-url <url>","Official Helper runtime manifest URL override").option("--no-official-download","Do not fall back to Shennian official Helper runtime download").option("--json","Output JSON",!1).option("--platform <platform>","Platform override: darwin or win32").action(async(o,n)=>{const a=o==="helper"?await b({source:n.source,platform:n.platform,force:!0,officialDownload:n.officialDownload===!1?!1:{channel:n.channel,manifestUrl:n.manifestUrl}}):M(o);P(a,!!n.json),a.ok||(process.exitCode=1)})}function V(e={}){const r=k(e.platform);if(!r)return{ok:!1,installed:!1,repairable:!1,reasonCode:"unsupported_platform",message:"Shennian Helper runtime is only available on macOS and Windows.",repairSuggestion:l("unsupported_platform")};const o=e.env??process.env,n=e.homedir,a=C({platform:r,env:o,homedir:n,includeInstalledDesktop:!1}),i=D({platform:r,env:o,homedir:n});if(!a.ok)return{ok:!1,platform:r,targetPlatform:r,installed:!1,repairable:K(a.reasonCode),reasonCode:a.reasonCode,message:a.message,runtimeRoot:i,repairSuggestion:l(a.reasonCode),checkedSources:_({platform:r,env:o,homedir:n})};const s=w({platform:r,helperDir:a.helperDir});if(!s.ok)return{ok:!1,platform:r,targetPlatform:r,installed:!1,repairable:!0,reasonCode:s.reasonCode,message:s.message,helperDir:a.helperDir,helperPath:a.helperPath,helperVersion:a.version,runtimeVersion:a.version,protocolVersion:a.manifest.protocolVersion,runtimeRoot:i,repairSuggestion:l(s.reasonCode),checkedSources:_({platform:r,env:o,homedir:n})};const c=R({platform:r,packageManifest:s.manifest,currentCliVersion:H(),helperVersion:a.version,protocolVersion:a.manifest.protocolVersion});return c.ok?{ok:!0,platform:r,targetPlatform:r,installed:!0,repairable:!1,message:`Shennian Helper runtime is installed: ${a.helperDir}`,helperDir:a.helperDir,helperPath:a.helperPath,helperVersion:a.version,runtimeVersion:a.version,protocolVersion:a.manifest.protocolVersion,minCliVersion:s.manifest.minCliVersion,sha256:s.manifest.sha256,packageManifestPath:s.manifestPath,packageKind:s.manifest.packageKind,installTarget:s.manifest.installTarget,signature:s.manifest.signature,runtimeRoot:i}:{ok:!1,platform:r,targetPlatform:r,installed:!0,repairable:c.repairable,reasonCode:c.reasonCode,message:c.message,helperDir:a.helperDir,helperPath:a.helperPath,helperVersion:a.version,runtimeVersion:a.version,protocolVersion:a.manifest.protocolVersion,minCliVersion:s.manifest.minCliVersion,sha256:s.manifest.sha256,packageManifestPath:s.manifestPath,packageKind:s.manifest.packageKind,installTarget:s.manifest.installTarget,signature:s.manifest.signature,runtimeRoot:i,repairSuggestion:l(c.reasonCode,s.manifest.minCliVersion),checkedSources:_({platform:r,env:o,homedir:n})}}function j(e={}){const r=k(e.platform);if(!r)return{ok:!1,reasonCode:"unsupported_platform",message:"Shennian Helper runtime is only available on macOS and Windows.",repairable:!1,repairSuggestion:l("unsupported_platform")};const o=e.env??process.env,n=F(e.io),a=e.homedir??n.homedir(),i=V({platform:r,env:o,homedir:a});if(i.ok&&!e.force&&i.helperDir&&i.helperPath&&i.helperVersion&&i.protocolVersion)return{ok:!0,action:"already_installed",platform:r,targetPlatform:r,installed:!0,repairable:!1,targetDir:q(r,i.helperDir),helperDir:i.helperDir,helperPath:i.helperPath,helperVersion:i.helperVersion,runtimeVersion:i.helperVersion,protocolVersion:i.protocolVersion,minCliVersion:i.minCliVersion,sha256:i.sha256,packageManifestPath:i.packageManifestPath,packageKind:i.packageKind,installTarget:i.installTarget,signature:i.signature,message:`Shennian Helper runtime is already installed: ${i.helperDir}`};const s=e.source?[t.resolve(e.source)]:_({platform:r,env:o,homedir:a}),c=s.map(S=>z({candidate:S,platform:r,io:n})).find(S=>S!=null);if(!c)return{ok:!1,platform:r,targetPlatform:r,installed:!1,repairable:!0,reasonCode:"helper_runtime_source_missing",message:"No installable Shennian Helper runtime package was found. Open Shennian Desktop, pass --source, or download the official helper package.",repairSuggestion:l("helper_runtime_source_missing"),checkedSources:s};const h=w({platform:r,manifestPath:c.packageManifestPath});if(!h.ok)return{ok:!1,platform:r,targetPlatform:r,installed:!1,repairable:!0,reasonCode:h.reasonCode,message:h.message,repairSuggestion:l(h.reasonCode),checkedSources:s};const g=R({platform:r,packageManifest:h.manifest,currentCliVersion:H()});if(!g.ok)return{ok:!1,platform:r,targetPlatform:r,installed:!1,repairable:g.repairable,reasonCode:g.reasonCode,message:g.message,repairSuggestion:l(g.reasonCode,h.manifest.minCliVersion),checkedSources:s};const d=J({platform:r,env:o,homedir:a,packageKind:c.packageKind});if(!d)return{ok:!1,platform:r,targetPlatform:r,installed:!1,repairable:!1,reasonCode:"helper_runtime_target_unavailable",message:"Unable to determine Helper runtime install target for this platform.",repairSuggestion:l("helper_runtime_target_unavailable"),checkedSources:s};X({platform:r,sourceDir:c.sourceDir,targetDir:d,io:n}),Y({sourceManifestPath:c.packageManifestPath,targetManifestPath:c.packageManifestPathInTarget(d),io:n});const m=C({platform:r,baseDir:c.manifestDirInTarget(d),verifyIntegrity:!0});if(!m.ok)return{ok:!1,platform:r,targetPlatform:r,installed:!1,repairable:!0,reasonCode:m.reasonCode,message:`Installed Helper runtime could not be verified: ${m.message}`,repairSuggestion:l(m.reasonCode),checkedSources:s};Z(r,m.helperPath,n);const p=w({platform:r,helperDir:m.helperDir});return p.ok?{ok:!0,action:"installed",platform:r,targetPlatform:r,installed:!0,repairable:!1,targetDir:d,helperDir:m.helperDir,helperPath:m.helperPath,helperVersion:m.version,runtimeVersion:m.version,protocolVersion:m.manifest.protocolVersion,minCliVersion:p.manifest.minCliVersion,sha256:p.manifest.sha256,packageManifestPath:p.manifestPath,packageKind:p.manifest.packageKind,installTarget:p.manifest.installTarget,signature:p.manifest.signature,message:`Installed Shennian Helper runtime to ${d}`}:{ok:!1,platform:r,targetPlatform:r,installed:!1,repairable:!0,reasonCode:p.reasonCode,message:`Installed Helper runtime package manifest could not be verified: ${p.message}`,repairSuggestion:l(p.reasonCode),checkedSources:s}}async function b(e={}){const r=new U({lockPath:T(e),owner:"helper-runtime-install",defaultWaitMs:0});try{return await r.run("helper-runtime-install",()=>N(e))}catch(o){if(L(o)){const n=k(e.platform)??void 0;return{ok:!1,platform:n,targetPlatform:n,installed:!1,repairable:!0,reasonCode:"wechat_automation_busy",message:o instanceof Error?o.message:String(o),repairSuggestion:"Wait for the current WeChat automation run to finish, then retry Helper install or repair."}}throw o}}function T(e){const r=e.env?.SHENNIAN_HOME?.trim()||process.env.SHENNIAN_HOME?.trim(),o=r?t.resolve(r):t.join(e.homedir??y.homedir(),".shennian");return t.join(o,"runtime","wechat-automation.lock")}function L(e){return e instanceof Error?e.message.includes("wechat_automation_busy"):String(e).includes("wechat_automation_busy")}async function N(e={}){const r=j(e);if(r.ok||e.source||e.officialDownload===!1||!W(r.reasonCode))return r.ok?{...r,source:r.source??"local"}:r;const o=k(e.platform);if(!o)return r;const n=await $({...e.officialDownload,platform:o,env:e.env??process.env});if(!n.ok)return{ok:!1,platform:o,targetPlatform:o,installed:!1,repairable:!0,reasonCode:n.reasonCode,message:n.message,repairSuggestion:l(n.reasonCode),checkedSources:r.checkedSources,officialDownload:{manifestUrl:n.manifestUrl,artifactUrl:n.artifactUrl}};try{const a=j({...e,source:n.sourceDir,platform:o});return a.ok?{...a,source:"official-download",officialDownload:{channel:n.channel,manifestUrl:n.manifestUrl,artifactUrl:n.artifactUrl,artifactSha256:n.artifactSha256,artifactSizeBytes:n.artifactSizeBytes,arch:n.arch}}:{...a,reasonCode:a.reasonCode==="helper_runtime_source_missing"?"helper_runtime_artifact_invalid":a.reasonCode,message:a.reasonCode==="helper_runtime_source_missing"?"Official Helper runtime artifact did not contain an installable runtime package.":a.message,repairSuggestion:l(a.reasonCode==="helper_runtime_source_missing"?"helper_runtime_artifact_invalid":a.reasonCode),officialDownload:{manifestUrl:n.manifestUrl,artifactUrl:n.artifactUrl}}}finally{n.cleanup()}}function W(e){return e==="helper_runtime_source_missing"||e==="helper_runtime_package_manifest_missing"||e==="helper_runtime_package_manifest_invalid"}function k(e){const r=e??process.platform;return r==="darwin"||r==="win32"?r:null}function R(e){return e.packageManifest.platform!==e.platform?{ok:!1,reasonCode:"helper_runtime_platform_mismatch",message:`Helper runtime package platform ${e.packageManifest.platform} does not match ${e.platform}.`,repairable:!0}:e.helperVersion&&e.packageManifest.helperVersion!==e.helperVersion?{ok:!1,reasonCode:"helper_runtime_version_mismatch",message:`Helper runtime package version ${e.packageManifest.helperVersion} does not match installed helper ${e.helperVersion}.`,repairable:!0}:e.protocolVersion&&e.packageManifest.protocolVersion!==e.protocolVersion?{ok:!1,reasonCode:"helper_runtime_protocol_mismatch",message:`Helper runtime package protocol ${e.packageManifest.protocolVersion} does not match installed helper protocol ${e.protocolVersion}.`,repairable:!0}:x(e.currentCliVersion,e.packageManifest.minCliVersion)!=="none"?{ok:!1,reasonCode:"helper_runtime_cli_too_old",message:`Shennian CLI ${e.currentCliVersion} is older than required ${e.packageManifest.minCliVersion}.`,repairable:!1}:{ok:!0}}function K(e){return e!=="unsupported_platform"&&e!=="helper_runtime_cli_too_old"}function l(e,r){if(e==="unsupported_platform")return"Use Shennian Helper runtime on macOS or Windows.";if(e==="unsupported_runtime_component")return"Use `shennian runtime install helper` or `shennian runtime repair helper`.";if(e==="helper_runtime_cli_too_old")return`Upgrade Shennian CLI${r?` to ${r} or newer`:""}, then retry.`;if(e==="helper_runtime_target_unavailable")return"Set the current user home/LOCALAPPDATA correctly, then retry.";if(e==="helper_runtime_required")return"Open Shennian Desktop or use the \u4F7F\u7528\u5FAE\u4FE1 page to install Helper.";if(e==="helper_runtime_source_missing")return"Open Shennian Desktop, retry from the \u4F7F\u7528\u5FAE\u4FE1 page, or pass `--source <path>`.";if(e==="helper_runtime_manifest_unavailable")return"Check network access to Shennian official downloads, then retry from the \u4F7F\u7528\u5FAE\u4FE1 page.";if(e==="helper_runtime_artifact_unavailable")return"Upgrade Shennian CLI or wait for this platform Helper runtime to be published.";if(e==="helper_runtime_download_failed")return"Check network access to Shennian official downloads, then retry.";if(e==="helper_runtime_artifact_integrity_mismatch")return"Retry the official Helper runtime download; the downloaded package did not match the published hash.";if(e==="helper_runtime_artifact_invalid")return"Retry after Shennian republishes a valid Helper runtime package for this platform.";if(e==="helper_runtime_package_manifest_missing"||e==="helper_runtime_package_manifest_invalid")return"Reinstall or repair the official Shennian Helper runtime package.";if(e==="helper_runtime_platform_mismatch")return"Install the Helper runtime package for this platform.";if(e==="helper_runtime_version_mismatch"||e==="helper_runtime_protocol_mismatch")return"Run `shennian runtime repair helper` with the matching official Helper runtime package.";if(e==="manifest_missing"||e==="helper_missing"||e==="integrity_mismatch"||e==="helper_not_executable")return"Run `shennian runtime repair helper` or reinstall Shennian Desktop."}function M(e){return{ok:!1,repairable:!1,reasonCode:"unsupported_runtime_component",message:`Unsupported runtime component: ${e}. Supported component: helper.`,repairSuggestion:l("unsupported_runtime_component")}}function F(e){return{existsSync:e?.existsSync??f.existsSync,readFileSync:e?.readFileSync??f.readFileSync,mkdirSync:e?.mkdirSync??f.mkdirSync,rmSync:e?.rmSync??f.rmSync,cpSync:e?.cpSync??f.cpSync,renameSync:e?.renameSync??f.renameSync,chmodSync:e?.chmodSync??f.chmodSync,homedir:e?.homedir??y.homedir,stopWindowsHelperProcesses:e?.stopWindowsHelperProcesses??Q}}function z(e){const r=t.resolve(e.candidate);return e.io.existsSync(r)?e.platform==="darwin"?B(r,e.io):G(r,e.io):null}function B(e,r){const o=e.endsWith(".app")?e:t.join(e,"Shennian Helper.app");if(r.existsSync(t.join(o,"Contents","Resources","wechat-channel","macos","manifest.json")))return{sourceDir:o,packageKind:"macos-app",packageManifestPath:I(r,[t.join(o,"Contents","Resources","helper-runtime-package.json"),t.join(e,u)]),packageManifestPathInTarget:a=>t.join(a,"Contents","Resources",u),manifestDirInTarget:a=>t.join(a,"Contents","Resources","wechat-channel","macos")};const n=r.existsSync(t.join(e,"manifest.json"))?e:t.join(e,"wechat-channel","macos");return r.existsSync(t.join(n,"manifest.json"))?{sourceDir:n,packageKind:"asset-dir",packageManifestPath:t.join(n,"helper-runtime-package.json"),packageManifestPathInTarget:a=>t.join(a,u),manifestDirInTarget:a=>a}:null}function G(e,r){const o=t.basename(e).toLowerCase()==="shennian helper"?e:t.join(e,"Shennian Helper");if(r.existsSync(t.join(o,"resources","wechat-channel","windows","manifest.json")))return{sourceDir:o,packageKind:"windows-runtime",packageManifestPath:I(r,[t.join(o,"resources",u),t.join(e,u)]),packageManifestPathInTarget:a=>t.join(a,"resources",u),manifestDirInTarget:a=>t.join(a,"resources","wechat-channel","windows")};const n=r.existsSync(t.join(e,"manifest.json"))?e:t.join(e,"wechat-channel","windows");return r.existsSync(t.join(n,"manifest.json"))?{sourceDir:n,packageKind:"asset-dir",packageManifestPath:t.join(n,"helper-runtime-package.json"),packageManifestPathInTarget:a=>t.join(a,u),manifestDirInTarget:a=>a}:null}function J(e){const r=D(e);if(!r)return null;if(e.platform==="darwin")return e.packageKind==="macos-app"?t.join(r,"Shennian Helper.app"):t.join(r,"wechat-channel","macos");if(e.packageKind==="windows-runtime"){const o=e.env.LOCALAPPDATA||(e.homedir?t.join(e.homedir,"AppData","Local"):"");return o?t.join(o,"Programs","Shennian Helper"):null}return t.join(r,"wechat-channel","windows")}function q(e,r){const o=t.resolve(r);if(e==="darwin"){const a=`${t.sep}Contents${t.sep}Resources${t.sep}wechat-channel${t.sep}macos`;return o.endsWith(a)?o.slice(0,-a.length):o}const n=`${t.sep}resources${t.sep}wechat-channel${t.sep}windows`;return o.endsWith(n)?o.slice(0,-n.length):o}function X(e){const r=t.resolve(e.sourceDir),o=t.resolve(e.targetDir);if(r===o)return;const n=`${o}.tmp-${process.pid}-${Date.now()}`;e.io.rmSync(n,{recursive:!0,force:!0}),e.io.mkdirSync(t.dirname(o),{recursive:!0}),e.io.cpSync(r,n,{recursive:!0}),e.platform==="win32"&&e.io.stopWindowsHelperProcesses(),e.io.rmSync(o,{recursive:!0,force:!0}),e.io.renameSync(n,o)}function Q(){if(process.platform!=="win32")return;const e=A("taskkill",["/IM","shennian-wechat-channel-helper.exe","/T","/F"],{stdio:"ignore",windowsHide:!0});if(e.status!==0&&e.status!==128)throw new Error(`taskkill shennian-wechat-channel-helper.exe exited ${e.status}`)}function Y(e){const r=t.resolve(e.sourceManifestPath),o=t.resolve(e.targetManifestPath);!e.io.existsSync(r)||r===o||(e.io.mkdirSync(t.dirname(o),{recursive:!0}),e.io.cpSync(r,o))}function I(e,r){return r.find(o=>e.existsSync(o))??r[0]}function Z(e,r,o){if(e==="darwin")try{const n=f.statSync(r);(n.mode&73)===0&&o.chmodSync(r,n.mode|493)}catch{}}function _(e){const r=e.homedir||(e.platform==="win32"?e.env.USERPROFILE:e.env.HOME),o=[],n=a=>{if(!a)return;const i=t.resolve(a);o.includes(i)||o.push(i)};if(n(e.env.SHENNIAN_HELPER_PACKAGE_DIR),n(e.env[O]),n(e.env[E]),e.platform==="darwin")r&&(n(t.join(r,"Applications","Shennian.app","Contents","Resources")),n(t.join(r,"Applications","Shennian Helper.app"))),n(t.join("/Applications","Shennian.app","Contents","Resources")),n(t.join("/Applications","Shennian Helper.app"));else{const a=e.env.LOCALAPPDATA||(r?t.join(r,"AppData","Local"):""),i=e.env.ProgramFiles||e.env.PROGRAMFILES,s=e.env["ProgramFiles(x86)"]||e.env["PROGRAMFILES(X86)"];a&&(n(t.join(a,"Programs","Shennian","resources")),n(t.join(a,"Programs","Shennian Helper"))),i&&(n(t.join(i,"Shennian","resources")),n(t.join(i,"Shennian Helper"))),s&&(n(t.join(s,"Shennian","resources")),n(t.join(s,"Shennian Helper")))}return n(t.resolve(process.cwd(),"packages/helper-runtime/dist",e.platform==="darwin"?"macos":"windows")),n(t.resolve(process.cwd(),"packages/helper-runtime/wechat-channel",e.platform==="darwin"?"macos":"windows")),o}function P(e,r){if(r){console.log(JSON.stringify(e,null,2));return}if(e.ok){console.log(v.green(`\u2713 ${e.message}`)),"helperVersion"in e&&console.log(`version=${e.helperVersion} protocol=${e.protocolVersion}`),"helperPath"in e&&console.log(e.helperPath);return}console.error(v.red(`\u2717 ${e.reasonCode}`)),console.error(e.message)}export{V as getHelperRuntimeStatus,j as installHelperRuntime,b as installHelperRuntimeWithOfficialDownload,ce as registerRuntimeCommand};
|
|
1
|
+
import m from"node:fs";import v from"node:os";import t from"node:path";import{spawnSync as C}from"node:child_process";import H from"chalk";import{getDefaultWeChatHelperRuntimeRoot as V,readWeChatChannelHelperRuntimePackageManifest as y,resolveWeChatChannelHelperAsset as b,SHENNIAN_HELPER_RUNTIME_DIR_ENV as U,WECHAT_CHANNEL_HELPER_DIR_ENV as T}from"../channels/wechat-channel/helper-assets.js";import{downloadOfficialHelperRuntimePackage as L}from"./helper-runtime-official-download.js";import{compareVersions as N,getCurrentVersion as j}from"../upgrade/engine.js";import{SerialWeChatAutomationLane as W}from"../channels/wechat-channel/automation-lane.js";const u="helper-runtime-package.json";function ge(e){const r=e.command("runtime").description("Install, repair, and inspect Shennian local runtimes");r.command("status").description("Print local Shennian runtime status").option("--json","Output JSON",!1).option("--platform <platform>","Platform override: darwin or win32").action(o=>{const a=R({platform:o.platform});_(a,!!o.json),a.ok||(process.exitCode=1)}),r.command("install").description("Install a Shennian runtime component").argument("<component>","Runtime component, currently only: helper").option("--source <path>","Helper runtime package/source directory").option("--channel <channel>","Official Helper runtime channel: stable or next").option("--manifest-url <url>","Official Helper runtime manifest URL override").option("--no-official-download","Do not fall back to Shennian official Helper runtime download").option("--json","Output JSON",!1).option("--platform <platform>","Platform override: darwin or win32").action(async(o,a)=>{const n=o==="helper"?await P({source:a.source,platform:a.platform,officialDownload:a.officialDownload===!1?!1:{channel:a.channel,manifestUrl:a.manifestUrl}}):D(o);_(n,!!a.json),n.ok||(process.exitCode=1)}),r.command("upgrade").description("Upgrade a Shennian runtime component").argument("<component>","Runtime component, currently only: helper").option("--source <path>","Helper runtime package/source directory").option("--channel <channel>","Official Helper runtime channel: stable or next").option("--manifest-url <url>","Official Helper runtime manifest URL override").option("--no-official-download","Do not use Shennian official Helper runtime download").option("--json","Output JSON",!1).option("--platform <platform>","Platform override: darwin or win32").action(async(o,a)=>{const n=o==="helper"?await P({source:a.source,platform:a.platform,force:!0,preferOfficialDownload:!a.source,officialDownload:a.officialDownload===!1?!1:{channel:a.channel,manifestUrl:a.manifestUrl}}):D(o);_(n,!!a.json),n.ok||(process.exitCode=1)}),r.command("repair").description("Repair a Shennian runtime component").argument("[component]","Runtime component, currently only: helper","helper").option("--source <path>","Helper runtime package/source directory").option("--channel <channel>","Official Helper runtime channel: stable or next").option("--manifest-url <url>","Official Helper runtime manifest URL override").option("--no-official-download","Do not fall back to Shennian official Helper runtime download").option("--json","Output JSON",!1).option("--platform <platform>","Platform override: darwin or win32").action(async(o,a)=>{const n=o==="helper"?await P({source:a.source,platform:a.platform,force:!0,preferOfficialDownload:!a.source,officialDownload:a.officialDownload===!1?!1:{channel:a.channel,manifestUrl:a.manifestUrl}}):D(o);_(n,!!a.json),n.ok||(process.exitCode=1)})}function R(e={}){const r=k(e.platform);if(!r)return{ok:!1,installed:!1,repairable:!1,reasonCode:"unsupported_platform",message:"Shennian Helper runtime is only available on macOS and Windows.",repairSuggestion:c("unsupported_platform")};const o=e.env??process.env,a=e.homedir,n=b({platform:r,env:o,homedir:a,includeInstalledDesktop:!1}),i=V({platform:r,env:o,homedir:a});if(!n.ok)return{ok:!1,platform:r,targetPlatform:r,installed:!1,repairable:G(n.reasonCode),reasonCode:n.reasonCode,message:n.message,runtimeRoot:i,repairSuggestion:c(n.reasonCode),checkedSources:S({platform:r,env:o,homedir:a})};const s=y({platform:r,helperDir:n.helperDir});if(!s.ok)return{ok:!1,platform:r,targetPlatform:r,installed:!1,repairable:!0,reasonCode:s.reasonCode,message:s.message,helperDir:n.helperDir,helperPath:n.helperPath,helperVersion:n.version,runtimeVersion:n.version,protocolVersion:n.manifest.protocolVersion,runtimeRoot:i,repairSuggestion:c(s.reasonCode),checkedSources:S({platform:r,env:o,homedir:a})};const l=O({platform:r,packageManifest:s.manifest,currentCliVersion:j(),helperVersion:n.version,protocolVersion:n.manifest.protocolVersion});return l.ok?{ok:!0,platform:r,targetPlatform:r,installed:!0,repairable:!1,message:`Shennian Helper runtime is installed: ${n.helperDir}`,helperDir:n.helperDir,helperPath:n.helperPath,helperVersion:n.version,runtimeVersion:n.version,protocolVersion:n.manifest.protocolVersion,minCliVersion:s.manifest.minCliVersion,sha256:s.manifest.sha256,packageManifestPath:s.manifestPath,packageKind:s.manifest.packageKind,installTarget:s.manifest.installTarget,signature:s.manifest.signature,runtimeRoot:i}:{ok:!1,platform:r,targetPlatform:r,installed:!0,repairable:l.repairable,reasonCode:l.reasonCode,message:l.message,helperDir:n.helperDir,helperPath:n.helperPath,helperVersion:n.version,runtimeVersion:n.version,protocolVersion:n.manifest.protocolVersion,minCliVersion:s.manifest.minCliVersion,sha256:s.manifest.sha256,packageManifestPath:s.manifestPath,packageKind:s.manifest.packageKind,installTarget:s.manifest.installTarget,signature:s.manifest.signature,runtimeRoot:i,repairSuggestion:c(l.reasonCode,s.manifest.minCliVersion),checkedSources:S({platform:r,env:o,homedir:a})}}function M(e={}){const r=k(e.platform);if(!r)return{ok:!1,reasonCode:"unsupported_platform",message:"Shennian Helper runtime is only available on macOS and Windows.",repairable:!1,repairSuggestion:c("unsupported_platform")};const o=e.env??process.env,a=J(e.io),n=e.homedir??a.homedir(),i=R({platform:r,env:o,homedir:n});if(i.ok&&!e.force&&i.helperDir&&i.helperPath&&i.helperVersion&&i.protocolVersion)return{ok:!0,action:"already_installed",platform:r,targetPlatform:r,installed:!0,repairable:!1,targetDir:Z(r,i.helperDir),helperDir:i.helperDir,helperPath:i.helperPath,helperVersion:i.helperVersion,runtimeVersion:i.helperVersion,protocolVersion:i.protocolVersion,minCliVersion:i.minCliVersion,sha256:i.sha256,packageManifestPath:i.packageManifestPath,packageKind:i.packageKind,installTarget:i.installTarget,signature:i.signature,message:`Shennian Helper runtime is already installed: ${i.helperDir}`};const s=e.source?[t.resolve(e.source)]:S({platform:r,env:o,homedir:n}),l=s.map(w=>q({candidate:w,platform:r,io:a})).find(w=>w!=null);if(!l)return{ok:!1,platform:r,targetPlatform:r,installed:!1,repairable:!0,reasonCode:"helper_runtime_source_missing",message:"No installable Shennian Helper runtime package was found. Open Shennian Desktop, pass --source, or download the official helper package.",repairSuggestion:c("helper_runtime_source_missing"),checkedSources:s};const h=y({platform:r,manifestPath:l.packageManifestPath});if(!h.ok)return{ok:!1,platform:r,targetPlatform:r,installed:!1,repairable:!0,reasonCode:h.reasonCode,message:h.message,repairSuggestion:c(h.reasonCode),checkedSources:s};const g=O({platform:r,packageManifest:h.manifest,currentCliVersion:j()});if(!g.ok)return{ok:!1,platform:r,targetPlatform:r,installed:!1,repairable:g.repairable,reasonCode:g.reasonCode,message:g.message,repairSuggestion:c(g.reasonCode,h.manifest.minCliVersion),checkedSources:s};const d=Y({platform:r,env:o,homedir:n,packageKind:l.packageKind});if(!d)return{ok:!1,platform:r,targetPlatform:r,installed:!1,repairable:!1,reasonCode:"helper_runtime_target_unavailable",message:"Unable to determine Helper runtime install target for this platform.",repairSuggestion:c("helper_runtime_target_unavailable"),checkedSources:s};ee({platform:r,sourceDir:l.sourceDir,targetDir:d,io:a}),te({sourceManifestPath:l.packageManifestPath,targetManifestPath:l.packageManifestPathInTarget(d),io:a});const f=b({platform:r,baseDir:l.manifestDirInTarget(d),verifyIntegrity:!0});if(!f.ok)return{ok:!1,platform:r,targetPlatform:r,installed:!1,repairable:!0,reasonCode:f.reasonCode,message:`Installed Helper runtime could not be verified: ${f.message}`,repairSuggestion:c(f.reasonCode),checkedSources:s};ie(r,f.helperPath,a);const p=y({platform:r,helperDir:f.helperDir});return p.ok?{ok:!0,action:"installed",platform:r,targetPlatform:r,installed:!0,repairable:!1,targetDir:d,helperDir:f.helperDir,helperPath:f.helperPath,helperVersion:f.version,runtimeVersion:f.version,protocolVersion:f.manifest.protocolVersion,minCliVersion:p.manifest.minCliVersion,sha256:p.manifest.sha256,packageManifestPath:p.manifestPath,packageKind:p.manifest.packageKind,installTarget:p.manifest.installTarget,signature:p.manifest.signature,message:`Installed Shennian Helper runtime to ${d}`}:{ok:!1,platform:r,targetPlatform:r,installed:!1,repairable:!0,reasonCode:p.reasonCode,message:`Installed Helper runtime package manifest could not be verified: ${p.message}`,repairSuggestion:c(p.reasonCode),checkedSources:s}}async function P(e={}){const r=new W({lockPath:K(e),owner:"helper-runtime-install",defaultWaitMs:0});try{return await r.run("helper-runtime-install",()=>B(e))}catch(o){if(F(o)){const a=k(e.platform)??void 0;return{ok:!1,platform:a,targetPlatform:a,installed:!1,repairable:!0,reasonCode:"wechat_automation_busy",message:o instanceof Error?o.message:String(o),repairSuggestion:"Wait for the current WeChat automation run to finish, then retry Helper install or repair."}}throw o}}function K(e){const r=e.env?.SHENNIAN_HOME?.trim()||process.env.SHENNIAN_HOME?.trim(),o=r?t.resolve(r):t.join(e.homedir??v.homedir(),".shennian");return t.join(o,"runtime","wechat-automation.lock")}function F(e){return e instanceof Error?e.message.includes("wechat_automation_busy"):String(e).includes("wechat_automation_busy")}async function B(e={}){if(e.preferOfficialDownload&&!e.source&&e.officialDownload!==!1)return await I(e);const r=M(e);return r.ok||e.source||e.officialDownload===!1||!z(r.reasonCode)?r.ok?{...r,source:r.source??"local"}:r:await I(e,r)}async function I(e,r){const o=k(e.platform);if(!o)return r??{ok:!1,repairable:!1,reasonCode:"unsupported_platform",message:"Shennian Helper runtime is only available on macOS and Windows.",repairSuggestion:c("unsupported_platform")};const a=e.officialDownload===!1||e.officialDownload==null?{}:e.officialDownload,n=await L({...a,platform:o,env:e.env??process.env});if(!n.ok)return{ok:!1,platform:o,targetPlatform:o,installed:!1,repairable:!0,reasonCode:n.reasonCode,message:n.message,repairSuggestion:c(n.reasonCode),checkedSources:r?.checkedSources,officialDownload:{manifestUrl:n.manifestUrl,artifactUrl:n.artifactUrl}};try{const i=M({...e,source:n.sourceDir,platform:o,force:e.force??e.preferOfficialDownload??!1});return i.ok?{...i,source:"official-download",officialDownload:{channel:n.channel,manifestUrl:n.manifestUrl,artifactUrl:n.artifactUrl,artifactSha256:n.artifactSha256,artifactSizeBytes:n.artifactSizeBytes,arch:n.arch,helperVersion:n.helperVersion,protocolVersion:n.protocolVersion,minCliVersion:n.minCliVersion}}:{...i,reasonCode:i.reasonCode==="helper_runtime_source_missing"?"helper_runtime_artifact_invalid":i.reasonCode,message:i.reasonCode==="helper_runtime_source_missing"?"Official Helper runtime artifact did not contain an installable runtime package.":i.message,repairSuggestion:c(i.reasonCode==="helper_runtime_source_missing"?"helper_runtime_artifact_invalid":i.reasonCode),officialDownload:{manifestUrl:n.manifestUrl,artifactUrl:n.artifactUrl}}}finally{n.cleanup()}}function z(e){return e==="helper_runtime_source_missing"||e==="helper_runtime_package_manifest_missing"||e==="helper_runtime_package_manifest_invalid"}function k(e){const r=e??process.platform;return r==="darwin"||r==="win32"?r:null}function O(e){return e.packageManifest.platform!==e.platform?{ok:!1,reasonCode:"helper_runtime_platform_mismatch",message:`Helper runtime package platform ${e.packageManifest.platform} does not match ${e.platform}.`,repairable:!0}:e.helperVersion&&e.packageManifest.helperVersion!==e.helperVersion?{ok:!1,reasonCode:"helper_runtime_version_mismatch",message:`Helper runtime package version ${e.packageManifest.helperVersion} does not match installed helper ${e.helperVersion}.`,repairable:!0}:e.protocolVersion&&e.packageManifest.protocolVersion!==e.protocolVersion?{ok:!1,reasonCode:"helper_runtime_protocol_mismatch",message:`Helper runtime package protocol ${e.packageManifest.protocolVersion} does not match installed helper protocol ${e.protocolVersion}.`,repairable:!0}:N(e.currentCliVersion,e.packageManifest.minCliVersion)!=="none"?{ok:!1,reasonCode:"helper_runtime_cli_too_old",message:`Shennian CLI ${e.currentCliVersion} is older than required ${e.packageManifest.minCliVersion}.`,repairable:!1}:{ok:!0}}function G(e){return e!=="unsupported_platform"&&e!=="helper_runtime_cli_too_old"}function c(e,r){if(e==="unsupported_platform")return"Use Shennian Helper runtime on macOS or Windows.";if(e==="unsupported_runtime_component")return"Use `shennian runtime install helper` or `shennian runtime repair helper`.";if(e==="helper_runtime_cli_too_old")return`Upgrade Shennian CLI${r?` to ${r} or newer`:""}, then retry.`;if(e==="helper_runtime_target_unavailable")return"Set the current user home/LOCALAPPDATA correctly, then retry.";if(e==="helper_runtime_required")return"Open Shennian Desktop or use the \u4F7F\u7528\u5FAE\u4FE1 page to install Helper.";if(e==="helper_runtime_source_missing")return"Open Shennian Desktop, retry from the \u4F7F\u7528\u5FAE\u4FE1 page, or pass `--source <path>`.";if(e==="helper_runtime_manifest_unavailable")return"Check network access to Shennian official downloads, then retry from the \u4F7F\u7528\u5FAE\u4FE1 page.";if(e==="helper_runtime_artifact_unavailable")return"Upgrade Shennian CLI or wait for this platform Helper runtime to be published.";if(e==="helper_runtime_download_failed")return"Check network access to Shennian official downloads, then retry.";if(e==="helper_runtime_artifact_integrity_mismatch")return"Retry the official Helper runtime download; the downloaded package did not match the published hash.";if(e==="helper_runtime_artifact_invalid")return"Retry after Shennian republishes a valid Helper runtime package for this platform.";if(e==="helper_runtime_package_manifest_missing"||e==="helper_runtime_package_manifest_invalid")return"Reinstall or repair the official Shennian Helper runtime package.";if(e==="helper_runtime_platform_mismatch")return"Install the Helper runtime package for this platform.";if(e==="helper_runtime_version_mismatch"||e==="helper_runtime_protocol_mismatch")return"Run `shennian runtime repair helper` with the matching official Helper runtime package.";if(e==="manifest_missing"||e==="helper_missing"||e==="integrity_mismatch"||e==="helper_not_executable")return"Run `shennian runtime repair helper` or reinstall Shennian Desktop."}function D(e){return{ok:!1,repairable:!1,reasonCode:"unsupported_runtime_component",message:`Unsupported runtime component: ${e}. Supported component: helper.`,repairSuggestion:c("unsupported_runtime_component")}}function J(e){return{existsSync:e?.existsSync??m.existsSync,lstatSync:e?.lstatSync??m.lstatSync,readFileSync:e?.readFileSync??m.readFileSync,realpathSync:e?.realpathSync??m.realpathSync,mkdirSync:e?.mkdirSync??m.mkdirSync,rmSync:e?.rmSync??m.rmSync,cpSync:e?.cpSync??m.cpSync,renameSync:e?.renameSync??m.renameSync,chmodSync:e?.chmodSync??m.chmodSync,homedir:e?.homedir??v.homedir,stopMacHelperProcesses:e?.stopMacHelperProcesses??ne,stopWindowsHelperProcesses:e?.stopWindowsHelperProcesses??oe}}function q(e){const r=t.resolve(e.candidate);return!e.io.existsSync(r)||re(r,e.io)?null:e.platform==="darwin"?X(r,e.io):Q(r,e.io)}function X(e,r){const o=e.endsWith(".app")?e:t.join(e,"Shennian Helper.app");if(r.existsSync(t.join(o,"Contents","Resources","wechat-channel","macos","manifest.json")))return{sourceDir:o,packageKind:"macos-app",packageManifestPath:$(r,[t.join(o,"Contents","Resources","helper-runtime-package.json"),t.join(e,u)]),packageManifestPathInTarget:n=>t.join(n,"Contents","Resources",u),manifestDirInTarget:n=>t.join(n,"Contents","Resources","wechat-channel","macos")};const a=r.existsSync(t.join(e,"manifest.json"))?e:t.join(e,"wechat-channel","macos");return r.existsSync(t.join(a,"manifest.json"))?{sourceDir:a,packageKind:"asset-dir",packageManifestPath:t.join(a,"helper-runtime-package.json"),packageManifestPathInTarget:n=>t.join(n,u),manifestDirInTarget:n=>n}:null}function Q(e,r){const o=t.basename(e).toLowerCase()==="shennian helper"?e:t.join(e,"Shennian Helper");if(r.existsSync(t.join(o,"resources","wechat-channel","windows","manifest.json")))return{sourceDir:o,packageKind:"windows-runtime",packageManifestPath:$(r,[t.join(o,"resources",u),t.join(e,u)]),packageManifestPathInTarget:n=>t.join(n,"resources",u),manifestDirInTarget:n=>t.join(n,"resources","wechat-channel","windows")};const a=r.existsSync(t.join(e,"manifest.json"))?e:t.join(e,"wechat-channel","windows");return r.existsSync(t.join(a,"manifest.json"))?{sourceDir:a,packageKind:"asset-dir",packageManifestPath:t.join(a,"helper-runtime-package.json"),packageManifestPathInTarget:n=>t.join(n,u),manifestDirInTarget:n=>n}:null}function Y(e){const r=V(e);if(!r)return null;if(e.platform==="darwin")return e.packageKind==="macos-app"?t.join(r,"Shennian Helper.app"):t.join(r,"wechat-channel","macos");if(e.packageKind==="windows-runtime"){const o=e.env.LOCALAPPDATA||(e.homedir?t.join(e.homedir,"AppData","Local"):"");return o?t.join(o,"Programs","Shennian Helper"):null}return t.join(r,"wechat-channel","windows")}function Z(e,r){const o=t.resolve(r);if(e==="darwin"){const n=`${t.sep}Contents${t.sep}Resources${t.sep}wechat-channel${t.sep}macos`;return o.endsWith(n)?o.slice(0,-n.length):o}const a=`${t.sep}resources${t.sep}wechat-channel${t.sep}windows`;return o.endsWith(a)?o.slice(0,-a.length):o}function ee(e){const r=t.resolve(e.sourceDir),o=t.resolve(e.targetDir);if(r===o)return;const a=A(r,e.io),n=A(o,e.io);if(a&&n&&a===n)return;const i=`${o}.tmp-${process.pid}-${Date.now()}`;e.io.rmSync(i,{recursive:!0,force:!0}),e.io.mkdirSync(t.dirname(o),{recursive:!0}),e.io.cpSync(r,i,{recursive:!0}),e.platform==="darwin"&&e.io.stopMacHelperProcesses(o),e.platform==="win32"&&e.io.stopWindowsHelperProcesses(),e.io.rmSync(o,{recursive:!0,force:!0}),e.io.renameSync(i,o)}function re(e,r){try{return r.lstatSync(e).isSymbolicLink()}catch{return!1}}function A(e,r){try{return r.realpathSync(e)}catch{return null}}function ne(e){if(process.platform!=="darwin")return;const r=t.join(t.resolve(e),"Contents","MacOS","Shennian Helper"),o=C("ps",["-axo","pid=,command="],{encoding:"utf8"});if(o.status!==0)return;const a=o.stdout.split(/\r?\n/u).map(n=>{const i=/^\s*(\d+)\s+(.+)$/u.exec(n);if(!i)return null;const s=Number(i[1]),l=i[2];return Number.isInteger(s)&&l.includes(r)?s:null}).filter(n=>!!(n&&n!==process.pid));for(const n of a)try{process.kill(n,"SIGTERM")}catch{}x(a,1e3);for(const n of a)if(E(n))try{process.kill(n,"SIGKILL")}catch{}x(a,500)}function x(e,r){const o=Date.now()+r;for(;Date.now()<o&&e.some(E);)ae(50)}function ae(e){Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,e)}function E(e){try{return process.kill(e,0),!0}catch{return!1}}function oe(){if(process.platform!=="win32")return;const e=C("taskkill",["/IM","shennian-wechat-channel-helper.exe","/T","/F"],{stdio:"ignore",windowsHide:!0});if(e.status!==0&&e.status!==128)throw new Error(`taskkill shennian-wechat-channel-helper.exe exited ${e.status}`)}function te(e){const r=t.resolve(e.sourceManifestPath),o=t.resolve(e.targetManifestPath);!e.io.existsSync(r)||r===o||(e.io.mkdirSync(t.dirname(o),{recursive:!0}),e.io.cpSync(r,o))}function $(e,r){return r.find(o=>e.existsSync(o))??r[0]}function ie(e,r,o){if(e==="darwin")try{const a=m.statSync(r);(a.mode&73)===0&&o.chmodSync(r,a.mode|493)}catch{}}function S(e){const r=e.homedir||(e.platform==="win32"?e.env.USERPROFILE:e.env.HOME),o=[],a=n=>{if(!n)return;const i=t.resolve(n);o.includes(i)||o.push(i)};if(a(e.env.SHENNIAN_HELPER_PACKAGE_DIR),a(e.env[T]),a(e.env[U]),e.platform==="darwin")r&&(a(t.join(r,"Applications","Shennian.app","Contents","Resources")),a(t.join(r,"Applications","Shennian Helper.app"))),a(t.join("/Applications","Shennian.app","Contents","Resources")),a(t.join("/Applications","Shennian Helper.app"));else{const n=e.env.LOCALAPPDATA||(r?t.join(r,"AppData","Local"):""),i=e.env.ProgramFiles||e.env.PROGRAMFILES,s=e.env["ProgramFiles(x86)"]||e.env["PROGRAMFILES(X86)"];n&&(a(t.join(n,"Programs","Shennian","resources")),a(t.join(n,"Programs","Shennian Helper"))),i&&(a(t.join(i,"Shennian","resources")),a(t.join(i,"Shennian Helper"))),s&&(a(t.join(s,"Shennian","resources")),a(t.join(s,"Shennian Helper")))}return a(t.resolve(process.cwd(),"packages/helper-runtime/dist",e.platform==="darwin"?"macos":"windows")),a(t.resolve(process.cwd(),"packages/helper-runtime/wechat-channel",e.platform==="darwin"?"macos":"windows")),o}function _(e,r){if(r){console.log(JSON.stringify(e,null,2));return}if(e.ok){console.log(H.green(`\u2713 ${e.message}`)),"helperVersion"in e&&console.log(`version=${e.helperVersion} protocol=${e.protocolVersion}`),"helperPath"in e&&console.log(e.helperPath);return}console.error(H.red(`\u2717 ${e.reasonCode}`)),console.error(e.message)}export{R as getHelperRuntimeStatus,M as installHelperRuntime,P as installHelperRuntimeWithOfficialDownload,ge as registerRuntimeCommand};
|
package/dist/src/config/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import r from"node:fs";import
|
|
1
|
+
import r from"node:fs";import e from"node:path";import s from"node:os";function t(){const n=process.env.SHENNIAN_HOME?.trim();return n?e.resolve(n):u()?e.join(s.tmpdir(),"shennian-vitest-runtime",String(process.pid)):e.join(s.homedir(),".shennian")}function c(...n){return e.join(t(),...n)}function o(){return c("config.json")}const h=o();function S(){try{const n=o();if(!r.existsSync(n))return{};const i=JSON.parse(f(r.readFileSync(n,"utf-8")));return i.serverUrl?.includes("shennian.ai")&&(i.serverUrl="https://shennian.net"),i}catch{return{}}}function l(n){r.mkdirSync(t(),{recursive:!0}),r.writeFileSync(o(),JSON.stringify(n,null,2))}function v(){r.mkdirSync(t(),{recursive:!0}),r.mkdirSync(c("sessions"),{recursive:!0})}function u(){return(process.env.VITEST==="true"||!!process.env.VITEST_WORKER_ID)&&!process.env.SHENNIAN_HOME?.trim()}function f(n){return n.charCodeAt(0)===65279?n.slice(1):n}export{h as configPath,v as ensureDir,o as getConfigPath,t as getShennianDir,S as loadConfig,c as resolveShennianPath,l as saveConfig};
|
|
@@ -3,6 +3,7 @@ import { type AgentType, type ExternalChannelSessionStatus, type ReqFrame } from
|
|
|
3
3
|
import { ManagerRegistry } from './registry.js';
|
|
4
4
|
import type { SessionManagerRuntime } from '../session/types.js';
|
|
5
5
|
import { ChannelRuntime, type ChannelRuntimeOptions } from '../channels/runtime.js';
|
|
6
|
+
import { type WeChatRpaSessionContext } from '../channels/wechat-rpa-session-sync.js';
|
|
6
7
|
import { type WeChatAutomationLane } from '../channels/wechat-channel/automation-lane.js';
|
|
7
8
|
export type LocalReqDispatcher = (req: ReqFrame) => Promise<void>;
|
|
8
9
|
type ManagerRuntimeServiceOptions = {
|
|
@@ -18,6 +19,7 @@ export declare class ManagerRuntimeService {
|
|
|
18
19
|
private opts;
|
|
19
20
|
readonly registry: ManagerRegistry;
|
|
20
21
|
readonly channelRuntime: ChannelRuntime;
|
|
22
|
+
private readonly weChatRpaSessionSync;
|
|
21
23
|
private server;
|
|
22
24
|
private ipcUrl;
|
|
23
25
|
private ipcToken;
|
|
@@ -26,6 +28,9 @@ export declare class ManagerRuntimeService {
|
|
|
26
28
|
private workerTextAcc;
|
|
27
29
|
private readonly weChatAutomationLane;
|
|
28
30
|
constructor(opts: ManagerRuntimeServiceOptions);
|
|
31
|
+
private listWeChatRpaSessionContexts;
|
|
32
|
+
notifyWeChatRpaSessionBinding(session: WeChatRpaSessionContext): Promise<void>;
|
|
33
|
+
reconcileWeChatRpaSessionBindings(): Promise<void>;
|
|
29
34
|
start(): Promise<void>;
|
|
30
35
|
private doStart;
|
|
31
36
|
ready(): Promise<void>;
|
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
import
|
|
2
|
-
`).trim()}function ge(r){try{const e=JSON.parse(r),a=e.type==="tool_result"||e.result?"tool_result":"tool_call",n=e.name||"tool",t=typeof e.result=="string"?e.result.replace(/\s+/g," ").trim():"",s=t.length>220?`${t.slice(0,220)}...`:t;return s?`[${a}] ${n}: ${s}`:`[${a}] ${n}`}catch{return"[tool]"}}function $(r){if(!r||typeof r!="object")return;const e=r,a=String(e.kind||""),n=String(e.name||""),t=String(e.mimeType||""),s=String(e.dataBase64||""),o=String(e.localPath||""),i=String(e.url||""),c=Number(e.size||0);if(s)throw new Error("Manager IPC external attachments must use localPath or url; dataBase64 is not accepted");if(!(a!=="image"&&a!=="video"&&a!=="file")&&!(!n||!t||!Number.isFinite(c)||c<0)&&!(!o&&!i))return{kind:a,name:n,mimeType:t,size:c,...o?{localPath:o}:{},...i?{url:i}:{}}}function _(r){const e=r.binding&&typeof r.binding=="object"&&!Array.isArray(r.binding)?r.binding:{},a=String(e.sessionId||r.managerSessionId||"").trim(),n=String(e.channelId||"").trim(),t=String(e.conversationId||"").trim(),s=String(e.conversationName||r.conversation||"").trim(),o=String(e.workDir||r.workDir||"").trim();if(!a)throw new Error("WeChat tool sessionId is required");if(!n)throw new Error("WeChat tool channelId is required");if(!t)throw new Error("WeChat tool conversationId is required");if(!s)throw new Error("WeChat tool conversationName is required");if(!o)throw new Error("WeChat tool workDir is required");return{sessionId:a,channelId:n,conversationId:t,conversationName:s,workDir:o}}function me(r,e){const a=[...r].sort((d,l)=>d.ts-l.ts),n=[];let t=null,s=null,o=null,i="";const c=()=>{if(!t)return;const d=i.trim();d&&n.push({...t,id:`${t.id}-compact`,payload:d}),t=null,s=null,o=null,i=""};for(const d of a){if(X(d.payload)){c();continue}if(d.role==="user"){c(),n.push(d);continue}if(Z(d.payload)){c(),n.push({...d,payload:ge(d.payload)});continue}const l=J(d.payload);if(!l.trim())continue;const h=de(d.id),g=he(d.id);t&&t.role===d.role&&s===h&&h&&g!==null&&o!==null&&g===o+1?(i+=l,t.ts=d.ts,o=g):(c(),t=d,s=h,o=g,i=l)}return c(),n.slice(-e).sort((d,l)=>l.ts-d.ts)}async function pe(r){const e=[];let a=0;for await(const t of r){const s=Buffer.from(t);if(a+=s.byteLength,Number.isFinite(C)&&C>0&&a>C)throw new Error(`Manager IPC request body is too large. Max: ${C} bytes.`);e.push(s)}const n=Buffer.concat(e).toString("utf-8");return n?JSON.parse(n):{}}function w(r,e,a,n){r.client.sendRes({type:"res",id:e,ok:a,...a?{payload:n}:{error:String(n.error||"unknown error")}})}function B(r){return/binding not found|unknown method|not supported|relay is not connected|no external channel/i.test(r)}function q(){return fe()?y.join(D.tmpdir(),"shennian-vitest-runtime",String(process.pid),"manager-ipc.json"):se("runtime","manager-ipc.json")}function fe(){return(process.env.VITEST==="true"||!!process.env.VITEST_WORKER_ID)&&!process.env.SHENNIAN_HOME?.trim()}class Ke{opts;registry=new ee;channelRuntime;server=null;ipcUrl=null;ipcToken=j(24).toString("hex");healthTimer=null;startPromise=null;workerTextAcc=new Map;weChatAutomationLane;constructor(e){this.opts=e,this.weChatAutomationLane=e.weChatAutomationLane??le(),this.channelRuntime=e.channelRuntime??new ne((a,n)=>{this.handleExternalMessage(a,n)},a=>this.registry.createReplyTarget(a).replyTarget,{createWeChatRpaProductRunner:e.createWeChatRpaProductRunner,weChatAutomationLane:this.weChatAutomationLane})}async start(){return this.startPromise?this.startPromise:(this.startPromise=this.doStart(),this.startPromise)}async doStart(){if(this.server)return;this.server=G.createServer((a,n)=>{this.handleIpc(a,n)}),this.server.requestTimeout=A,this.server.timeout=A,this.server.keepAliveTimeout=A,this.server.headersTimeout=A+5e3,await new Promise((a,n)=>{this.server.once("error",n),this.server.listen(0,"127.0.0.1",()=>a())});const e=this.server.address();typeof e=="object"&&e&&(this.ipcUrl=`http://127.0.0.1:${e.port}`,this.writeIpcRuntimeFile()),this.server.unref(),await this.channelRuntime.start(),this.broadcastConfiguredChannelStatuses(),this.healthTimer=setInterval(()=>this.scanWorkerHealth(),6e4),this.healthTimer.unref()}async ready(){await this.start()}async stop(){this.healthTimer&&clearInterval(this.healthTimer),this.healthTimer=null,await this.channelRuntime.stop(),await new Promise(e=>{if(!this.server)return e();this.server.close(()=>e())}),this.server=null,this.ipcUrl=null,this.removeIpcRuntimeFile()}writeIpcRuntimeFile(){if(this.ipcUrl)try{const e=q();k.mkdirSync(y.dirname(e),{recursive:!0}),k.writeFileSync(e,JSON.stringify({url:this.ipcUrl,token:this.ipcToken,pid:process.pid,updatedAt:new Date().toISOString()},null,2),{mode:384}),k.chmodSync(e,384)}catch{}}removeIpcRuntimeFile(){try{const e=q(),a=JSON.parse(k.readFileSync(e,"utf-8"));(a.url===this.ipcUrl||a.token===this.ipcToken)&&k.unlinkSync(e)}catch{}}getInjectedEnv(e,a,n,t){return this.ipcUrl?{SHENNIAN_MANAGER_SESSION_ID:e,SHENNIAN_MANAGER_AGENT_SESSION_ID:a??"",SHENNIAN_MANAGER_WORKDIR:S(n),SHENNIAN_MANAGER_MODEL:t,SHENNIAN_MANAGER_IPC_URL:this.ipcUrl,SHENNIAN_MANAGER_IPC_TOKEN:this.ipcToken}:{}}registerManager(e){this.registry.upsertManager({...e,workDir:S(e.workDir),machineId:process.env.SHENNIAN_MACHINE_ID??null})}setManagerWorkerDefaults(e,a,n){const t=this.registry.getManager(e);t&&this.registry.upsertManager({...t,defaultWorkerAgentType:a??null,defaultWorkerModelId:n??null})}getManagerWorkerDefaults(e){const a=this.registry.getManager(e);return{agentType:a?.defaultWorkerAgentType??null,modelId:a?.defaultWorkerModelId??null}}noteManagerAgentSession(e,a,n,t){this.registry.upsertManager({sessionId:e,agentSessionId:a,workDir:S(n),machineId:process.env.SHENNIAN_MACHINE_ID??null,modelId:t})}noteAgentEvent(e,a){if(!this.findWorker(e))return;const t={lastActivityAt:new Date().toISOString(),runId:a.runId};a.agentSessionId&&(t.agentSessionId=a.agentSessionId);const s=`${e}:${a.runId}`;if(a.state==="delta"&&a.text&&!a.thinking){const i=(this.workerTextAcc.get(s)??"")+a.text;this.workerTextAcc.set(s,i);const c=W(i);c&&(t.summary=c.length>160?`${c.slice(0,160)}...`:c)}if(a.state==="final"||a.state==="error"||a.state==="aborted"){t.status=a.state;const i=W(this.workerTextAcc.get(s)??"");i&&(t.summary=i.length>240?`${i.slice(0,240)}...`:i),this.workerTextAcc.delete(s)}else a.state==="start"&&(t.status="running");const o=this.registry.updateWorker(e,t);o&&(a.state==="final"||a.state==="error"||a.state==="aborted")&&this.wakeManagerForWorker(o.managedBy,o,a.state,a.message)}findWorker(e){return this.registry.load().workers[e]}async handleAppReq(e){const a=this.opts.getRuntime(),n=e.params??{};try{const t=String(n.managerSessionId||n.sessionId||"");if(!t)throw new Error("sessionId is required");const s=this.registry.getManager(t),o=e.method==="session.wechat-rpa.channel.get"||e.method==="manager.wechat-rpa.channel.get",i=e.method==="session.wechat-rpa.channel.upsert"||e.method==="manager.wechat-rpa.channel.upsert",c=e.method==="session.wechat-rpa.channel.sync"||e.method==="manager.wechat-rpa.channel.sync",d=e.method==="session.wechat-rpa.outbound.cancel"||e.method==="manager.wechat-rpa.outbound.cancel";if(e.method==="manager.channel.get"){w(a,e.id,!0,{channel:this.channelRuntime.getManagerChannel(t,"websocket",{includeSecret:!0})});return}if(e.method==="manager.channel.upsert"){const l=await this.channelRuntime.upsertManagerChannel({id:String(n.id||`websocket:${t}`),managerSessionId:t,sessionId:t,workDir:String(n.workDir||s?.workDir||""),type:"websocket",name:typeof n.name=="string"?n.name:void 0,agentType:typeof n.agentType=="string"?n.agentType:void 0,agentSessionId:typeof n.agentSessionId=="string"?n.agentSessionId:null,modelId:typeof n.modelId=="string"?n.modelId:null,enabled:!!n.enabled,wsUrl:typeof n.wsUrl=="string"?n.wsUrl:void 0,token:typeof n.token=="string"?n.token:void 0,canReply:n.canReply===void 0?void 0:!!n.canReply,systemPrompt:typeof n.systemPrompt=="string"?n.systemPrompt:void 0});this.broadcastManagerChannelStatus(t),w(a,e.id,!0,{channel:l});return}if(o){w(a,e.id,!0,{channel:this.channelRuntime.getManagerChannel(t,"wechat-rpa",{includeSecret:!0})});return}if(i){const l=await this.channelRuntime.upsertManagerWeChatRpaChannel({id:String(n.id||`wechat-rpa:${t}`),managerSessionId:t,sessionId:t,workDir:S(String(n.workDir||s?.workDir||"")),name:typeof n.name=="string"?n.name:void 0,agentType:typeof n.agentType=="string"?n.agentType:void 0,agentSessionId:typeof n.agentSessionId=="string"?n.agentSessionId:null,modelId:typeof n.modelId=="string"?n.modelId:null,enabled:!!n.enabled,groups:L(n.groups),canReply:n.canReply===void 0?void 0:!!n.canReply,systemPrompt:typeof n.systemPrompt=="string"?n.systemPrompt:void 0,source:F(n.source),pollIntervalMs:p(n.pollIntervalMs),recentLimit:p(n.recentLimit),idleSeconds:p(n.idleSeconds),forceForeground:n.forceForeground===void 0?void 0:!!n.forceForeground,noRestore:n.noRestore===void 0?void 0:!!n.noRestore,downloadAttachments:n.downloadAttachments===void 0?void 0:!!n.downloadAttachments,downloadAttachmentsDir:typeof n.downloadAttachmentsDir=="string"?n.downloadAttachmentsDir:void 0,selfNickname:typeof n.selfNickname=="string"?n.selfNickname:void 0,selfTriggerMarker:typeof n.selfTriggerMarker=="string"?n.selfTriggerMarker:void 0,deferInitialPoll:n.deferInitialPoll===void 0?void 0:!!n.deferInitialPoll,privacyConsentAccepted:n.privacyConsentAccepted===void 0?void 0:!!n.privacyConsentAccepted,flowScriptPath:typeof n.flowScriptPath=="string"?n.flowScriptPath:void 0});this.broadcastManagerChannelStatus(t),w(a,e.id,!0,{channel:l});return}if(c){const l=await this.channelRuntime.syncManagerWeChatRpaChannel(t);this.broadcastManagerChannelStatus(t),w(a,e.id,!0,l);return}if(d){const l=await this.channelRuntime.cancelManagerWeChatRpaOutbound({managerSessionId:t,idempotencyKey:typeof n.idempotencyKey=="string"?n.idempotencyKey:null,replyId:typeof n.replyId=="string"?n.replyId:null,reason:typeof n.reason=="string"?n.reason:"user_cancelled"});this.broadcastManagerChannelStatus(t),w(a,e.id,!0,l);return}throw new Error(`Unsupported manager app method: ${e.method}`)}catch(t){w(a,e.id,!1,{error:t instanceof Error?t.message:String(t)})}}broadcastConfiguredChannelStatuses(){for(const e of this.channelRuntime.listManagerChannelStatuses())this.broadcastManagerChannelStatus(e.managerSessionId)}broadcastManagerChannelStatus(e){const a=this.opts.getRuntime();if(!a.client?.sendEvent)return;const n=this.registry.getManager(e),t=this.channelRuntime.getManagerChannelStatus(e),s=this.channelRuntime.getManagerChannel(e,"wechat-rpa")??this.channelRuntime.getManagerChannel(e,"websocket");a.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:e,agentType:n?"manager":s?.agentType,agentSessionId:n?.agentSessionId??s?.agentSessionId??null,modelId:n?.modelId??s?.modelId??null,workDir:n?.workDir??s?.workDir,externalChannel:t}}})}async handleIpc(e,a){if(e.headers.authorization!==`Bearer ${this.ipcToken}`){u(a,401,{ok:!1,error:"Unauthorized"});return}try{const n=new URL(e.url??"/","http://127.0.0.1"),t=await pe(e),s=String(t.managerSessionId||e.headers["x-shennian-manager-session-id"]||"");if(!s)throw new Error("managerSessionId is required");const o=this.registry.getManager(s);if(n.pathname==="/sessions/list"){if(!o)throw new Error("Manager runtime is not registered");const i=new Set(this.opts.getRuntime().sessions.keys());u(a,200,{ok:!0,sessions:this.registry.listWorkers(s,{runningSessionIds:i})});return}if(n.pathname==="/sessions/start"){if(!o)throw new Error("Manager runtime is not registered");const i=String(t.agentType||t.agent||o.defaultWorkerAgentType||"codex");if(!ue(i))throw new Error(`Unsupported manager worker agent: ${i}`);const c=S(String(t.workDir||o.workDir));if(c!==o.workDir)throw new Error("Manager can only start workers in the same workDir");const d=String(t.message||"");if(!d)throw new Error("message is required");const l=this.registry.addWorker({managerSessionId:s,agentType:i,workDir:c,summary:d.slice(0,120)}),h=String(t.modelId||(i===o.defaultWorkerAgentType?o.defaultWorkerModelId??"":""));await this.dispatchChatSend(l.sessionId,i,c,d,null,h),u(a,200,{ok:!0,session:l});return}if(n.pathname==="/sessions/send"){const i=String(t.sessionId||""),c=String(t.message||""),d=t.enqueue===void 0?!0:!!t.enqueue,l=this.registry.getWorkerForManager(s,i);if(!l)throw new Error("Worker not found in this manager scope");const h=String(t.modelId||(l.agentType===o?.defaultWorkerAgentType?o.defaultWorkerModelId??"":""));d?await this.dispatchChatEnqueue(l.sessionId,l.agentType,l.workDir,c,l.agentSessionId??null,h):await this.dispatchChatSend(l.sessionId,l.agentType,l.workDir,c,l.agentSessionId??null,h),u(a,200,{ok:!0});return}if(n.pathname==="/sessions/queue"){const i=String(t.sessionId||"");if(!this.registry.getWorkerForManager(s,i))throw new Error("Worker not found in this manager scope");const d=this.opts.getRuntime().chatQueue?.getSnapshot(i);u(a,200,{ok:!0,queue:d});return}if(n.pathname==="/sessions/queue/edit"){const i=String(t.sessionId||"");if(!this.registry.getWorkerForManager(s,i))throw new Error("Worker not found in this manager scope");await this.opts.dispatchReq({type:"req",id:`manager-queue-edit-${m()}`,method:"chat.queue.edit",params:{sessionId:i,queueMessageId:String(t.queueMessageId||t.messageId||""),text:String(t.message||t.text||"")}}),u(a,200,{ok:!0,queue:this.opts.getRuntime().chatQueue?.getSnapshot(i)});return}if(n.pathname==="/sessions/queue/delete"){const i=String(t.sessionId||"");if(!this.registry.getWorkerForManager(s,i))throw new Error("Worker not found in this manager scope");await this.opts.dispatchReq({type:"req",id:`manager-queue-delete-${m()}`,method:"chat.queue.delete",params:{sessionId:i,queueMessageId:String(t.queueMessageId||t.messageId||"")}}),u(a,200,{ok:!0,queue:this.opts.getRuntime().chatQueue?.getSnapshot(i)});return}if(n.pathname==="/sessions/stop"||n.pathname==="/sessions/terminate"){const i=String(t.sessionId||"");if(!this.registry.getWorkerForManager(s,i))throw new Error("Worker not found in this manager scope");await this.opts.dispatchReq({type:"req",id:`manager-abort-${m()}`,method:"chat.abort",params:{sessionId:i}}),this.registry.updateWorker(i,{status:"aborted"}),u(a,200,{ok:!0});return}if(n.pathname==="/sessions/read"){const i=String(t.sessionId||""),c=Number(t.limit||200);if(!this.registry.getWorkerForManager(s,i))throw new Error("Worker not found in this manager scope");const l=te(i,{limit:Math.max(c*20,c)});u(a,200,{ok:!0,messages:me(l,c),rawMessageCount:l.length});return}if(n.pathname==="/memory/path"){if(!o)throw new Error("Manager runtime is not registered");u(a,200,{ok:!0,path:y.join(o.workDir,".shennian")});return}if(n.pathname==="/external/reply"){const i=typeof t.replyTarget=="string"?this.registry.getReplyTarget(t.replyTarget):this.registry.getLatestReplyTargetForManager(s),c=String(t.text||""),d=$(t.attachment),l=String(t.idempotencyKey||m()),h=String(t.channelId||""),g=String(t.conversationId||""),R=!i&&(!h||!g)?await this.channelRuntime.getDefaultReplyTarget(s).catch(()=>null):null,M=i?.channelId||h||R?.channelId||"",T=i?.conversationId||g||R?.conversationId||"";if(M&&this.channelRuntime.getChannelById(M)){if(!T)throw new Error("No external channel target is available for this Manager");const f=await this.channelRuntime.reply({managerSessionId:s,channelId:M,conversationId:T,messageId:i?.messageId??void 0,text:c,attachment:d,idempotencyKey:l});u(a,f.ok?200:400,f);return}const b=await this.tryDirectWeChatRpaReply(s,c,d);if(b){u(a,b.ok?200:400,b);return}let I;try{I=await this.sendManagedWeComReply({managerSessionId:s,text:c,attachment:d,idempotencyKey:l})}catch(f){const N=f instanceof Error?f.message:String(f);if(!B(N))throw f;I={ok:!1,error:N}}if(I.ok){u(a,200,{ok:!0,payload:I.payload});return}if(!B(I.error||"")||!M||!T){u(a,400,{ok:!1,error:I.error||"External send failed"});return}u(a,400,{ok:!1,error:`No local external channel is configured for ${M}`});return}if(n.pathname==="/channel/get"){u(a,200,{ok:!0,channel:this.channelRuntime.getManagerChannel(s,"websocket")});return}if(n.pathname==="/channel/upsert"){if(!o)throw new Error("Manager runtime is not registered");const i=await this.channelRuntime.upsertManagerChannel({id:String(t.id||`websocket:${s}`),managerSessionId:s,workDir:o.workDir,type:"websocket",name:typeof t.name=="string"?t.name:void 0,enabled:!!t.enabled,wsUrl:typeof t.wsUrl=="string"?t.wsUrl:void 0,token:typeof t.token=="string"?t.token:void 0,canReply:t.canReply===void 0?void 0:!!t.canReply,systemPrompt:typeof t.systemPrompt=="string"?t.systemPrompt:void 0});this.registry.upsertManager({...o,status:o.status}),this.broadcastManagerChannelStatus(s),u(a,200,{ok:!0,channel:i});return}if(n.pathname==="/wechat-rpa/tool/read"){const i=_(t),c=p(t.limit)??10,d=typeof t.traceId=="string"?t.traceId:void 0;let l;try{l=await this.weChatAutomationLane.run(`wechat-tool:read:${i.channelId}`,()=>ce(i,{conversation:i.conversationName,workDir:i.workDir,sessionId:i.sessionId,limit:c,recentLimit:c,download:t.download==="never"?"never":"auto",traceId:d,timeoutMs:p(t.timeoutMs)}))}catch(h){u(a,400,{ok:!1,...K(h,d)});return}u(a,200,{ok:!0,messages:l.messages,outDir:l.outDir,helperTracePath:l.helperTracePath,activityGuardPath:l.activityGuardPath});return}if(n.pathname==="/wechat-rpa/tool/send"){const i=_(t),c=String(t.text||""),d=$(t.attachment),l=typeof t.traceId=="string"?t.traceId:void 0;let h;try{h=await this.weChatAutomationLane.run(`wechat-tool:send:${i.channelId}`,()=>P(i,{conversation:i.conversationName,workDir:i.workDir,sessionId:i.sessionId,text:c,attachment:d,traceId:l,timeoutMs:p(t.timeoutMs)}))}catch(g){u(a,400,{ok:!1,...K(g,l)});return}u(a,200,{ok:!0,...h});return}if(n.pathname==="/wechat-rpa/channel/get"){u(a,200,{ok:!0,channel:this.channelRuntime.getManagerChannel(s,"wechat-rpa",{includeSecret:!0})});return}if(n.pathname==="/wechat-rpa/channel/upsert"){const i=S(String(t.workDir||o?.workDir||""));if(!i)throw new Error("workDir is required");const c=await this.channelRuntime.upsertManagerWeChatRpaChannel({id:String(t.id||`wechat-rpa:${s}`),managerSessionId:s,sessionId:s,workDir:i,name:typeof t.name=="string"?t.name:void 0,agentType:typeof t.agentType=="string"?t.agentType:void 0,agentSessionId:typeof t.agentSessionId=="string"?t.agentSessionId:null,modelId:typeof t.modelId=="string"?t.modelId:null,enabled:!!t.enabled,groups:L(t.groups),canReply:t.canReply===void 0?void 0:!!t.canReply,systemPrompt:typeof t.systemPrompt=="string"?t.systemPrompt:void 0,source:F(t.source),pollIntervalMs:p(t.pollIntervalMs),recentLimit:p(t.recentLimit),idleSeconds:p(t.idleSeconds),forceForeground:t.forceForeground===void 0?void 0:!!t.forceForeground,noRestore:t.noRestore===void 0?void 0:!!t.noRestore,downloadAttachments:t.downloadAttachments===void 0?void 0:!!t.downloadAttachments,downloadAttachmentsDir:typeof t.downloadAttachmentsDir=="string"?t.downloadAttachmentsDir:void 0,selfNickname:typeof t.selfNickname=="string"?t.selfNickname:void 0,selfTriggerMarker:typeof t.selfTriggerMarker=="string"?t.selfTriggerMarker:void 0,deferInitialPoll:t.deferInitialPoll===void 0?void 0:!!t.deferInitialPoll,privacyConsentAccepted:t.privacyConsentAccepted===void 0?void 0:!!t.privacyConsentAccepted,flowScriptPath:typeof t.flowScriptPath=="string"?t.flowScriptPath:void 0});o&&this.registry.upsertManager({...o,status:o.status}),this.broadcastManagerChannelStatus(s),u(a,200,{ok:!0,channel:c});return}if(n.pathname==="/wechat-rpa/channel/sync"){const{channel:i,messages:c}=await this.channelRuntime.syncManagerWeChatRpaChannel(s);this.broadcastManagerChannelStatus(s),u(a,200,{ok:!0,channel:i,messages:c});return}if(n.pathname==="/wechat-rpa/outbound/cancel"){const i=await this.channelRuntime.cancelManagerWeChatRpaOutbound({managerSessionId:s,idempotencyKey:typeof t.idempotencyKey=="string"?t.idempotencyKey:null,replyId:typeof t.replyId=="string"?t.replyId:null,reason:typeof t.reason=="string"?t.reason:"user_cancelled"});this.broadcastManagerChannelStatus(s),u(a,200,{ok:!0,...i});return}u(a,404,{ok:!1,error:`Unknown manager IPC path: ${n.pathname}`})}catch(n){u(a,400,{ok:!1,error:n instanceof Error?n.message:String(n)})}}async dispatchChatSend(e,a,n,t,s,o){await this.opts.dispatchReq({type:"req",id:`manager-send-${m()}`,method:"chat.send",params:{sessionId:e,text:t,agentType:a,workDir:n,agentSessionId:s,modelId:o}})}async dispatchChatEnqueue(e,a,n,t,s,o){await this.opts.dispatchReq({type:"req",id:`manager-enqueue-${m()}`,method:"chat.enqueue",params:{sessionId:e,text:t,agentType:a,workDir:n,agentSessionId:s,modelId:o}})}async tryDirectWeChatRpaReply(e,a,n){const t=this.opts.getRuntime().sessions.get(e),s=t?.externalChannel;if(!s||s.type!=="wechat-rpa"||!s.channelId||!s.name)return null;const o=process.env.SHENNIAN_MACHINE_ID||ae().machineId||"";if(s.machineId&&o&&s.machineId!==o)return{ok:!1,error:`WeChat \u7ED1\u5B9A\u5C5E\u4E8E\u5176\u4ED6\u673A\u5668 (${s.machineId})\uFF0C\u8BF7\u5728\u7ED1\u5B9A\u673A\u5668\u4E0A\u53D1\u9001\u3002`};if(!a.trim()&&!n)return{ok:!1,error:"text or attachment is required"};const i=t?.workDir||process.cwd(),c={sessionId:e,channelId:s.channelId,conversationId:ie(s.name),conversationName:s.name,workDir:i};try{return{ok:!0,payload:await this.weChatAutomationLane.run(`wechat-tool:send:${c.channelId}`,()=>P(c,{conversation:c.conversationName,workDir:c.workDir,sessionId:c.sessionId,text:a,attachment:n}))}}catch(d){return{ok:!1,error:d instanceof Error?d.message:String(d)}}}async sendManagedWeComReply(e){const a=re(e.text);if(!a.length&&!e.attachment)return{ok:!1,error:"text or attachment is required"};const n=this.opts.getRuntime().client;if(!n||typeof n.sendReq!="function")return{ok:!1,error:"Relay is not connected"};const t=[];for(const[s,o]of a.entries()){const i=await n.sendReq({type:"req",id:`external-send-${m()}`,method:"external.send",params:{managerSessionId:e.managerSessionId,text:o,idempotencyKey:a.length>1?`${e.idempotencyKey}:${s+1}`:e.idempotencyKey}});if(!i.ok)return{ok:!1,error:i.error||"External send failed"};t.push(i.payload)}if(e.attachment){const s=await n.sendReq({type:"req",id:`external-send-${m()}`,method:"external.send",params:{managerSessionId:e.managerSessionId,attachment:e.attachment,idempotencyKey:a.length?`${e.idempotencyKey}:attachment`:e.idempotencyKey}});if(!s.ok)return{ok:!1,error:s.error||"External send failed"};t.push(s.payload)}return{ok:!0,payload:t.length===1?t[0]:t}}wakeManagerForWorker(e,a,n,t){const s=this.registry.getManager(e);if(!s)return;const o=`\u4F60\u7BA1\u7406\u7684 worker \u5DF2\u7ED3\u675F\u3002
|
|
1
|
+
import J from"node:http";import{randomBytes as Y,randomUUID as p}from"node:crypto";import k from"node:fs";import P from"node:os";import y from"node:path";import{AVAILABLE_BUILTIN_AGENT_TYPES as V,extractPayloadText as Q,formatExternalConversationText as X,formatExternalMessageLine as Z,formatExternalAttachmentReference as ee,isAgentHiddenPayload as te,isToolPayload as ne}from"@shennian/wire";import{ManagerRegistry as re}from"./registry.js";import{readMessages as ae}from"../session/store.js";import{ChannelRuntime as se}from"../channels/runtime.js";import{WeChatRpaSessionBindingSync as ie}from"../channels/wechat-rpa-session-sync.js";import{splitExternalReplyText as oe}from"../channels/reply-split.js";import{loadConfig as W,resolveShennianPath as ce}from"../config/index.js";import{weChatChannelConversationId as le}from"../channels/wechat-rpa/product-channel.js";import{buildExternalChannelInstructions as de}from"../agents/external-channel-instructions.js";import{readDirectWeChatLatestOnce as ue,sendDirectWeChatMessageOnce as v}from"../commands/wechat.js";import{createWeChatAutomationLane as he}from"../channels/wechat-channel/automation-lane.js";const C=Number(process.env.SHENNIAN_MANAGER_IPC_BODY_MAX_BYTES||2*1024*1024),A=12*6e4;let $=null;function ze(r){$=r}function Je(){return $}function R(r){return y.resolve(r||P.homedir())}function u(r,e,a){r.writeHead(e,{"content-type":"application/json; charset=utf-8"}),r.end(JSON.stringify(a))}function ge(r){return/^agent-(.+)-\d+$/.exec(r)?.[1]??null}function pe(r){return r==="manager"?!1:r.startsWith("custom:")?!0:V.includes(r)}function me(r){const e=/^agent-.+-(\d+)$/.exec(r);if(!e)return null;const a=Number(e[1]);return Number.isInteger(a)&&a>=0?a:null}function _(r){return r.replace(/\r\n/g,`
|
|
2
|
+
`).trim()}function fe(r){try{const e=JSON.parse(r),a=e.type==="tool_result"||e.result?"tool_result":"tool_call",t=e.name||"tool",n=typeof e.result=="string"?e.result.replace(/\s+/g," ").trim():"",s=n.length>220?`${n.slice(0,220)}...`:n;return s?`[${a}] ${t}: ${s}`:`[${a}] ${t}`}catch{return"[tool]"}}function B(r){if(!r||typeof r!="object")return;const e=r,a=String(e.kind||""),t=String(e.name||""),n=String(e.mimeType||""),s=String(e.dataBase64||""),o=String(e.localPath||""),i=String(e.url||""),c=Number(e.size||0);if(s)throw new Error("Manager IPC external attachments must use localPath or url; dataBase64 is not accepted");if(!(a!=="image"&&a!=="video"&&a!=="file")&&!(!t||!n||!Number.isFinite(c)||c<0)&&!(!o&&!i))return{kind:a,name:t,mimeType:n,size:c,...o?{localPath:o}:{},...i?{url:i}:{}}}function q(r){const e=r.binding&&typeof r.binding=="object"&&!Array.isArray(r.binding)?r.binding:{},a=String(e.sessionId||r.managerSessionId||"").trim(),t=String(e.channelId||"").trim(),n=String(e.conversationId||"").trim(),s=String(e.conversationName||r.conversation||"").trim(),o=String(e.workDir||r.workDir||"").trim();if(!a)throw new Error("WeChat tool sessionId is required");if(!t)throw new Error("WeChat tool channelId is required");if(!n)throw new Error("WeChat tool conversationId is required");if(!s)throw new Error("WeChat tool conversationName is required");if(!o)throw new Error("WeChat tool workDir is required");return{sessionId:a,channelId:t,conversationId:n,conversationName:s,workDir:o}}function ye(r,e){const a=[...r].sort((d,l)=>d.ts-l.ts),t=[];let n=null,s=null,o=null,i="";const c=()=>{if(!n)return;const d=i.trim();d&&t.push({...n,id:`${n.id}-compact`,payload:d}),n=null,s=null,o=null,i=""};for(const d of a){if(te(d.payload)){c();continue}if(d.role==="user"){c(),t.push(d);continue}if(ne(d.payload)){c(),t.push({...d,payload:fe(d.payload)});continue}const l=Q(d.payload);if(!l.trim())continue;const h=ge(d.id),g=me(d.id);n&&n.role===d.role&&s===h&&h&&g!==null&&o!==null&&g===o+1?(i+=l,n.ts=d.ts,o=g):(c(),n=d,s=h,o=g,i=l)}return c(),t.slice(-e).sort((d,l)=>l.ts-d.ts)}async function we(r){const e=[];let a=0;for await(const n of r){const s=Buffer.from(n);if(a+=s.byteLength,Number.isFinite(C)&&C>0&&a>C)throw new Error(`Manager IPC request body is too large. Max: ${C} bytes.`);e.push(s)}const t=Buffer.concat(e).toString("utf-8");return t?JSON.parse(t):{}}function w(r,e,a,t){r.client.sendRes({type:"res",id:e,ok:a,...a?{payload:t}:{error:String(t.error||"unknown error")}})}function L(r){return/binding not found|unknown method|not supported|relay is not connected|no external channel/i.test(r)}function F(){return Ie()?y.join(P.tmpdir(),"shennian-vitest-runtime",String(process.pid),"manager-ipc.json"):ce("runtime","manager-ipc.json")}function Ie(){return(process.env.VITEST==="true"||!!process.env.VITEST_WORKER_ID)&&!process.env.SHENNIAN_HOME?.trim()}class Ye{opts;registry=new re;channelRuntime;weChatRpaSessionSync;server=null;ipcUrl=null;ipcToken=Y(24).toString("hex");healthTimer=null;startPromise=null;workerTextAcc=new Map;weChatAutomationLane;constructor(e){this.opts=e,this.weChatAutomationLane=e.weChatAutomationLane??he(),this.channelRuntime=e.channelRuntime??new se((a,t)=>{this.handleExternalMessage(a,t)},a=>this.registry.createReplyTarget(a).replyTarget,{createWeChatRpaProductRunner:e.createWeChatRpaProductRunner,weChatAutomationLane:this.weChatAutomationLane}),this.weChatRpaSessionSync=new ie({channelRuntime:this.channelRuntime,getLocalMachineId:()=>process.env.SHENNIAN_MACHINE_ID||W().machineId||"",listSessions:()=>this.listWeChatRpaSessionContexts()})}*listWeChatRpaSessionContexts(){const e=this.opts.getRuntime().sessions;for(const[a,t]of e)yield{sessionId:a,workDir:t.workDir,agentType:t.agentType,agentSessionId:t.agentSessionId,externalChannel:t.externalChannel??null}}async notifyWeChatRpaSessionBinding(e){await this.weChatRpaSessionSync.reconcileSession(e).catch(a=>{console.error(`[wechat-rpa-sync] reconcileSession failed sessionId=${e.sessionId}: ${a instanceof Error?a.message:String(a)}`)})}async reconcileWeChatRpaSessionBindings(){await this.weChatRpaSessionSync.reconcileAll().catch(e=>{console.error(`[wechat-rpa-sync] reconcileAll failed: ${e instanceof Error?e.message:String(e)}`)})}async start(){return this.startPromise?this.startPromise:(this.startPromise=this.doStart(),this.startPromise)}async doStart(){if(this.server)return;this.server=J.createServer((a,t)=>{this.handleIpc(a,t)}),this.server.requestTimeout=A,this.server.timeout=A,this.server.keepAliveTimeout=A,this.server.headersTimeout=A+5e3,await new Promise((a,t)=>{this.server.once("error",t),this.server.listen(0,"127.0.0.1",()=>a())});const e=this.server.address();typeof e=="object"&&e&&(this.ipcUrl=`http://127.0.0.1:${e.port}`,this.writeIpcRuntimeFile()),this.server.unref(),await this.channelRuntime.start(),this.reconcileWeChatRpaSessionBindings(),this.broadcastConfiguredChannelStatuses(),this.healthTimer=setInterval(()=>this.scanWorkerHealth(),6e4),this.healthTimer.unref()}async ready(){await this.start()}async stop(){this.healthTimer&&clearInterval(this.healthTimer),this.healthTimer=null,await this.channelRuntime.stop(),await new Promise(e=>{if(!this.server)return e();this.server.close(()=>e())}),this.server=null,this.ipcUrl=null,this.removeIpcRuntimeFile()}writeIpcRuntimeFile(){if(this.ipcUrl)try{const e=F();k.mkdirSync(y.dirname(e),{recursive:!0}),k.writeFileSync(e,JSON.stringify({url:this.ipcUrl,token:this.ipcToken,pid:process.pid,updatedAt:new Date().toISOString()},null,2),{mode:384}),k.chmodSync(e,384)}catch{}}removeIpcRuntimeFile(){try{const e=F(),a=JSON.parse(k.readFileSync(e,"utf-8"));(a.url===this.ipcUrl||a.token===this.ipcToken)&&k.unlinkSync(e)}catch{}}getInjectedEnv(e,a,t,n){return this.ipcUrl?{SHENNIAN_MANAGER_SESSION_ID:e,SHENNIAN_MANAGER_AGENT_SESSION_ID:a??"",SHENNIAN_MANAGER_WORKDIR:R(t),SHENNIAN_MANAGER_MODEL:n,SHENNIAN_MANAGER_IPC_URL:this.ipcUrl,SHENNIAN_MANAGER_IPC_TOKEN:this.ipcToken}:{}}registerManager(e){this.registry.upsertManager({...e,workDir:R(e.workDir),machineId:process.env.SHENNIAN_MACHINE_ID??null})}setManagerWorkerDefaults(e,a,t){const n=this.registry.getManager(e);n&&this.registry.upsertManager({...n,defaultWorkerAgentType:a??null,defaultWorkerModelId:t??null})}getManagerWorkerDefaults(e){const a=this.registry.getManager(e);return{agentType:a?.defaultWorkerAgentType??null,modelId:a?.defaultWorkerModelId??null}}noteManagerAgentSession(e,a,t,n){this.registry.upsertManager({sessionId:e,agentSessionId:a,workDir:R(t),machineId:process.env.SHENNIAN_MACHINE_ID??null,modelId:n})}noteAgentEvent(e,a){if(!this.findWorker(e))return;const n={lastActivityAt:new Date().toISOString(),runId:a.runId};a.agentSessionId&&(n.agentSessionId=a.agentSessionId);const s=`${e}:${a.runId}`;if(a.state==="delta"&&a.text&&!a.thinking){const i=(this.workerTextAcc.get(s)??"")+a.text;this.workerTextAcc.set(s,i);const c=_(i);c&&(n.summary=c.length>160?`${c.slice(0,160)}...`:c)}if(a.state==="final"||a.state==="error"||a.state==="aborted"){n.status=a.state;const i=_(this.workerTextAcc.get(s)??"");i&&(n.summary=i.length>240?`${i.slice(0,240)}...`:i),this.workerTextAcc.delete(s)}else a.state==="start"&&(n.status="running");const o=this.registry.updateWorker(e,n);o&&(a.state==="final"||a.state==="error"||a.state==="aborted")&&this.wakeManagerForWorker(o.managedBy,o,a.state,a.message)}findWorker(e){return this.registry.load().workers[e]}async handleAppReq(e){const a=this.opts.getRuntime(),t=e.params??{};try{const n=String(t.managerSessionId||t.sessionId||"");if(!n)throw new Error("sessionId is required");const s=this.registry.getManager(n),o=e.method==="session.wechat-rpa.channel.get"||e.method==="manager.wechat-rpa.channel.get",i=e.method==="session.wechat-rpa.channel.upsert"||e.method==="manager.wechat-rpa.channel.upsert",c=e.method==="session.wechat-rpa.channel.sync"||e.method==="manager.wechat-rpa.channel.sync",d=e.method==="session.wechat-rpa.outbound.cancel"||e.method==="manager.wechat-rpa.outbound.cancel";if(e.method==="manager.channel.get"){w(a,e.id,!0,{channel:this.channelRuntime.getManagerChannel(n,"websocket",{includeSecret:!0})});return}if(e.method==="manager.channel.upsert"){const l=await this.channelRuntime.upsertManagerChannel({id:String(t.id||`websocket:${n}`),managerSessionId:n,sessionId:n,workDir:String(t.workDir||s?.workDir||""),type:"websocket",name:typeof t.name=="string"?t.name:void 0,agentType:typeof t.agentType=="string"?t.agentType:void 0,agentSessionId:typeof t.agentSessionId=="string"?t.agentSessionId:null,modelId:typeof t.modelId=="string"?t.modelId:null,enabled:!!t.enabled,wsUrl:typeof t.wsUrl=="string"?t.wsUrl:void 0,token:typeof t.token=="string"?t.token:void 0,canReply:t.canReply===void 0?void 0:!!t.canReply,systemPrompt:typeof t.systemPrompt=="string"?t.systemPrompt:void 0});this.broadcastManagerChannelStatus(n),w(a,e.id,!0,{channel:l});return}if(o){w(a,e.id,!0,{channel:this.channelRuntime.getManagerChannel(n,"wechat-rpa",{includeSecret:!0})});return}if(i){const l=await this.channelRuntime.upsertManagerWeChatRpaChannel({id:String(t.id||`wechat-rpa:${n}`),managerSessionId:n,sessionId:n,workDir:R(String(t.workDir||s?.workDir||"")),name:typeof t.name=="string"?t.name:void 0,agentType:typeof t.agentType=="string"?t.agentType:void 0,agentSessionId:typeof t.agentSessionId=="string"?t.agentSessionId:null,modelId:typeof t.modelId=="string"?t.modelId:null,enabled:!!t.enabled,groups:O(t.groups),canReply:t.canReply===void 0?void 0:!!t.canReply,systemPrompt:typeof t.systemPrompt=="string"?t.systemPrompt:void 0,source:U(t.source),pollIntervalMs:m(t.pollIntervalMs),recentLimit:m(t.recentLimit),idleSeconds:m(t.idleSeconds),forceForeground:t.forceForeground===void 0?void 0:!!t.forceForeground,noRestore:t.noRestore===void 0?void 0:!!t.noRestore,downloadAttachments:t.downloadAttachments===void 0?void 0:!!t.downloadAttachments,downloadAttachmentsDir:typeof t.downloadAttachmentsDir=="string"?t.downloadAttachmentsDir:void 0,selfNickname:typeof t.selfNickname=="string"?t.selfNickname:void 0,selfTriggerMarker:typeof t.selfTriggerMarker=="string"?t.selfTriggerMarker:void 0,deferInitialPoll:t.deferInitialPoll===void 0?void 0:!!t.deferInitialPoll,privacyConsentAccepted:t.privacyConsentAccepted===void 0?void 0:!!t.privacyConsentAccepted,flowScriptPath:typeof t.flowScriptPath=="string"?t.flowScriptPath:void 0});this.broadcastManagerChannelStatus(n),w(a,e.id,!0,{channel:l});return}if(c){const l=await this.channelRuntime.syncManagerWeChatRpaChannel(n);this.broadcastManagerChannelStatus(n),w(a,e.id,!0,l);return}if(d){const l=await this.channelRuntime.cancelManagerWeChatRpaOutbound({managerSessionId:n,idempotencyKey:typeof t.idempotencyKey=="string"?t.idempotencyKey:null,replyId:typeof t.replyId=="string"?t.replyId:null,reason:typeof t.reason=="string"?t.reason:"user_cancelled"});this.broadcastManagerChannelStatus(n),w(a,e.id,!0,l);return}throw new Error(`Unsupported manager app method: ${e.method}`)}catch(n){w(a,e.id,!1,{error:n instanceof Error?n.message:String(n)})}}broadcastConfiguredChannelStatuses(){for(const e of this.channelRuntime.listManagerChannelStatuses())this.broadcastManagerChannelStatus(e.managerSessionId)}broadcastManagerChannelStatus(e){const a=this.opts.getRuntime();if(!a.client?.sendEvent)return;const t=this.registry.getManager(e),n=this.channelRuntime.getManagerChannelStatus(e),s=this.channelRuntime.getManagerChannel(e,"wechat-rpa")??this.channelRuntime.getManagerChannel(e,"websocket");a.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:e,agentType:t?"manager":s?.agentType,agentSessionId:t?.agentSessionId??s?.agentSessionId??null,modelId:t?.modelId??s?.modelId??null,workDir:t?.workDir??s?.workDir,externalChannel:n}}})}async handleIpc(e,a){if(e.headers.authorization!==`Bearer ${this.ipcToken}`){u(a,401,{ok:!1,error:"Unauthorized"});return}try{const t=new URL(e.url??"/","http://127.0.0.1"),n=await we(e),s=String(n.managerSessionId||e.headers["x-shennian-manager-session-id"]||"");if(!s)throw new Error("managerSessionId is required");const o=this.registry.getManager(s);if(t.pathname==="/sessions/list"){if(!o)throw new Error("Manager runtime is not registered");const i=new Set(this.opts.getRuntime().sessions.keys());u(a,200,{ok:!0,sessions:this.registry.listWorkers(s,{runningSessionIds:i})});return}if(t.pathname==="/sessions/start"){if(!o)throw new Error("Manager runtime is not registered");const i=String(n.agentType||n.agent||o.defaultWorkerAgentType||"codex");if(!pe(i))throw new Error(`Unsupported manager worker agent: ${i}`);const c=R(String(n.workDir||o.workDir));if(c!==o.workDir)throw new Error("Manager can only start workers in the same workDir");const d=String(n.message||"");if(!d)throw new Error("message is required");const l=this.registry.addWorker({managerSessionId:s,agentType:i,workDir:c,summary:d.slice(0,120)}),h=String(n.modelId||(i===o.defaultWorkerAgentType?o.defaultWorkerModelId??"":""));await this.dispatchChatSend(l.sessionId,i,c,d,null,h),u(a,200,{ok:!0,session:l});return}if(t.pathname==="/sessions/send"){const i=String(n.sessionId||""),c=String(n.message||""),d=n.enqueue===void 0?!0:!!n.enqueue,l=this.registry.getWorkerForManager(s,i);if(!l)throw new Error("Worker not found in this manager scope");const h=String(n.modelId||(l.agentType===o?.defaultWorkerAgentType?o.defaultWorkerModelId??"":""));d?await this.dispatchChatEnqueue(l.sessionId,l.agentType,l.workDir,c,l.agentSessionId??null,h):await this.dispatchChatSend(l.sessionId,l.agentType,l.workDir,c,l.agentSessionId??null,h),u(a,200,{ok:!0});return}if(t.pathname==="/sessions/queue"){const i=String(n.sessionId||"");if(!this.registry.getWorkerForManager(s,i))throw new Error("Worker not found in this manager scope");const d=this.opts.getRuntime().chatQueue?.getSnapshot(i);u(a,200,{ok:!0,queue:d});return}if(t.pathname==="/sessions/queue/edit"){const i=String(n.sessionId||"");if(!this.registry.getWorkerForManager(s,i))throw new Error("Worker not found in this manager scope");await this.opts.dispatchReq({type:"req",id:`manager-queue-edit-${p()}`,method:"chat.queue.edit",params:{sessionId:i,queueMessageId:String(n.queueMessageId||n.messageId||""),text:String(n.message||n.text||"")}}),u(a,200,{ok:!0,queue:this.opts.getRuntime().chatQueue?.getSnapshot(i)});return}if(t.pathname==="/sessions/queue/delete"){const i=String(n.sessionId||"");if(!this.registry.getWorkerForManager(s,i))throw new Error("Worker not found in this manager scope");await this.opts.dispatchReq({type:"req",id:`manager-queue-delete-${p()}`,method:"chat.queue.delete",params:{sessionId:i,queueMessageId:String(n.queueMessageId||n.messageId||"")}}),u(a,200,{ok:!0,queue:this.opts.getRuntime().chatQueue?.getSnapshot(i)});return}if(t.pathname==="/sessions/stop"||t.pathname==="/sessions/terminate"){const i=String(n.sessionId||"");if(!this.registry.getWorkerForManager(s,i))throw new Error("Worker not found in this manager scope");await this.opts.dispatchReq({type:"req",id:`manager-abort-${p()}`,method:"chat.abort",params:{sessionId:i}}),this.registry.updateWorker(i,{status:"aborted"}),u(a,200,{ok:!0});return}if(t.pathname==="/sessions/read"){const i=String(n.sessionId||""),c=Number(n.limit||200);if(!this.registry.getWorkerForManager(s,i))throw new Error("Worker not found in this manager scope");const l=ae(i,{limit:Math.max(c*20,c)});u(a,200,{ok:!0,messages:ye(l,c),rawMessageCount:l.length});return}if(t.pathname==="/memory/path"){if(!o)throw new Error("Manager runtime is not registered");u(a,200,{ok:!0,path:y.join(o.workDir,".shennian")});return}if(t.pathname==="/external/reply"){const i=typeof n.replyTarget=="string"?this.registry.getReplyTarget(n.replyTarget):this.registry.getLatestReplyTargetForManager(s),c=String(n.text||""),d=B(n.attachment),l=String(n.idempotencyKey||p()),h=String(n.channelId||""),g=String(n.conversationId||""),M=!i&&(!h||!g)?await this.channelRuntime.getDefaultReplyTarget(s).catch(()=>null):null,I=i?.channelId||h||M?.channelId||"",T=i?.conversationId||g||M?.conversationId||"",N=I?this.channelRuntime.getChannelById(I):void 0,z=N&&(!!(i||h)||N.managedBy!=="session-sync");if(I&&z){if(!T)throw new Error("No external channel target is available for this Manager");const f=await this.channelRuntime.reply({managerSessionId:s,channelId:I,conversationId:T,messageId:i?.messageId??void 0,text:c,attachment:d,idempotencyKey:l});u(a,f.ok?200:400,f);return}const b=await this.tryDirectWeChatRpaReply(s,c,d);if(b){u(a,b.ok?200:400,b);return}let S;try{S=await this.sendManagedWeComReply({managerSessionId:s,text:c,attachment:d,idempotencyKey:l})}catch(f){const D=f instanceof Error?f.message:String(f);if(!L(D))throw f;S={ok:!1,error:D}}if(S.ok){u(a,200,{ok:!0,payload:S.payload});return}if(!L(S.error||"")||!I||!T){u(a,400,{ok:!1,error:S.error||"External send failed"});return}u(a,400,{ok:!1,error:`No local external channel is configured for ${I}`});return}if(t.pathname==="/channel/get"){u(a,200,{ok:!0,channel:this.channelRuntime.getManagerChannel(s,"websocket")});return}if(t.pathname==="/channel/upsert"){if(!o)throw new Error("Manager runtime is not registered");const i=await this.channelRuntime.upsertManagerChannel({id:String(n.id||`websocket:${s}`),managerSessionId:s,workDir:o.workDir,type:"websocket",name:typeof n.name=="string"?n.name:void 0,enabled:!!n.enabled,wsUrl:typeof n.wsUrl=="string"?n.wsUrl:void 0,token:typeof n.token=="string"?n.token:void 0,canReply:n.canReply===void 0?void 0:!!n.canReply,systemPrompt:typeof n.systemPrompt=="string"?n.systemPrompt:void 0});this.registry.upsertManager({...o,status:o.status}),this.broadcastManagerChannelStatus(s),u(a,200,{ok:!0,channel:i});return}if(t.pathname==="/wechat-rpa/tool/read"){const i=q(n),c=m(n.limit)??10,d=typeof n.traceId=="string"?n.traceId:void 0;let l;try{l=await this.weChatAutomationLane.run(`wechat-tool:read:${i.channelId}`,()=>ue(i,{conversation:i.conversationName,workDir:i.workDir,sessionId:i.sessionId,limit:c,recentLimit:c,download:n.download==="never"?"never":"auto",traceId:d,timeoutMs:m(n.timeoutMs)}))}catch(h){u(a,400,{ok:!1,...j(h,d)});return}u(a,200,{ok:!0,messages:l.messages,outDir:l.outDir,helperTracePath:l.helperTracePath,activityGuardPath:l.activityGuardPath});return}if(t.pathname==="/wechat-rpa/tool/send"){const i=q(n),c=String(n.text||""),d=B(n.attachment),l=typeof n.traceId=="string"?n.traceId:void 0;let h;try{h=await this.weChatAutomationLane.run(`wechat-tool:send:${i.channelId}`,()=>v(i,{conversation:i.conversationName,workDir:i.workDir,sessionId:i.sessionId,text:c,attachment:d,traceId:l,timeoutMs:m(n.timeoutMs)}))}catch(g){u(a,400,{ok:!1,...j(g,l)});return}u(a,200,{ok:!0,...h});return}if(t.pathname==="/wechat-rpa/channel/get"){u(a,200,{ok:!0,channel:this.channelRuntime.getManagerChannel(s,"wechat-rpa",{includeSecret:!0})});return}if(t.pathname==="/wechat-rpa/channel/upsert"){const i=R(String(n.workDir||o?.workDir||""));if(!i)throw new Error("workDir is required");const c=await this.channelRuntime.upsertManagerWeChatRpaChannel({id:String(n.id||`wechat-rpa:${s}`),managerSessionId:s,sessionId:s,workDir:i,name:typeof n.name=="string"?n.name:void 0,agentType:typeof n.agentType=="string"?n.agentType:void 0,agentSessionId:typeof n.agentSessionId=="string"?n.agentSessionId:null,modelId:typeof n.modelId=="string"?n.modelId:null,enabled:!!n.enabled,groups:O(n.groups),canReply:n.canReply===void 0?void 0:!!n.canReply,systemPrompt:typeof n.systemPrompt=="string"?n.systemPrompt:void 0,source:U(n.source),pollIntervalMs:m(n.pollIntervalMs),recentLimit:m(n.recentLimit),idleSeconds:m(n.idleSeconds),forceForeground:n.forceForeground===void 0?void 0:!!n.forceForeground,noRestore:n.noRestore===void 0?void 0:!!n.noRestore,downloadAttachments:n.downloadAttachments===void 0?void 0:!!n.downloadAttachments,downloadAttachmentsDir:typeof n.downloadAttachmentsDir=="string"?n.downloadAttachmentsDir:void 0,selfNickname:typeof n.selfNickname=="string"?n.selfNickname:void 0,selfTriggerMarker:typeof n.selfTriggerMarker=="string"?n.selfTriggerMarker:void 0,deferInitialPoll:n.deferInitialPoll===void 0?void 0:!!n.deferInitialPoll,privacyConsentAccepted:n.privacyConsentAccepted===void 0?void 0:!!n.privacyConsentAccepted,flowScriptPath:typeof n.flowScriptPath=="string"?n.flowScriptPath:void 0});o&&this.registry.upsertManager({...o,status:o.status}),this.broadcastManagerChannelStatus(s),u(a,200,{ok:!0,channel:c});return}if(t.pathname==="/wechat-rpa/channel/sync"){const{channel:i,messages:c}=await this.channelRuntime.syncManagerWeChatRpaChannel(s);this.broadcastManagerChannelStatus(s),u(a,200,{ok:!0,channel:i,messages:c});return}if(t.pathname==="/wechat-rpa/outbound/cancel"){const i=await this.channelRuntime.cancelManagerWeChatRpaOutbound({managerSessionId:s,idempotencyKey:typeof n.idempotencyKey=="string"?n.idempotencyKey:null,replyId:typeof n.replyId=="string"?n.replyId:null,reason:typeof n.reason=="string"?n.reason:"user_cancelled"});this.broadcastManagerChannelStatus(s),u(a,200,{ok:!0,...i});return}u(a,404,{ok:!1,error:`Unknown manager IPC path: ${t.pathname}`})}catch(t){u(a,400,{ok:!1,error:t instanceof Error?t.message:String(t)})}}async dispatchChatSend(e,a,t,n,s,o){await this.opts.dispatchReq({type:"req",id:`manager-send-${p()}`,method:"chat.send",params:{sessionId:e,text:n,agentType:a,workDir:t,agentSessionId:s,modelId:o}})}async dispatchChatEnqueue(e,a,t,n,s,o){await this.opts.dispatchReq({type:"req",id:`manager-enqueue-${p()}`,method:"chat.enqueue",params:{sessionId:e,text:n,agentType:a,workDir:t,agentSessionId:s,modelId:o}})}async tryDirectWeChatRpaReply(e,a,t){const n=this.opts.getRuntime().sessions.get(e),s=n?.externalChannel;if(!s||s.type!=="wechat-rpa"||!s.channelId||!s.name)return null;const o=process.env.SHENNIAN_MACHINE_ID||W().machineId||"";if(s.machineId&&o&&s.machineId!==o)return{ok:!1,error:`WeChat \u7ED1\u5B9A\u5C5E\u4E8E\u5176\u4ED6\u673A\u5668 (${s.machineId})\uFF0C\u8BF7\u5728\u7ED1\u5B9A\u673A\u5668\u4E0A\u53D1\u9001\u3002`};if(!a.trim()&&!t)return{ok:!1,error:"text or attachment is required"};const i=n?.workDir||process.cwd(),c={sessionId:e,channelId:s.channelId,conversationId:le(s.name),conversationName:s.name,workDir:i};try{return{ok:!0,payload:await this.weChatAutomationLane.run(`wechat-tool:send:${c.channelId}`,()=>v(c,{conversation:c.conversationName,workDir:c.workDir,sessionId:c.sessionId,text:a,attachment:t}))}}catch(d){return{ok:!1,error:d instanceof Error?d.message:String(d)}}}async sendManagedWeComReply(e){const a=oe(e.text);if(!a.length&&!e.attachment)return{ok:!1,error:"text or attachment is required"};const t=this.opts.getRuntime().client;if(!t||typeof t.sendReq!="function")return{ok:!1,error:"Relay is not connected"};const n=[];for(const[s,o]of a.entries()){const i=await t.sendReq({type:"req",id:`external-send-${p()}`,method:"external.send",params:{managerSessionId:e.managerSessionId,text:o,idempotencyKey:a.length>1?`${e.idempotencyKey}:${s+1}`:e.idempotencyKey}});if(!i.ok)return{ok:!1,error:i.error||"External send failed"};n.push(i.payload)}if(e.attachment){const s=await t.sendReq({type:"req",id:`external-send-${p()}`,method:"external.send",params:{managerSessionId:e.managerSessionId,attachment:e.attachment,idempotencyKey:a.length?`${e.idempotencyKey}:attachment`:e.idempotencyKey}});if(!s.ok)return{ok:!1,error:s.error||"External send failed"};n.push(s.payload)}return{ok:!0,payload:n.length===1?n[0]:n}}wakeManagerForWorker(e,a,t,n){const s=this.registry.getManager(e);if(!s)return;const o=`\u4F60\u7BA1\u7406\u7684 worker \u5DF2\u7ED3\u675F\u3002
|
|
3
3
|
|
|
4
4
|
Worker: ${a.sessionId}
|
|
5
|
-
\u72B6\u6001\uFF1A${
|
|
5
|
+
\u72B6\u6001\uFF1A${t}
|
|
6
6
|
\u6700\u7EC8\u7ED3\u679C\uFF1A
|
|
7
|
-
${
|
|
7
|
+
${n||a.summary||"(\u65E0\u53EF\u89C1\u6458\u8981)"}
|
|
8
8
|
|
|
9
|
-
\u8BF7\u51B3\u5B9A\u4E0B\u4E00\u6B65\uFF1A\u7EE7\u7EED\u7B49\u5F85\u3001\u521B\u5EFA/\u6307\u6D3E worker\u3001\u505C\u6B62 worker\u3001\u8BE2\u95EE\u7528\u6237\uFF0C\u6216\u5411\u7528\u6237\u6C47\u62A5\u3002`;this.interruptAndResumeManager(s,o,
|
|
9
|
+
\u8BF7\u51B3\u5B9A\u4E0B\u4E00\u6B65\uFF1A\u7EE7\u7EED\u7B49\u5F85\u3001\u521B\u5EFA/\u6307\u6D3E worker\u3001\u505C\u6B62 worker\u3001\u8BE2\u95EE\u7528\u6237\uFF0C\u6216\u5411\u7528\u6237\u6C47\u62A5\u3002`;this.interruptAndResumeManager(s,o,t==="final"?"worker.final":`worker.${t}`)}handleExternalMessage(e,a){const t=this.channelRuntime.getChannelById(a.channelId)??this.channelRuntime.getManagerChannel(e,a.channelType),n=this.channelRuntime.getChannelStatusById(a.channelId)??this.channelRuntime.getManagerChannelStatus(e),s=this.registry.getManager(e),o=t?.agentType||(s?"manager":"codex"),i=t?.workDir||s?.workDir||process.cwd(),c=t?.agentSessionId??s?.agentSessionId??null,d=t?.modelId||s?.modelId||"",l=Se(a.attachments,a.channelType),h=Re(a);this.dispatchExternalMessage({sessionId:e,agentType:o,workDir:i,agentSessionId:c,modelId:d,text:h,attachments:l,externalChannel:Ae(n),replyTarget:a.replyTarget})}async dispatchExternalMessage(e){await this.opts.dispatchReq({type:"req",id:`external-enqueue-${p()}`,method:"chat.enqueue",params:{sessionId:e.sessionId,text:e.text,agentType:e.agentType,workDir:e.workDir,agentSessionId:e.agentSessionId,modelId:e.modelId,origin:"external",attachments:e.attachments,externalChannel:e.externalChannel??null,replyTarget:e.replyTarget}})}scanWorkerHealth(){const e=Date.now(),a=this.registry.load();for(const t of Object.values(a.workers)){if(t.status!=="running")continue;const n=e-Date.parse(t.createdAt);if(n<10*6e4)continue;const s=t.healthNotifiedAt?Date.parse(t.healthNotifiedAt):0;if(s&&e-s<10*6e4)continue;const o=a.managers[t.managedBy];if(!o)continue;const i=Math.max(0,Math.floor((e-Date.parse(t.lastActivityAt))/6e4)),c=`Worker ${t.sessionId} \u5DF2\u8FD0\u884C ${Math.floor(n/6e4)} \u5206\u949F\uFF0C\u5C1A\u672A\u7ED3\u675F\u3002
|
|
10
10
|
|
|
11
11
|
\u5F53\u524D\u53EF\u89C1\u8FDB\u5C55\uFF1A
|
|
12
|
-
- \u6700\u8FD1\u6587\u672C\u6458\u8981\uFF1A${
|
|
13
|
-
- \u6700\u8FD1\u6D3B\u52A8\u65F6\u95F4\uFF1A${
|
|
12
|
+
- \u6700\u8FD1\u6587\u672C\u6458\u8981\uFF1A${t.summary||"(\u65E0\u53EF\u89C1\u6458\u8981)"}
|
|
13
|
+
- \u6700\u8FD1\u6D3B\u52A8\u65F6\u95F4\uFF1A${t.lastActivityAt}
|
|
14
14
|
- \u6700\u8FD1 ${i} \u5206\u949F\u6CA1\u6709\u65B0\u6D3B\u52A8\u3002
|
|
15
15
|
|
|
16
|
-
\u8BF7\u51B3\u5B9A\u662F\u5426\u7EE7\u7EED\u7B49\u5F85\u3001\u8BE2\u95EE\u7528\u6237\u3001\u505C\u6B62 worker\u3001\u6216\u521B\u5EFA\u5176\u4ED6 worker \u534F\u52A9\u3002`;this.registry.updateWorker(
|
|
16
|
+
\u8BF7\u51B3\u5B9A\u662F\u5426\u7EE7\u7EED\u7B49\u5F85\u3001\u8BE2\u95EE\u7528\u6237\u3001\u505C\u6B62 worker\u3001\u6216\u521B\u5EFA\u5176\u4ED6 worker \u534F\u52A9\u3002`;this.registry.updateWorker(t.sessionId,{healthNotifiedAt:new Date(e).toISOString(),lastHealthSummary:c}),o.status==="idle"&&this.dispatchChatSend(o.sessionId,"manager",o.workDir,c,o.agentSessionId,o.modelId)}}async interruptAndResumeManager(e,a,t){this.opts.getRuntime().sessions.get(e.sessionId)?.currentRunId&&(this.registry.upsertManager({...e,status:"interrupting"}),await this.opts.dispatchReq({type:"req",id:`manager-interrupt-${p()}`,method:"chat.abort",params:{sessionId:e.sessionId}}));const o=t?`\u4E8B\u4EF6\u7C7B\u578B\uFF1A${t}
|
|
17
17
|
|
|
18
|
-
${a}`:a;await this.dispatchChatSend(e.sessionId,"manager",e.workDir,o,e.agentSessionId,e.modelId)}bindManagerAdapterEvents(e,a){a.on("agentEvent",
|
|
18
|
+
${a}`:a;await this.dispatchChatSend(e.sessionId,"manager",e.workDir,o,e.agentSessionId,e.modelId)}bindManagerAdapterEvents(e,a){a.on("agentEvent",t=>{if(t.state==="start"&&t.agentSessionId){const n=this.registry.getManager(e);n&&this.noteManagerAgentSession(e,t.agentSessionId,n.workDir,n.modelId)}t.state==="start"&&this.updateManagerStatus(e,"running"),(t.state==="final"||t.state==="error"||t.state==="aborted")&&this.updateManagerStatus(e,"idle")})}updateManagerStatus(e,a){const t=this.registry.getManager(e);t&&this.registry.upsertManager({...t,status:a})}getExternalChannelStatus(e){return this.channelRuntime.getManagerChannelStatus(e)}getManagerExternalChannelSystemPrompt(e){return this.channelRuntime.listManagerExternalChannels(e).map(a=>de(a,void 0,e,"manager")).join(`
|
|
19
19
|
|
|
20
|
-
`).trim()}}function
|
|
21
|
-
${h}`;return s.has(
|
|
22
|
-
`)}function
|
|
23
|
-
`);return[
|
|
24
|
-
`)}function
|
|
20
|
+
`).trim()}}function O(r){return Array.isArray(r)?r.map(e=>({name:String(e?.name||"").trim()})).filter(e=>e.name):[]}function U(r){return r==="wechat-channel"||r==="macos-flow"||r==="macos-probe"||r==="windows-visual-flow"||r==="wechat-rpa-lab"||r==="fixture-jsonl"?r:void 0}function Se(r,e){const a=r.map(t=>H(t,e)).filter(t=>t!=null);return a.length?a:void 0}function ke(r,e,a,t){const n=e||"",s=new Set;return r.map((i,c)=>{const d=H(i,t),l=ee({type:i.type,name:d?.name||i.name,path:d?.path,mimeType:d?.mimeType||i.mimeType,availability:i.availability,providerError:i.providerError||Ce(i)},c),h=d?.path||"";if(h){const M=`${l}
|
|
21
|
+
${h}`;return s.has(M)||n.includes(h)?"":(s.add(M),`${a}: ${l}`)}const g=l;return s.has(g)?"":(s.add(g),`${a}: ${l}`)}).filter(i=>!n.includes(i)).join(`
|
|
22
|
+
`)}function Re(r){const a=(r.channelType==="wechat-rpa"?Me(r.sender.name):r.sender.name||r.sender.id)||(r.channelType==="wechat-rpa"?"\u5BF9\u65B9":r.sender.id)||"\u5BF9\u65B9",t=ke(r.attachments,r.text,a,r.channelType),s=[r.text?Z({senderName:a,senderId:r.sender.id,text:r.text}):t?"":`${a}:`,t].filter(Boolean).join(`
|
|
23
|
+
`);return[X({conversationName:r.conversationName||r.conversationId,messages:[]}),s].filter(Boolean).join(`
|
|
24
|
+
`)}function Me(r){const e=r?.trim()||"";return!e||/^(contact|self|system|unknown)$/i.test(e)||/^wechat-sender:/i.test(e)?"":e}function Ce(r){return!r.localPath||r.availability&&r.availability!=="edge-local"||x(r.localPath)?"":"edge-local-unavailable"}function H(r,e){return r.localPath&&Ee(r,e)&&x(r.localPath)?{path:r.localPath,name:r.name||y.basename(r.localPath)||"attachment",mimeType:r.mimeType||G(r)}:r.url&&Te(r)?{path:r.url,name:r.name||r.url.split("/").filter(Boolean).at(-1)||"attachment",mimeType:r.mimeType||G(r)}:r.thumbnailPath&&!K(r,e)&&x(r.thumbnailPath)?{path:r.thumbnailPath,name:r.name?`${r.name}-preview.png`:y.basename(r.thumbnailPath)||"preview.png",mimeType:"image/png"}:null}function Ae(r){return r?r.type!=="wechat-rpa"?r:{configured:r.configured,connected:r.connected,type:r.type,channelId:r.channelId,name:r.name,canReply:r.canReply,systemPrompt:r.systemPrompt,wechatRpaSource:r.wechatRpaSource,wechatRpaGroups:r.wechatRpaGroups,pollIntervalMs:r.pollIntervalMs,recentLimit:r.recentLimit,idleSeconds:r.idleSeconds,forceForeground:r.forceForeground,noRestore:r.noRestore,downloadAttachments:r.downloadAttachments,selfNickname:r.selfNickname,wechatRpaPrivacyConsentAccepted:r.wechatRpaPrivacyConsentAccepted,wechatRpaServerDecisionAvailable:r.wechatRpaServerDecisionAvailable,wechatRpaPreflightChecks:r.wechatRpaPreflightChecks,wechatRpaRuntimeState:r.wechatRpaRuntimeState,wechatRpaLastMessageAt:r.wechatRpaLastMessageAt,wechatRpaPendingReplyCount:r.wechatRpaPendingReplyCount,wechatRpaLastError:r.wechatRpaLastError}:null}function Ee(r,e){return r.providerError||r.availability&&r.availability!=="edge-local"?!1:K(r,e)?r.materializationKind==="original-file"&&r.isOriginal===!0&&r.mimeKindMatches===!0&&!be(r.localPath):!0}function Te(r){return!r.providerError&&(!r.availability||r.availability==="server-url")}function K(r,e){if(e!=="wechat-rpa")return!1;const a=String(r.type||"").toLowerCase();return a==="file"||a==="video"||a==="video-file"}function be(r){const e=String(r||"").replace(/\\/g,"/"),a=y.basename(e).toLowerCase();return/(^|\/)\.uploads(\/|$)/.test(e)||/(^|-)preview([-.]|$)/i.test(a)}function x(r){try{return k.statSync(r).isFile()}catch{return!1}}function G(r){return r.type==="image"?"image/*":r.type==="video"?"video/*":r.type==="audio"?"audio/*":"application/octet-stream"}function m(r){const e=Number(r);return Number.isFinite(e)?e:void 0}function j(r,e){const a=E(r,"reasonCode")||xe(r),t=E(r,"outDir"),n=E(r,"helperTracePath"),s=E(r,"activityGuardPath");return{error:r instanceof Error?r.message:String(r||a),reasonCode:a,...t?{outDir:t}:{},...n?{helperTracePath:n}:{},...s?{activityGuardPath:s}:{},...e?{traceId:e}:{}}}function E(r,e){if(!r||typeof r!="object")return"";const a=r[e];return typeof a=="string"&&a.trim()?a.trim():""}function xe(r){const e=r instanceof Error?r.message:String(r||"");return/^([a-z][a-z0-9_]*)(?::|\b)/i.exec(e.trim())?.[1]||"wechat_tool_failed"}export{Ye as ManagerRuntimeService,Je as getManagerRuntimeService,ze as setManagerRuntimeService};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import F from"node:os";import{createAgent as H}from"../../agents/adapter.js";import{buildApprovalPendingPayload as O,buildUserMessagePayload as G}from"@shennian/wire";import{reportLog as x}from"../../log-reporter.js";import{lookupClaudeTranscriptCwd as U}from"../../native-fusion/parsers.js";import{appendMessage as A,recordSession as
|
|
2
|
-
`),a=e>0?Math.min(e,80):Math.min(t.length,80);return t.slice(0,a)}function Z(t,e,a={}){if(t.state==="tool-call"||t.state==="tool-result"){const s={runId:t.runId,sourceSeq:t.seq};return{state:t.state,runId:t.runId,seq:t.seq,sessionId:e,detailRef:s,...t.name?{name:t.name}:{},...t.source?{source:t.source}:{},...t.agentSessionId?{agentSessionId:t.agentSessionId}:{},...a}}return{...t,sessionId:e,...a}}const ee=3e4;function te(t){return t.state==="heartbeat"?t.runPhase??null:t.state==="tool-call"||t.state==="tool-result"?"tool_running":t.state==="approval-pending"?"waiting_approval":t.state==="delta"?t.thinking?"thinking":"streaming_text":t.state==="init"||t.state==="start"?"thinking":null}function ne(t,e){const a=e instanceof Error?e.message:String(e);return t==="pi"&&(a.includes("429")||a.includes("daily_quota_exceeded")||a.includes("nian_quota_exceeded"))?a.includes("too quickly")||a.includes("per minute")?"Nian \u8BF7\u6C42\u8FC7\u4E8E\u9891\u7E41\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\u3002":"Nian \u4ECA\u65E5\u989D\u5EA6\u5DF2\u7528\u5B8C\uFF0C\u6B21\u65E5\u81EA\u52A8\u6062\u590D\u3002":`Agent send failed: ${a}`}function v(t,e){return t!=="manager"?t:e==="claude"?"claude":"codex"}function z(t,e,a){t.client.sendEvent({type:"event",event:"session.message",payload:{sessionId:e.sessionId,message:e,session:{id:e.sessionId,agentType:a.agentType,agentSessionId:a.agentSessionId??null,modelId:a.modelId??null,workDir:a.workDir,status:"active",externalChannel:j(t,e.sessionId,a.agentType)}}})}function ae(t,e){L({sessionId:e.sessionId,agentType:e.agentType,workDir:e.workDir,agentSessionId:e.agentSessionId??null,modelId:e.modelId??null}),t.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:e.sessionId,agentType:e.agentType,agentSessionId:e.agentSessionId??null,modelId:e.modelId??null,workDir:e.workDir,status:"active",externalChannel:j(t,e.sessionId,e.agentType)}}})}function re(t){if(!t||typeof t!="object")return null;const e=t;return{configured:e.configured===void 0?void 0:!!e.configured,connected:!!e.connected,type:typeof e.type=="string"?e.type:null,channelId:typeof e.channelId=="string"?e.channelId:null,name:typeof e.name=="string"?e.name:null,canReply:e.canReply===void 0||e.canReply===null?null:!!e.canReply,systemPrompt:typeof e.systemPrompt=="string"?e.systemPrompt:null,wechatRpaSource:typeof e.wechatRpaSource=="string"?e.wechatRpaSource:null,wechatRpaGroups:Array.isArray(e.wechatRpaGroups)?e.wechatRpaGroups.map(a=>({name:String(a?.name||"").trim()})).filter(a=>a.name):null,pollIntervalMs:Number.isFinite(e.pollIntervalMs)?Number(e.pollIntervalMs):null,recentLimit:Number.isFinite(e.recentLimit)?Number(e.recentLimit):null,idleSeconds:Number.isFinite(e.idleSeconds)?Number(e.idleSeconds):null,forceForeground:e.forceForeground===void 0||e.forceForeground===null?null:!!e.forceForeground,noRestore:e.noRestore===void 0||e.noRestore===null?null:!!e.noRestore,downloadAttachments:e.downloadAttachments===void 0||e.downloadAttachments===null?null:!!e.downloadAttachments,downloadAttachmentsDir:typeof e.downloadAttachmentsDir=="string"?e.downloadAttachmentsDir:null}}function se(t){return!!(t?.configured??t?.connected)}function B(t){return K(t)}function W(t,e,a){return se(e)?{...Q()?.getInjectedEnv(t,null,process.cwd(),"external")??{},SHENNIAN_EXTERNAL_SESSION_ID:t,SHENNIAN_MANAGER_SESSION_ID:t,...a?.trim()?{SHENNIAN_EXTERNAL_REPLY_TARGET:a.trim()}:{}}:{}}function oe(t,e,a,s,d){t.configure?.({sessionId:e,externalChannel:s??null,env:{...B(a),...W(e,s,d)}})}function j(t,e,a){const s=t.sessions.get(e);return s?.externalChannel?s.externalChannel:a==="manager"?t.managerRuntime?.getExternalChannelStatus(e)??null:null}function le(t,e,a){if(t!=="claude"||!a)return e;const s=e.trim();return!!s&&s.startsWith("-")&&!s.includes("/")?U(a)??e:e}function ie(t,e,a,s){let d=null;function f(n,r={}){t.client.sendAgentEvent({type:"event",event:"agent",payload:Z(n,e,r),seq:n.seq,id:`agent-evt-${n.runId}-${n.seq}`})}function l(n){n?.heartbeatTimer&&(clearInterval(n.heartbeatTimer),n.heartbeatTimer=null)}function i(n){if(!n.currentRunId||!n.currentRunPhase)return;const r=n.heartbeatSeq++;t.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:e,runId:n.currentRunId,seq:r,runPhase:n.currentRunPhase},seq:r,id:`agent-heartbeat-${n.currentRunId}-${r}-${Date.now()}`})}function I(n){!n||n.heartbeatTimer||(n.heartbeatTimer=setInterval(()=>{i(n)},ee),n.heartbeatTimer.unref?.())}function S(n){const r=n?.pendingTextEvent;!n||!r||!r.text||(f({state:"delta",runId:r.runId,seq:r.seq,text:r.text,thinking:r.thinking||void 0}),n.pendingTextEvent=null)}s.on("agentEvent",n=>{const r=t.sessions.get(e),T=te(n),R=n.state==="final"||n.state==="error"||n.state==="aborted";r&&(r.nextEventSeq=n.seq+1,R||(r.currentRunId=n.runId),!R&&T&&(r.currentRunPhase=T,I(r)),n.agentSessionId&&(r.agentSessionId=n.agentSessionId)),t.managerRuntime?.noteAgentEvent(e,n),n.state!=="delta"&&x({level:"info",sessionId:e,wsEvent:`agent.${n.state}`,wsDirection:"out",metadata:{runId:n.runId,seq:n.seq,agentType:a}}),n.state==="delta"&&n.text&&!n.thinking?A(e,{id:`agent-${n.runId}-${n.seq}`,sessionId:e,role:"agent",ts:Date.now(),payload:n.text}):n.state==="tool-call"||n.state==="tool-result"?A(e,{id:`agent-${n.runId}-${n.seq}`,sessionId:e,role:"agent",ts:Date.now(),payload:JSON.stringify({v:1,type:n.state==="tool-call"?"tool_use":"tool_result",name:n.name,status:n.state==="tool-call"?"running":"completed",detailRef:{runId:n.runId,sourceSeq:n.seq},args:n.args,result:n.result})}):n.state==="approval-pending"?A(e,{id:`agent-${n.runId||"run"}-${n.seq}`,sessionId:e,role:"agent",ts:Date.now(),payload:O(n.approval)}):(n.state==="error"||n.state==="aborted")&&n.message&&A(e,{id:`agent-${n.runId||"run"}-${n.seq}`,sessionId:e,role:"agent",ts:Date.now(),payload:n.message});const h=`${e}:${n.runId}`;if(n.state==="delta"&&!n.thinking&&n.text&&t.runTextAcc.set(h,(t.runTextAcc.get(h)??"")+n.text),n.agentSessionId&&n.agentSessionId!==d&&(d=n.agentSessionId,t.nativeFusion?.noteManagedSourceSession(e,v(a,n.source),n.agentSessionId),r&&L({sessionId:e,agentType:a,workDir:r.workDir,agentSessionId:n.agentSessionId}),t.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:e,agentType:a,agentSessionId:n.agentSessionId}}})),n.state==="delta"){const m=n.text??"";if(!m)return;const b=!!n.thinking;r?.pendingTextEvent&&(r.pendingTextEvent.runId!==n.runId||r.pendingTextEvent.thinking!==b)&&S(r),r&&!r.pendingTextEvent&&(r.pendingTextEvent={runId:n.runId,seq:n.seq,text:"",thinking:b}),r?.pendingTextEvent&&(r.pendingTextEvent.text+=m,r.pendingTextEvent.seq=n.seq);return}let p={};if(n.state==="final"){S(r);const m=t.runTextAcc.get(h)??"";m&&(p={messageSummary:Y(m)}),t.runTextAcc.delete(h)}else n.state==="error"||n.state==="aborted"?(S(r),t.runTextAcc.delete(h)):(n.state==="tool-call"||n.state==="tool-result"||n.state==="approval-pending")&&S(r);R&&r?.currentRunId===n.runId&&(r.currentRunId=null,r.currentRunPhase=null,r.nextEventSeq=0,l(r),t.chatQueue?.noteTerminal(e)),f(n,p)}),s.on("error",n=>{console.error(`[chat.send] adapter error sessionId=${e} agentType=${a}: ${n.message}`),t.sessions.delete(e),t.chatQueue?.noteTerminal(e),t.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:e,message:n.message,runId:"",seq:0}})})}function de(t,e){if(t.processedReqIds.add(e),t.processedReqIds.size>1e3){const a=t.processedReqIds.values().next().value;t.processedReqIds.delete(a)}}async function q(t){t.heartbeatTimer&&(clearInterval(t.heartbeatTimer),t.heartbeatTimer=null),t.adapter.removeAllListeners(),await t.adapter.stop().catch(()=>{})}async function ce(t,e,a,s,d,f,l){t.evictIdleSessions();const i=H(a);if(!i)throw new Error(`Unsupported agent: ${a}`);oe(i,e,a,f,l),await i.start(e,s,d);const I={adapter:i,workDir:s,agentType:a,agentSessionId:d??null,lastActiveAt:Date.now(),currentRunId:null,currentRunPhase:null,nextEventSeq:0,heartbeatSeq:0,heartbeatTimer:null,pendingTextEvent:null,externalChannel:f??null,externalReplyTarget:l??null,externalChannelEnv:{...B(a),...W(e,f,l)}};return t.sessions.set(e,I),ie(t,e,a,i),I}function ue(t,e){const a=t.sessions.get(e),s=a?.currentRunId;if(!a||!s)return;const d=a.nextEventSeq;t.runTextAcc.delete(`${e}:${s}`),a.pendingTextEvent=null,a.currentRunId=null,a.currentRunPhase=null,a.nextEventSeq=0,a.heartbeatTimer&&(clearInterval(a.heartbeatTimer),a.heartbeatTimer=null),t.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"aborted",sessionId:e,runId:s,seq:d},seq:d,id:`agent-evt-${s}-${d}`}),t.chatQueue?.noteTerminal(e)}async function Ae(t,e){if(t.processedReqIds.has(e.id)){t.client.sendRes({type:"res",id:e.id,ok:!0});return}de(t,e.id);const{sessionId:a,text:s,agentType:d,workDir:f,agentSessionId:l,modelId:i,managerDefaultWorkerAgentType:I,managerDefaultWorkerModelId:S,reasoningEffort:n,clientMessageId:r,sessionListProjection:T,waitForDispatch:R,responseId:h}=e.params,p=h||e.id;J(T);const m=re(e.params.externalChannel),b=typeof e.params.replyTarget=="string"?e.params.replyTarget.trim():"";if(!a||!s){t.processedReqIds.delete(e.id),t.client.sendRes({type:"res",id:p,ok:!1,error:"sessionId and text are required"});return}const u=d;u==="manager"&&t.managerRuntime?.setManagerWorkerDefaults(a,I??null,S??null);const y=(u==="claude"||u==="codex")&&typeof n=="string"&&n.trim()?n.trim():void 0,E=t.resolvePath(le(u,f||F.homedir(),l)||F.homedir()),$=E,D=X(e.params.attachments),g=D?.length?await V({text:s,attachments:D,workDir:E}):{text:s,attachments:D,localized:!1};let o=t.sessions.get(a);if(o){if(o.lastActiveAt=Date.now(),o.agentType!==u||o.workDir!==E||JSON.stringify(o.externalChannel??null)!==JSON.stringify(m??null)){t.sessions.delete(a);try{await q(o)}catch{t.processedReqIds.delete(e.id)}o=void 0}else if(l&&o.agentSessionId!==l)try{await o.adapter.resume(l),o.agentSessionId=l}catch{t.sessions.delete(a);try{await q(o)}catch{t.processedReqIds.delete(e.id)}o=void 0}}if(!o)try{o=await ce(t,a,u,E,l,m,b)}catch(c){const w=c instanceof Error&&c.message.startsWith("Unsupported agent:")?c.message:`Failed to start ${d}: ${c instanceof Error?c.message:String(c)}`;console.error(`[chat.send] start failed reqId=${e.id} sessionId=${a} agentType=${d} workDir=${E} agentSessionId=${l??""}: ${w}`),t.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:a,message:w,runId:"",seq:0}}),t.processedReqIds.delete(e.id),t.client.sendRes({type:"res",id:p,ok:!1,error:w});return}const N={id:r??`user-${e.id}`,sessionId:a,role:"user",ts:Date.now(),payload:G(g.text,g.attachments)};x({level:"info",sessionId:a,wsEvent:"chat.send.start",metadata:{reqId:e.id,agentType:u,modelId:i,reasoningEffort:y}});const P=()=>{ae(t,{sessionId:a,agentType:u,workDir:$,agentSessionId:o.agentSessionId??l??null,modelId:i}),o.currentRunId||(o.nextEventSeq=0),t.nativeFusion?.registerManagedSend({sessionId:a,agentType:u,sourceAgentType:v(u,i),canonicalMessageId:r??null,sourceSessionKey:o.agentSessionId??l??null,text:g.text}),A(a,N),z(t,N,{agentType:u,workDir:$,agentSessionId:o.agentSessionId??l??null,modelId:i})},_=async(c,w)=>{const k=ne(u,c);console.error(`[chat.send] send failed reqId=${e.id} sessionId=${a} agentType=${d} workDir=${E} agentSessionId=${o.agentSessionId??l??""}: ${k}`),t.sessions.delete(a);try{await q(o)}catch{}if(t.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:a,message:k,runId:"",seq:0}}),!w){const M={id:`agent-error-${e.id}-${Date.now()}`,sessionId:a,role:"agent",ts:Date.now(),payload:k};A(a,M),z(t,M,{agentType:u,workDir:$,agentSessionId:o.agentSessionId??l??null,modelId:i})}w&&(t.processedReqIds.delete(e.id),t.client.sendRes({type:"res",id:p,ok:!1,error:k}))};if(R){try{const c=g.attachments;c?.length?await o.adapter.send(g.text,i,y,c):y?await o.adapter.send(g.text,i,y):await o.adapter.send(g.text,i),x({level:"info",sessionId:a,wsEvent:"chat.send.done",metadata:{reqId:e.id}})}catch(c){await _(c,!0);return}P(),t.client.sendRes({type:"res",id:p,ok:!0,...g.localized?{payload:{localizedAttachments:!0}}:{}}),x({level:"info",sessionId:a,wsEvent:"chat.send.res",metadata:{reqId:e.id,ok:!0}});return}P(),t.client.sendRes({type:"res",id:p,ok:!0,...g.localized?{payload:{localizedAttachments:!0}}:{}}),x({level:"info",sessionId:a,wsEvent:"chat.send.res",metadata:{reqId:e.id,ok:!0}});const C=g.attachments;(C?.length?o.adapter.send(g.text,i,y,C):y?o.adapter.send(g.text,i,y):o.adapter.send(g.text,i)).then(()=>{x({level:"info",sessionId:a,wsEvent:"chat.send.done",metadata:{reqId:e.id}})}).catch(c=>{_(c,!1)})}async function Re(t,e){const{sessionId:a}=e.params,s=t.sessions.get(a);if(s){try{await s.adapter.stop()}catch{}ue(t,a),t.activityPublisher?.publish(a,null)}t.client.sendRes({type:"res",id:e.id,ok:!0})}export{Re as handleChatAbort,Ae as handleChatSend};
|
|
1
|
+
import F from"node:os";import{createAgent as H}from"../../agents/adapter.js";import{buildApprovalPendingPayload as O,buildUserMessagePayload as G}from"@shennian/wire";import{reportLog as x}from"../../log-reporter.js";import{lookupClaudeTranscriptCwd as U}from"../../native-fusion/parsers.js";import{appendMessage as A,recordSession as v}from"../store.js";import{mergeProjectedSessions as J}from"../projection.js";import{getManagerRuntimeService as Q}from"../../manager/runtime.js";import{buildManagedAgentEnv as K}from"../../agents/config-status.js";import{materializeRemoteChatAttachments as V}from"../remote-attachments.js";function X(t){if(!Array.isArray(t))return;const e=t.map(a=>{if(!a||typeof a!="object")return null;const s=a,d=typeof s.path=="string"?s.path:"",f=typeof s.name=="string"?s.name:"",l=typeof s.mimeType=="string"?s.mimeType:"";if(!d||!f||!l)return null;const i=typeof s.previewData=="string"&&s.previewData.trim()?s.previewData.trim():void 0;return{path:d,name:f,mimeType:l,kind:l.startsWith("image/")?"image":"file",...i?{previewData:i}:{}}}).filter(a=>a!=null);return e.length?e:void 0}function Y(t){const e=t.indexOf(`
|
|
2
|
+
`),a=e>0?Math.min(e,80):Math.min(t.length,80);return t.slice(0,a)}function Z(t,e,a={}){if(t.state==="tool-call"||t.state==="tool-result"){const s={runId:t.runId,sourceSeq:t.seq};return{state:t.state,runId:t.runId,seq:t.seq,sessionId:e,detailRef:s,...t.name?{name:t.name}:{},...t.source?{source:t.source}:{},...t.agentSessionId?{agentSessionId:t.agentSessionId}:{},...a}}return{...t,sessionId:e,...a}}const ee=3e4;function te(t){return t.state==="heartbeat"?t.runPhase??null:t.state==="tool-call"||t.state==="tool-result"?"tool_running":t.state==="approval-pending"?"waiting_approval":t.state==="delta"?t.thinking?"thinking":"streaming_text":t.state==="init"||t.state==="start"?"thinking":null}function ne(t,e){const a=e instanceof Error?e.message:String(e);return t==="pi"&&(a.includes("429")||a.includes("daily_quota_exceeded")||a.includes("nian_quota_exceeded"))?a.includes("too quickly")||a.includes("per minute")?"Nian \u8BF7\u6C42\u8FC7\u4E8E\u9891\u7E41\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\u3002":"Nian \u4ECA\u65E5\u989D\u5EA6\u5DF2\u7528\u5B8C\uFF0C\u6B21\u65E5\u81EA\u52A8\u6062\u590D\u3002":`Agent send failed: ${a}`}function B(t,e){return t!=="manager"?t:e==="claude"?"claude":"codex"}function L(t,e,a){t.client.sendEvent({type:"event",event:"session.message",payload:{sessionId:e.sessionId,message:e,session:{id:e.sessionId,agentType:a.agentType,agentSessionId:a.agentSessionId??null,modelId:a.modelId??null,workDir:a.workDir,status:"active",externalChannel:j(t,e.sessionId,a.agentType)}}})}function ae(t,e){v({sessionId:e.sessionId,agentType:e.agentType,workDir:e.workDir,agentSessionId:e.agentSessionId??null,modelId:e.modelId??null}),t.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:e.sessionId,agentType:e.agentType,agentSessionId:e.agentSessionId??null,modelId:e.modelId??null,workDir:e.workDir,status:"active",externalChannel:j(t,e.sessionId,e.agentType)}}})}function re(t){if(!t||typeof t!="object")return null;const e=t;return{configured:e.configured===void 0?void 0:!!e.configured,connected:!!e.connected,type:typeof e.type=="string"?e.type:null,channelId:typeof e.channelId=="string"?e.channelId:null,name:typeof e.name=="string"?e.name:null,canReply:e.canReply===void 0||e.canReply===null?null:!!e.canReply,systemPrompt:typeof e.systemPrompt=="string"?e.systemPrompt:null,wechatRpaSource:typeof e.wechatRpaSource=="string"?e.wechatRpaSource:null,wechatRpaGroups:Array.isArray(e.wechatRpaGroups)?e.wechatRpaGroups.map(a=>({name:String(a?.name||"").trim()})).filter(a=>a.name):null,pollIntervalMs:Number.isFinite(e.pollIntervalMs)?Number(e.pollIntervalMs):null,recentLimit:Number.isFinite(e.recentLimit)?Number(e.recentLimit):null,idleSeconds:Number.isFinite(e.idleSeconds)?Number(e.idleSeconds):null,forceForeground:e.forceForeground===void 0||e.forceForeground===null?null:!!e.forceForeground,noRestore:e.noRestore===void 0||e.noRestore===null?null:!!e.noRestore,downloadAttachments:e.downloadAttachments===void 0||e.downloadAttachments===null?null:!!e.downloadAttachments,downloadAttachmentsDir:typeof e.downloadAttachmentsDir=="string"?e.downloadAttachmentsDir:null}}function se(t){return!!(t?.configured??t?.connected)}function z(t){return K(t)}function W(t,e,a){return se(e)?{...Q()?.getInjectedEnv(t,null,process.cwd(),"external")??{},SHENNIAN_EXTERNAL_SESSION_ID:t,SHENNIAN_MANAGER_SESSION_ID:t,...a?.trim()?{SHENNIAN_EXTERNAL_REPLY_TARGET:a.trim()}:{}}:{}}function oe(t,e,a,s,d){t.configure?.({sessionId:e,externalChannel:s??null,env:{...z(a),...W(e,s,d)}})}function j(t,e,a){const s=t.sessions.get(e);return s?.externalChannel?s.externalChannel:a==="manager"?t.managerRuntime?.getExternalChannelStatus(e)??null:null}function le(t,e,a){if(t!=="claude"||!a)return e;const s=e.trim();return!!s&&s.startsWith("-")&&!s.includes("/")?U(a)??e:e}function ie(t,e,a,s){let d=null;function f(n,r={}){t.client.sendAgentEvent({type:"event",event:"agent",payload:Z(n,e,r),seq:n.seq,id:`agent-evt-${n.runId}-${n.seq}`})}function l(n){n?.heartbeatTimer&&(clearInterval(n.heartbeatTimer),n.heartbeatTimer=null)}function i(n){if(!n.currentRunId||!n.currentRunPhase)return;const r=n.heartbeatSeq++;t.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:e,runId:n.currentRunId,seq:r,runPhase:n.currentRunPhase},seq:r,id:`agent-heartbeat-${n.currentRunId}-${r}-${Date.now()}`})}function S(n){!n||n.heartbeatTimer||(n.heartbeatTimer=setInterval(()=>{i(n)},ee),n.heartbeatTimer.unref?.())}function E(n){const r=n?.pendingTextEvent;!n||!r||!r.text||(f({state:"delta",runId:r.runId,seq:r.seq,text:r.text,thinking:r.thinking||void 0}),n.pendingTextEvent=null)}s.on("agentEvent",n=>{const r=t.sessions.get(e),T=te(n),R=n.state==="final"||n.state==="error"||n.state==="aborted";r&&(r.nextEventSeq=n.seq+1,R||(r.currentRunId=n.runId),!R&&T&&(r.currentRunPhase=T,S(r)),n.agentSessionId&&(r.agentSessionId=n.agentSessionId)),t.managerRuntime?.noteAgentEvent(e,n),n.state!=="delta"&&x({level:"info",sessionId:e,wsEvent:`agent.${n.state}`,wsDirection:"out",metadata:{runId:n.runId,seq:n.seq,agentType:a}}),n.state==="delta"&&n.text&&!n.thinking?A(e,{id:`agent-${n.runId}-${n.seq}`,sessionId:e,role:"agent",ts:Date.now(),payload:n.text}):n.state==="tool-call"||n.state==="tool-result"?A(e,{id:`agent-${n.runId}-${n.seq}`,sessionId:e,role:"agent",ts:Date.now(),payload:JSON.stringify({v:1,type:n.state==="tool-call"?"tool_use":"tool_result",name:n.name,status:n.state==="tool-call"?"running":"completed",detailRef:{runId:n.runId,sourceSeq:n.seq},args:n.args,result:n.result})}):n.state==="approval-pending"?A(e,{id:`agent-${n.runId||"run"}-${n.seq}`,sessionId:e,role:"agent",ts:Date.now(),payload:O(n.approval)}):(n.state==="error"||n.state==="aborted")&&n.message&&A(e,{id:`agent-${n.runId||"run"}-${n.seq}`,sessionId:e,role:"agent",ts:Date.now(),payload:n.message});const h=`${e}:${n.runId}`;if(n.state==="delta"&&!n.thinking&&n.text&&t.runTextAcc.set(h,(t.runTextAcc.get(h)??"")+n.text),n.agentSessionId&&n.agentSessionId!==d&&(d=n.agentSessionId,t.nativeFusion?.noteManagedSourceSession(e,B(a,n.source),n.agentSessionId),r&&v({sessionId:e,agentType:a,workDir:r.workDir,agentSessionId:n.agentSessionId}),t.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:e,agentType:a,agentSessionId:n.agentSessionId}}})),n.state==="delta"){const p=n.text??"";if(!p)return;const k=!!n.thinking;r?.pendingTextEvent&&(r.pendingTextEvent.runId!==n.runId||r.pendingTextEvent.thinking!==k)&&E(r),r&&!r.pendingTextEvent&&(r.pendingTextEvent={runId:n.runId,seq:n.seq,text:"",thinking:k}),r?.pendingTextEvent&&(r.pendingTextEvent.text+=p,r.pendingTextEvent.seq=n.seq);return}let m={};if(n.state==="final"){E(r);const p=t.runTextAcc.get(h)??"";p&&(m={messageSummary:Y(p)}),t.runTextAcc.delete(h)}else n.state==="error"||n.state==="aborted"?(E(r),t.runTextAcc.delete(h)):(n.state==="tool-call"||n.state==="tool-result"||n.state==="approval-pending")&&E(r);R&&r?.currentRunId===n.runId&&(r.currentRunId=null,r.currentRunPhase=null,r.nextEventSeq=0,l(r),t.chatQueue?.noteTerminal(e)),f(n,m)}),s.on("error",n=>{console.error(`[chat.send] adapter error sessionId=${e} agentType=${a}: ${n.message}`),t.sessions.delete(e),t.chatQueue?.noteTerminal(e),t.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:e,message:n.message,runId:"",seq:0}})})}function de(t,e){if(t.processedReqIds.add(e),t.processedReqIds.size>1e3){const a=t.processedReqIds.values().next().value;t.processedReqIds.delete(a)}}async function q(t){t.heartbeatTimer&&(clearInterval(t.heartbeatTimer),t.heartbeatTimer=null),t.adapter.removeAllListeners(),await t.adapter.stop().catch(()=>{})}async function ce(t,e,a,s,d,f,l){t.evictIdleSessions();const i=H(a);if(!i)throw new Error(`Unsupported agent: ${a}`);oe(i,e,a,f,l),await i.start(e,s,d);const S={adapter:i,workDir:s,agentType:a,agentSessionId:d??null,lastActiveAt:Date.now(),currentRunId:null,currentRunPhase:null,nextEventSeq:0,heartbeatSeq:0,heartbeatTimer:null,pendingTextEvent:null,externalChannel:f??null,externalReplyTarget:l??null,externalChannelEnv:{...z(a),...W(e,f,l)}};return t.sessions.set(e,S),ie(t,e,a,i),S}function ue(t,e){const a=t.sessions.get(e),s=a?.currentRunId;if(!a||!s)return;const d=a.nextEventSeq;t.runTextAcc.delete(`${e}:${s}`),a.pendingTextEvent=null,a.currentRunId=null,a.currentRunPhase=null,a.nextEventSeq=0,a.heartbeatTimer&&(clearInterval(a.heartbeatTimer),a.heartbeatTimer=null),t.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"aborted",sessionId:e,runId:s,seq:d},seq:d,id:`agent-evt-${s}-${d}`}),t.chatQueue?.noteTerminal(e)}async function Ae(t,e){if(t.processedReqIds.has(e.id)){t.client.sendRes({type:"res",id:e.id,ok:!0});return}de(t,e.id);const{sessionId:a,text:s,agentType:d,workDir:f,agentSessionId:l,modelId:i,managerDefaultWorkerAgentType:S,managerDefaultWorkerModelId:E,reasoningEffort:n,clientMessageId:r,sessionListProjection:T,waitForDispatch:R,responseId:h}=e.params,m=h||e.id;J(T);const p=re(e.params.externalChannel),k=typeof e.params.replyTarget=="string"?e.params.replyTarget.trim():"";if(!a||!s){t.processedReqIds.delete(e.id),t.client.sendRes({type:"res",id:m,ok:!1,error:"sessionId and text are required"});return}const u=d;u==="manager"&&t.managerRuntime?.setManagerWorkerDefaults(a,S??null,E??null);const y=(u==="claude"||u==="codex")&&typeof n=="string"&&n.trim()?n.trim():void 0,I=t.resolvePath(le(u,f||F.homedir(),l)||F.homedir()),D=I,$=X(e.params.attachments),g=$?.length?await V({text:s,attachments:$,workDir:I}):{text:s,attachments:$,localized:!1};let o=t.sessions.get(a);if(o){if(o.lastActiveAt=Date.now(),o.agentType!==u||o.workDir!==I||JSON.stringify(o.externalChannel??null)!==JSON.stringify(p??null)){t.sessions.delete(a);try{await q(o)}catch{t.processedReqIds.delete(e.id)}o=void 0}else if(l&&o.agentSessionId!==l)try{await o.adapter.resume(l),o.agentSessionId=l}catch{t.sessions.delete(a);try{await q(o)}catch{t.processedReqIds.delete(e.id)}o=void 0}}if(!o)try{o=await ce(t,a,u,I,l,p,k)}catch(c){const w=c instanceof Error&&c.message.startsWith("Unsupported agent:")?c.message:`Failed to start ${d}: ${c instanceof Error?c.message:String(c)}`;console.error(`[chat.send] start failed reqId=${e.id} sessionId=${a} agentType=${d} workDir=${I} agentSessionId=${l??""}: ${w}`),t.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:a,message:w,runId:"",seq:0}}),t.processedReqIds.delete(e.id),t.client.sendRes({type:"res",id:m,ok:!1,error:w});return}t.managerRuntime?.notifyWeChatRpaSessionBinding({sessionId:a,workDir:I,agentType:u,agentSessionId:o.agentSessionId??l??null,modelId:i??null,externalChannel:p});const N={id:r??`user-${e.id}`,sessionId:a,role:"user",ts:Date.now(),payload:G(g.text,g.attachments)};x({level:"info",sessionId:a,wsEvent:"chat.send.start",metadata:{reqId:e.id,agentType:u,modelId:i,reasoningEffort:y}});const P=()=>{ae(t,{sessionId:a,agentType:u,workDir:D,agentSessionId:o.agentSessionId??l??null,modelId:i}),o.currentRunId||(o.nextEventSeq=0),t.nativeFusion?.registerManagedSend({sessionId:a,agentType:u,sourceAgentType:B(u,i),canonicalMessageId:r??null,sourceSessionKey:o.agentSessionId??l??null,text:g.text}),A(a,N),L(t,N,{agentType:u,workDir:D,agentSessionId:o.agentSessionId??l??null,modelId:i})},C=async(c,w)=>{const b=ne(u,c);console.error(`[chat.send] send failed reqId=${e.id} sessionId=${a} agentType=${d} workDir=${I} agentSessionId=${o.agentSessionId??l??""}: ${b}`),t.sessions.delete(a);try{await q(o)}catch{}if(t.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:a,message:b,runId:"",seq:0}}),!w){const M={id:`agent-error-${e.id}-${Date.now()}`,sessionId:a,role:"agent",ts:Date.now(),payload:b};A(a,M),L(t,M,{agentType:u,workDir:D,agentSessionId:o.agentSessionId??l??null,modelId:i})}w&&(t.processedReqIds.delete(e.id),t.client.sendRes({type:"res",id:m,ok:!1,error:b}))};if(R){try{const c=g.attachments;c?.length?await o.adapter.send(g.text,i,y,c):y?await o.adapter.send(g.text,i,y):await o.adapter.send(g.text,i),x({level:"info",sessionId:a,wsEvent:"chat.send.done",metadata:{reqId:e.id}})}catch(c){await C(c,!0);return}P(),t.client.sendRes({type:"res",id:m,ok:!0,...g.localized?{payload:{localizedAttachments:!0}}:{}}),x({level:"info",sessionId:a,wsEvent:"chat.send.res",metadata:{reqId:e.id,ok:!0}});return}P(),t.client.sendRes({type:"res",id:m,ok:!0,...g.localized?{payload:{localizedAttachments:!0}}:{}}),x({level:"info",sessionId:a,wsEvent:"chat.send.res",metadata:{reqId:e.id,ok:!0}});const _=g.attachments;(_?.length?o.adapter.send(g.text,i,y,_):y?o.adapter.send(g.text,i,y):o.adapter.send(g.text,i)).then(()=>{x({level:"info",sessionId:a,wsEvent:"chat.send.done",metadata:{reqId:e.id}})}).catch(c=>{C(c,!1)})}async function Re(t,e){const{sessionId:a}=e.params,s=t.sessions.get(a);if(s){try{await s.adapter.stop()}catch{}ue(t,a),t.activityPublisher?.publish(a,null)}t.client.sendRes({type:"res",id:e.id,ok:!0})}export{Re as handleChatAbort,Ae as handleChatSend};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{loadConfig as d}from"../../config/index.js";import{getHelperRuntimeStatus as p,installHelperRuntimeWithOfficialDownload as h}from"../../commands/runtime.js";import{createWeChatChannelApiClient as w}from"../../channels/wechat-channel/client.js";import{WECHAT_CHANNEL_HELPER_VERSION as g}from"../../channels/wechat-channel/helper-assets.js";import{WeChatChannelHelperClient as y}from"../../channels/wechat-channel/helper-client.js";import{requiredWeChatChannelHelperCapabilitiesForProfile as R}from"../../channels/wechat-channel/helper-protocol.js";import{createWeChatChannelRuntime as V}from"../../channels/wechat-channel/runtime.js";import{getCurrentVersion as b}from"../../upgrade/engine.js";async function I(e,o){const
|
|
1
|
+
import{loadConfig as d}from"../../config/index.js";import{getHelperRuntimeStatus as p,installHelperRuntimeWithOfficialDownload as h}from"../../commands/runtime.js";import{createWeChatChannelApiClient as w}from"../../channels/wechat-channel/client.js";import{WECHAT_CHANNEL_HELPER_VERSION as g}from"../../channels/wechat-channel/helper-assets.js";import{WeChatChannelHelperClient as y}from"../../channels/wechat-channel/helper-client.js";import{requiredWeChatChannelHelperCapabilitiesForProfile as R}from"../../channels/wechat-channel/helper-protocol.js";import{createWeChatChannelRuntime as V}from"../../channels/wechat-channel/runtime.js";import{getCurrentVersion as b}from"../../upgrade/engine.js";async function I(e,o){const r=o.params??{},t=k(r.action);if(!t){e.client.sendRes({type:"res",id:o.id,ok:!1,error:'action must be "status", "install", "upgrade", or "repair"'});return}const n=typeof r.platform=="string"?r.platform:void 0,i=typeof r.source=="string"&&r.source.trim()?r.source:void 0,a=typeof r.channel=="string"&&r.channel.trim()?r.channel:void 0,c=typeof r.manifestUrl=="string"&&r.manifestUrl.trim()?r.manifestUrl:void 0,u=await C({action:t,platform:n,source:i,channel:a,manifestUrl:c});e.client.sendRes({type:"res",id:o.id,ok:!0,payload:u})}async function C(e){if(e.action==="status"){const i=p({platform:e.platform}),a=e.probePermissions===!1?void 0:await f(i),c=await m(i,a);return{action:e.action,status:i,permissions:a,projection:c}}const o=await h({source:e.source,platform:e.platform,force:e.action==="upgrade"||e.action==="repair",preferOfficialDownload:(e.action==="upgrade"||e.action==="repair")&&!e.source,officialDownload:{channel:e.channel,manifestUrl:e.manifestUrl}}),r=p({platform:e.platform}),t=e.probePermissions===!1?void 0:await f(r),n=await m(r,t);return{action:e.action,install:o,status:r,permissions:t,projection:n}}function k(e){return e==="status"||e==="install"||e==="upgrade"||e==="repair"?e:null}async function f(e){const o=new Date().toISOString();if(!e.ok||!e.helperPath)return{checkedAt:o,helperReady:!1,helperVersion:e.helperVersion??null,protocolVersion:e.protocolVersion??null,platform:e.platform};const r=new y({helperPath:e.helperPath,expectedHelperVersion:e.helperVersion||g,requiredCapabilities:R("send")});try{await r.start();const t=await r.request("permissions.check",{},`helper-runtime-status-${Date.now()}`),n=t.ok&&t.result&&typeof t.result=="object"?t.result:{};return{checkedAt:o,helperReady:t.ok,helperVersion:e.helperVersion??H(n.helperVersion),protocolVersion:e.protocolVersion??A(n.protocolVersion),platform:e.platform,screenRecording:l(n.screenRecording),accessibility:l(n.accessibility),inputMonitoring:l(n.inputMonitoring),automation:l(n.automation),wechatRunning:s(n.wechatRunning),wechatAppDetected:s(n.wechatAppDetected??n.wechatRunning),wechatWindowAvailable:s(n.wechatWindowAvailable)}}catch{return{checkedAt:o,helperReady:!1,helperVersion:e.helperVersion??null,protocolVersion:e.protocolVersion??null,platform:e.platform}}finally{await r.stop().catch(()=>{})}}async function m(e,o){if(!o||e.platform!=="darwin"&&e.platform!=="win32")return;const r=d(),t=r.machineId?.trim(),n=r.machineToken?.trim();if(!t||!n)return{ok:!1,error:"machine_not_paired"};try{const i=V({runtimeId:`wechat-machine:${t}`,machineId:t,platform:e.platform,bindings:[]});return await w({serverUrl:r.serverUrl,machineToken:n}).upsertRuntime(i,void 0,{helperVersion:e.helperVersion??void 0,clientRuntimeVersion:b(),preflight:o}),{ok:!0}}catch(i){return{ok:!1,error:i instanceof Error?i.message:String(i)}}}function l(e){return typeof e=="boolean"?e:"unknown"}function s(e){return typeof e=="boolean"?e:void 0}function H(e){return(typeof e=="string"?e.trim():"")||null}function A(e){const o=Number(e);return Number.isFinite(o)?o:null}export{I as handleWeChatHelperRuntime,C as runHelperRuntimeAction};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shennian",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.121",
|
|
4
4
|
"description": "Shennian — AI Agent Control Plane CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@mariozechner/pi-agent-core": "^0.64.0",
|
|
41
|
+
"@mariozechner/pi-ai": "^0.64.0",
|
|
41
42
|
"@sinclair/typebox": "^0.34.49",
|
|
42
43
|
"chalk": "^5.4.1",
|
|
43
44
|
"commander": "^13.1.0",
|