shennian 0.2.88 → 0.2.90

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/dist/assets/wechat-channel/macos/manifest.json +22 -0
  2. package/dist/assets/wechat-channel/macos/shennian-wechat-channel-helper +0 -0
  3. package/dist/bin/shennian.js +1 -1
  4. package/dist/publish-build-manifest.json +548 -0
  5. package/dist/scripts/wechat-rpa-confirmation.mjs +5 -97
  6. package/dist/src/agent-env.js +4 -105
  7. package/dist/src/agents/adapter.d.ts +6 -0
  8. package/dist/src/agents/adapter.js +1 -19
  9. package/dist/src/agents/claude.js +8 -305
  10. package/dist/src/agents/codex-control.d.ts +35 -0
  11. package/dist/src/agents/codex-control.js +2 -0
  12. package/dist/src/agents/codex-utils.js +7 -200
  13. package/dist/src/agents/codex.d.ts +8 -0
  14. package/dist/src/agents/codex.js +15 -863
  15. package/dist/src/agents/command-spec.js +2 -413
  16. package/dist/src/agents/config-status.js +1 -226
  17. package/dist/src/agents/cursor.js +1 -249
  18. package/dist/src/agents/custom.js +4 -271
  19. package/dist/src/agents/detect.js +1 -56
  20. package/dist/src/agents/external-channel-instructions.js +10 -94
  21. package/dist/src/agents/gemini.js +1 -173
  22. package/dist/src/agents/manager.js +13 -157
  23. package/dist/src/agents/model-registry/cache.js +1 -37
  24. package/dist/src/agents/model-registry/discovery.js +2 -187
  25. package/dist/src/agents/model-registry/parsers.js +4 -447
  26. package/dist/src/agents/model-registry/runner.js +1 -30
  27. package/dist/src/agents/model-registry/service.js +1 -78
  28. package/dist/src/agents/model-registry/types.js +1 -8
  29. package/dist/src/agents/model-registry.js +1 -18
  30. package/dist/src/agents/openclaw.js +2 -275
  31. package/dist/src/agents/opencode.js +1 -231
  32. package/dist/src/agents/pi-context.js +12 -217
  33. package/dist/src/agents/pi.js +14 -723
  34. package/dist/src/agents/platform-instructions.js +9 -54
  35. package/dist/src/channels/base.d.ts +4 -1
  36. package/dist/src/channels/base.js +1 -3
  37. package/dist/src/channels/registry.js +1 -30
  38. package/dist/src/channels/reply-split.js +10 -89
  39. package/dist/src/channels/runtime.d.ts +1 -0
  40. package/dist/src/channels/runtime.js +5 -533
  41. package/dist/src/channels/secret-registry.d.ts +1 -0
  42. package/dist/src/channels/secret-registry.js +1 -46
  43. package/dist/src/channels/websocket.js +8 -378
  44. package/dist/src/channels/wechat-channel/anchor.d.ts +10 -0
  45. package/dist/src/channels/wechat-channel/anchor.js +1 -0
  46. package/dist/src/channels/wechat-channel/client.d.ts +74 -0
  47. package/dist/src/channels/wechat-channel/client.js +1 -0
  48. package/dist/src/channels/wechat-channel/cooldown.d.ts +15 -0
  49. package/dist/src/channels/wechat-channel/cooldown.js +1 -0
  50. package/dist/src/channels/wechat-channel/fingerprint.d.ts +28 -0
  51. package/dist/src/channels/wechat-channel/fingerprint.js +1 -0
  52. package/dist/src/channels/wechat-channel/helper-assets.d.ts +37 -0
  53. package/dist/src/channels/wechat-channel/helper-assets.js +1 -0
  54. package/dist/src/channels/wechat-channel/helper-client.d.ts +25 -0
  55. package/dist/src/channels/wechat-channel/helper-client.js +3 -0
  56. package/dist/src/channels/wechat-channel/helper-protocol.d.ts +84 -0
  57. package/dist/src/channels/wechat-channel/helper-protocol.js +1 -0
  58. package/dist/src/channels/wechat-channel/index.d.ts +17 -0
  59. package/dist/src/channels/wechat-channel/index.js +1 -0
  60. package/dist/src/channels/wechat-channel/ledger.d.ts +33 -0
  61. package/dist/src/channels/wechat-channel/ledger.js +1 -0
  62. package/dist/src/channels/wechat-channel/media-resolver.d.ts +32 -0
  63. package/dist/src/channels/wechat-channel/media-resolver.js +1 -0
  64. package/dist/src/channels/wechat-channel/message-key.d.ts +19 -0
  65. package/dist/src/channels/wechat-channel/message-key.js +1 -0
  66. package/dist/src/channels/wechat-channel/observer.d.ts +64 -0
  67. package/dist/src/channels/wechat-channel/observer.js +1 -0
  68. package/dist/src/channels/wechat-channel/outbound-ledger.d.ts +69 -0
  69. package/dist/src/channels/wechat-channel/outbound-ledger.js +2 -0
  70. package/dist/src/channels/wechat-channel/outbound-sender.d.ts +26 -0
  71. package/dist/src/channels/wechat-channel/outbound-sender.js +1 -0
  72. package/dist/src/channels/wechat-channel/preflight.d.ts +37 -0
  73. package/dist/src/channels/wechat-channel/preflight.js +1 -0
  74. package/dist/src/channels/wechat-channel/runner.d.ts +34 -0
  75. package/dist/src/channels/wechat-channel/runner.js +1 -0
  76. package/dist/src/channels/wechat-channel/runtime.d.ts +45 -0
  77. package/dist/src/channels/wechat-channel/runtime.js +1 -0
  78. package/dist/src/channels/wechat-channel/scheduler.d.ts +35 -0
  79. package/dist/src/channels/wechat-channel/scheduler.js +1 -0
  80. package/dist/src/channels/wechat-rpa/macos-flow.js +1 -96
  81. package/dist/src/channels/wechat-rpa/macos.js +6 -48
  82. package/dist/src/channels/wechat-rpa/normalizer.js +7 -127
  83. package/dist/src/channels/wechat-rpa.d.ts +21 -0
  84. package/dist/src/channels/wechat-rpa.js +6 -1022
  85. package/dist/src/channels/wecom.js +4 -357
  86. package/dist/src/commands/agent.js +6 -131
  87. package/dist/src/commands/daemon-windows.js +8 -48
  88. package/dist/src/commands/daemon.js +19 -1013
  89. package/dist/src/commands/external-attachments.js +1 -51
  90. package/dist/src/commands/external.js +1 -137
  91. package/dist/src/commands/manager.js +2 -389
  92. package/dist/src/commands/pair-qr.js +1 -6
  93. package/dist/src/commands/pair.js +9 -287
  94. package/dist/src/commands/tools.js +1 -34
  95. package/dist/src/commands/upgrade.js +1 -198
  96. package/dist/src/config/index.js +1 -35
  97. package/dist/src/daemon-log.js +6 -58
  98. package/dist/src/env-path.js +1 -64
  99. package/dist/src/fs/boundary.js +1 -126
  100. package/dist/src/fs/handler.js +1 -130
  101. package/dist/src/fs/security.js +1 -32
  102. package/dist/src/fs/text-decoder.d.ts +10 -0
  103. package/dist/src/fs/text-decoder.js +1 -0
  104. package/dist/src/index.js +2 -404
  105. package/dist/src/log-reporter.js +1 -16
  106. package/dist/src/manager/prompt.js +29 -34
  107. package/dist/src/manager/registry.js +2 -269
  108. package/dist/src/manager/runtime.js +19 -1003
  109. package/dist/src/native-fusion/config.js +1 -5
  110. package/dist/src/native-fusion/opencode-parser.js +3 -123
  111. package/dist/src/native-fusion/parser-common.js +8 -264
  112. package/dist/src/native-fusion/parsers.js +8 -729
  113. package/dist/src/native-fusion/service.d.ts +10 -0
  114. package/dist/src/native-fusion/service.js +2 -198
  115. package/dist/src/native-fusion/state.js +1 -22
  116. package/dist/src/native-fusion/types.js +1 -1
  117. package/dist/src/region.js +1 -88
  118. package/dist/src/relay/client.js +1 -343
  119. package/dist/src/session/archive-zip.js +1 -220
  120. package/dist/src/session/handlers/agent-config.js +1 -150
  121. package/dist/src/session/handlers/agents.js +1 -55
  122. package/dist/src/session/handlers/chat.js +2 -733
  123. package/dist/src/session/handlers/control.js +1 -55
  124. package/dist/src/session/handlers/fs.js +1 -747
  125. package/dist/src/session/handlers/session-refresh.js +1 -35
  126. package/dist/src/session/handlers/skills.js +1 -121
  127. package/dist/src/session/handlers/title.js +1 -60
  128. package/dist/src/session/handlers/tool-detail.d.ts +3 -0
  129. package/dist/src/session/handlers/tool-detail.js +1 -0
  130. package/dist/src/session/manager.d.ts +3 -0
  131. package/dist/src/session/manager.js +1 -261
  132. package/dist/src/session/projection.js +1 -54
  133. package/dist/src/session/queue.js +4 -317
  134. package/dist/src/session/remote-attachments.js +1 -72
  135. package/dist/src/session/store.js +3 -109
  136. package/dist/src/session/types.d.ts +4 -0
  137. package/dist/src/session/types.js +1 -4
  138. package/dist/src/skills/registry.js +15 -148
  139. package/dist/src/skills/setup.js +1 -101
  140. package/dist/src/tools/markdown-to-pdf.js +10 -346
  141. package/dist/src/upgrade/engine.js +3 -347
  142. package/package.json +3 -2
  143. package/dist/scripts/wechat-rpa-download-candidates.mjs +0 -105
@@ -1,35 +1 @@
1
- // @arch docs/architecture/cli/native-session-fusion.md
2
- // @test src/__tests__/session-manager.test.ts
3
- export async function handleSessionRefresh(runtime, req) {
4
- const params = req.params;
5
- if (!params.sessionId) {
6
- runtime.client.sendRes({
7
- type: 'res',
8
- id: req.id,
9
- ok: false,
10
- error: 'sessionId is required',
11
- });
12
- return;
13
- }
14
- if (!runtime.nativeFusion) {
15
- runtime.client.sendRes({
16
- type: 'res',
17
- id: req.id,
18
- ok: true,
19
- payload: { scanned: false },
20
- });
21
- return;
22
- }
23
- await runtime.nativeFusion.scanNow();
24
- runtime.client.sendRes({
25
- type: 'res',
26
- id: req.id,
27
- ok: true,
28
- payload: {
29
- scanned: true,
30
- sessionId: params.sessionId,
31
- agentType: params.agentType ?? null,
32
- agentSessionId: params.agentSessionId ?? null,
33
- },
34
- });
35
- }
1
+ async function a(s,i){const e=i.params;if(!e.sessionId){s.client.sendRes({type:"res",id:i.id,ok:!1,error:"sessionId is required"});return}if(!s.nativeFusion){s.client.sendRes({type:"res",id:i.id,ok:!0,payload:{scanned:!1}});return}if(await s.nativeFusion.scanNow(),e.agentType==="codex"&&e.agentSessionId&&typeof s.nativeFusion.getCodexThreadActivity=="function"){const n=await s.nativeFusion.getCodexThreadActivity({sessionId:e.sessionId,threadId:e.agentSessionId,workDir:e.workDir});n!==void 0&&s.activityPublisher?.publish(e.sessionId,n)}s.client.sendRes({type:"res",id:i.id,ok:!0,payload:{scanned:!0,sessionId:e.sessionId,agentType:e.agentType??null,agentSessionId:e.agentSessionId??null}})}export{a as handleSessionRefresh};
@@ -1,121 +1 @@
1
- // @arch docs/features/skill-marketplace.md
2
- // @test src/__tests__/skill-registry.test.ts
3
- import { buildSkillUsePrompt, getInstalledSkill, installSkillFromUrl, listInstalledSkills, updateInstalledSkillSetup, } from '../../skills/registry.js';
4
- import { runSkillDoctor, runSkillSetup, summarizeSetupStatus } from '../../skills/setup.js';
5
- export async function handleSkillList(runtime, req) {
6
- runtime.client.sendRes({
7
- type: 'res',
8
- id: req.id,
9
- ok: true,
10
- payload: { skills: listInstalledSkills() },
11
- });
12
- }
13
- export async function handleSkillInstall(runtime, req) {
14
- const installUrl = typeof req.params.installUrl === 'string' ? req.params.installUrl : '';
15
- if (!installUrl) {
16
- runtime.client.sendRes({ type: 'res', id: req.id, ok: false, error: 'installUrl is required' });
17
- return;
18
- }
19
- const installed = await installSkillFromUrl(installUrl);
20
- runtime.client.sendRes({
21
- type: 'res',
22
- id: req.id,
23
- ok: true,
24
- payload: { skill: installed },
25
- });
26
- }
27
- export async function handleSkillDoctor(runtime, req) {
28
- const skillId = typeof req.params.skillId === 'string' ? req.params.skillId : '';
29
- const doctorId = typeof req.params.doctorId === 'string' ? req.params.doctorId : undefined;
30
- if (!skillId) {
31
- runtime.client.sendRes({ type: 'res', id: req.id, ok: false, error: 'skillId is required' });
32
- return;
33
- }
34
- if (!getInstalledSkill(skillId)) {
35
- runtime.client.sendRes({
36
- type: 'res',
37
- id: req.id,
38
- ok: false,
39
- error: `Skill not installed: ${skillId}`,
40
- });
41
- return;
42
- }
43
- const doctorResults = await runSkillDoctor(skillId, doctorId);
44
- const setupStatus = summarizeSetupStatus(doctorResults);
45
- updateInstalledSkillSetup(skillId, { setupStatus, doctorResults });
46
- runtime.client.sendRes({
47
- type: 'res',
48
- id: req.id,
49
- ok: true,
50
- payload: { skill: getInstalledSkill(skillId), setupStatus, doctorResults },
51
- });
52
- }
53
- export async function handleSkillSetup(runtime, req) {
54
- const skillId = typeof req.params.skillId === 'string' ? req.params.skillId : '';
55
- const repairActionId = typeof req.params.repairActionId === 'string' ? req.params.repairActionId : '';
56
- if (!skillId || !repairActionId) {
57
- runtime.client.sendRes({
58
- type: 'res',
59
- id: req.id,
60
- ok: false,
61
- error: 'skillId and repairActionId are required',
62
- });
63
- return;
64
- }
65
- if (!getInstalledSkill(skillId)) {
66
- runtime.client.sendRes({
67
- type: 'res',
68
- id: req.id,
69
- ok: false,
70
- error: `Skill not installed: ${skillId}`,
71
- });
72
- return;
73
- }
74
- try {
75
- const result = await runSkillSetup(skillId, repairActionId);
76
- updateInstalledSkillSetup(skillId, {
77
- setupStatus: result.status,
78
- doctorResults: result.doctorResults,
79
- });
80
- runtime.client.sendRes({
81
- type: 'res',
82
- id: req.id,
83
- ok: result.status === 'ready',
84
- payload: { skill: getInstalledSkill(skillId), ...result },
85
- error: result.status === 'ready' ? undefined : result.message,
86
- });
87
- }
88
- catch (err) {
89
- runtime.client.sendRes({
90
- type: 'res',
91
- id: req.id,
92
- ok: false,
93
- error: err instanceof Error ? err.message : String(err),
94
- });
95
- }
96
- }
97
- export async function handleSkillUse(runtime, req) {
98
- const skillId = typeof req.params.skillId === 'string' ? req.params.skillId : '';
99
- const workDir = typeof req.params.workDir === 'string' ? req.params.workDir : process.cwd();
100
- const attachments = Array.isArray(req.params.attachments)
101
- ? req.params.attachments
102
- .map((item) => {
103
- const record = item;
104
- const path = typeof record.path === 'string' ? record.path : '';
105
- const name = typeof record.name === 'string' ? record.name : '';
106
- const mimeType = typeof record.mimeType === 'string' ? record.mimeType : '';
107
- return path && name ? { path, name, mimeType } : null;
108
- })
109
- .filter((item) => item != null)
110
- : undefined;
111
- if (!skillId) {
112
- runtime.client.sendRes({ type: 'res', id: req.id, ok: false, error: 'skillId is required' });
113
- return;
114
- }
115
- runtime.client.sendRes({
116
- type: 'res',
117
- id: req.id,
118
- ok: true,
119
- payload: { prompt: buildSkillUsePrompt(skillId, workDir, attachments) },
120
- });
121
- }
1
+ import{buildSkillUsePrompt as k,getInstalledSkill as n,installSkillFromUrl as y,listInstalledSkills as u,updateInstalledSkillSetup as p}from"../../skills/registry.js";import{runSkillDoctor as f,runSkillSetup as m,summarizeSetupStatus as I}from"../../skills/setup.js";async function g(e,s){e.client.sendRes({type:"res",id:s.id,ok:!0,payload:{skills:u()}})}async function h(e,s){const t=typeof s.params.installUrl=="string"?s.params.installUrl:"";if(!t){e.client.sendRes({type:"res",id:s.id,ok:!1,error:"installUrl is required"});return}const i=await y(t);e.client.sendRes({type:"res",id:s.id,ok:!0,payload:{skill:i}})}async function w(e,s){const t=typeof s.params.skillId=="string"?s.params.skillId:"",i=typeof s.params.doctorId=="string"?s.params.doctorId:void 0;if(!t){e.client.sendRes({type:"res",id:s.id,ok:!1,error:"skillId is required"});return}if(!n(t)){e.client.sendRes({type:"res",id:s.id,ok:!1,error:`Skill not installed: ${t}`});return}const l=await f(t,i),r=I(l);p(t,{setupStatus:r,doctorResults:l}),e.client.sendRes({type:"res",id:s.id,ok:!0,payload:{skill:n(t),setupStatus:r,doctorResults:l}})}async function U(e,s){const t=typeof s.params.skillId=="string"?s.params.skillId:"",i=typeof s.params.repairActionId=="string"?s.params.repairActionId:"";if(!t||!i){e.client.sendRes({type:"res",id:s.id,ok:!1,error:"skillId and repairActionId are required"});return}if(!n(t)){e.client.sendRes({type:"res",id:s.id,ok:!1,error:`Skill not installed: ${t}`});return}try{const l=await m(t,i);p(t,{setupStatus:l.status,doctorResults:l.doctorResults}),e.client.sendRes({type:"res",id:s.id,ok:l.status==="ready",payload:{skill:n(t),...l},error:l.status==="ready"?void 0:l.message})}catch(l){e.client.sendRes({type:"res",id:s.id,ok:!1,error:l instanceof Error?l.message:String(l)})}}async function A(e,s){const t=typeof s.params.skillId=="string"?s.params.skillId:"",i=typeof s.params.workDir=="string"?s.params.workDir:process.cwd(),l=Array.isArray(s.params.attachments)?s.params.attachments.map(r=>{const a=r,o=typeof a.path=="string"?a.path:"",d=typeof a.name=="string"?a.name:"",c=typeof a.mimeType=="string"?a.mimeType:"";return o&&d?{path:o,name:d,mimeType:c}:null}).filter(r=>r!=null):void 0;if(!t){e.client.sendRes({type:"res",id:s.id,ok:!1,error:"skillId is required"});return}e.client.sendRes({type:"res",id:s.id,ok:!0,payload:{prompt:k(t,i,l)}})}export{w as handleSkillDoctor,h as handleSkillInstall,g as handleSkillList,U as handleSkillSetup,A as handleSkillUse};
@@ -1,60 +1 @@
1
- // @arch docs/architecture/cli/daemon.md#会话管理
2
- // @test src/__tests__/session-manager.test.ts
3
- import { createAgent } from '../../agents/adapter.js';
4
- import { buildManagedAgentEnv } from '../../agents/config-status.js';
5
- function cleanString(value) {
6
- return typeof value === 'string' ? value.trim() : '';
7
- }
8
- export async function handleSessionTitleSet(runtime, req) {
9
- const params = req.params;
10
- const sessionId = cleanString(params.sessionId);
11
- const title = cleanString(params.title);
12
- const agentSessionId = cleanString(params.agentSessionId);
13
- const agentType = cleanString(params.agentType);
14
- const workDir = cleanString(params.workDir) || process.cwd();
15
- if (!sessionId || !title) {
16
- runtime.client.sendRes({
17
- type: 'res',
18
- id: req.id,
19
- ok: false,
20
- error: 'sessionId and title are required',
21
- });
22
- return;
23
- }
24
- if (agentType !== 'codex' || !agentSessionId) {
25
- runtime.client.sendRes({
26
- type: 'res',
27
- id: req.id,
28
- ok: true,
29
- payload: { skipped: true },
30
- });
31
- return;
32
- }
33
- const active = runtime.sessions.get(sessionId);
34
- if (active?.agentType === 'codex' && active.adapter.setTitle) {
35
- await active.adapter.setTitle(agentSessionId, title, active.workDir || workDir);
36
- active.agentSessionId = agentSessionId;
37
- active.lastActiveAt = Date.now();
38
- runtime.client.sendRes({ type: 'res', id: req.id, ok: true, payload: { synced: true } });
39
- return;
40
- }
41
- const adapter = createAgent('codex');
42
- if (!adapter?.setTitle) {
43
- runtime.client.sendRes({
44
- type: 'res',
45
- id: req.id,
46
- ok: false,
47
- error: 'Codex adapter does not support title sync',
48
- });
49
- return;
50
- }
51
- adapter.configure?.({ sessionId, env: buildManagedAgentEnv('codex') });
52
- try {
53
- await adapter.start(sessionId, workDir, agentSessionId);
54
- await adapter.setTitle(agentSessionId, title, workDir);
55
- runtime.client.sendRes({ type: 'res', id: req.id, ok: true, payload: { synced: true } });
56
- }
57
- finally {
58
- await adapter.stop().catch(() => { });
59
- }
60
- }
1
+ import{createAgent as l}from"../../agents/adapter.js";import{buildManagedAgentEnv as y}from"../../agents/config-status.js";function a(e){return typeof e=="string"?e.trim():""}async function u(e,t){const n=t.params,r=a(n.sessionId),d=a(n.title),i=a(n.agentSessionId),p=a(n.agentType),c=a(n.workDir)||process.cwd();if(!r||!d){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"sessionId and title are required"});return}if(p!=="codex"||!i){e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{skipped:!0}});return}const s=e.sessions.get(r);if(s?.agentType==="codex"&&s.adapter.setTitle){await s.adapter.setTitle(i,d,s.workDir||c),s.agentSessionId=i,s.lastActiveAt=Date.now(),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{synced:!0}});return}const o=l("codex");if(!o?.setTitle){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Codex adapter does not support title sync"});return}o.configure?.({sessionId:r,env:y("codex")});try{await o.start(r,c,i),await o.setTitle(i,d,c),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{synced:!0}})}finally{await o.stop().catch(()=>{})}}export{u as handleSessionTitleSet};
@@ -0,0 +1,3 @@
1
+ import type { ReqFrame } from '@shennian/wire';
2
+ import type { SessionManagerRuntime } from '../types.js';
3
+ export declare function handleSessionToolDetail(runtime: SessionManagerRuntime, req: ReqFrame): Promise<void>;
@@ -0,0 +1 @@
1
+ import{readMessages as R}from"../store.js";const E=2e3,A=8e3,_=/(?:token|api[-_]?key|password|passwd|pwd|secret|authorization|cookie|credential|private[-_]?key)/i,b=/\b([A-Z0-9_]*(?:TOKEN|KEY|SECRET|PASSWORD|PASSWD|PWD|AUTH)[A-Z0-9_]*)=("[^"]*"|'[^']*'|[^\s]+)/gi,x=/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi,h=/\bsk-[A-Za-z0-9_-]{12,}\b/g;function m(e){return e&&typeof e=="object"&&!Array.isArray(e)?e:null}function d(e){if(!e)return null;if(typeof e=="string")try{return d(JSON.parse(e))}catch{return null}const t=m(e);if(!t)return null;const s=typeof t.runId=="string"?t.runId:void 0,o=typeof t.sourceSeq=="number"?t.sourceSeq:typeof t.seq=="number"?t.seq:void 0,n=typeof t.toolIndex=="number"?t.toolIndex:void 0;return{...s?{runId:s}:{},...o!=null?{sourceSeq:o}:{},...n!=null?{toolIndex:n}:{}}}function p(e){return/^agent-(.+)-\d+$/.exec(e)?.[1]??null}function I(e){const t=/^agent-.+-(\d+)$/.exec(e);return t?Number(t[1]):null}function y(e){const t=typeof e=="number"?e:Number(e);return!Number.isFinite(t)||t<=0?E:Math.min(Math.max(200,Math.floor(t)),A)}function c(e,t){const s=e.replace(b,"$1=[REDACTED]").replace(x,"Bearer [REDACTED]").replace(h,"sk-[REDACTED]");return s.length>t?`${s.slice(0,t)}\u2026`:s}function u(e,t,s=0){if(e==null)return e;if(typeof e=="string")return c(e,t);if(typeof e=="number"||typeof e=="boolean")return e;if(s>=4)return"[Truncated]";if(Array.isArray(e))return e.slice(0,20).map(o=>u(o,t,s+1));if(typeof e=="object"){const o={};for(const[n,r]of Object.entries(e).slice(0,50)){if(_.test(n)){o[n]="[REDACTED]";continue}o[n]=u(r,t,s+1)}return o}return String(e)}function D(e,t){const o=m(e)?.command;if(typeof o=="string")return c(o,t);if(Array.isArray(o)){const n=o.map(r=>String(r)).join(" ");return c(n,t)}}function T(e){const t=R(e),s=[];for(const o of t)if(!(o.role!=="agent"||!o.payload.trim().startsWith("{")))try{const n=JSON.parse(o.payload),r=n.type;if(r!=="tool"&&r!=="tool_use"&&r!=="tool_result")continue;const a=typeof n.name=="string"&&n.name.trim()?n.name.trim():"tool";s.push({id:o.id,ts:o.ts,name:a,args:n.args,result:n.result,status:r==="tool_result"||Object.prototype.hasOwnProperty.call(n,"result")?"completed":n.status==="completed"||n.status==="failed"||n.status==="running"?n.status:"running",detailRef:d(n.detailRef)})}catch{}return s}function k(e,t,s){let o=0;e.id===t.messageId&&(o+=100),t.name&&e.name===t.name&&(o+=20);const n=s?.runId??t.runId??p(t.messageId),r=s?.sourceSeq??t.sourceSeq??I(t.messageId)??void 0;return n&&(e.detailRef?.runId??p(e.id))===n&&(o+=20),r!=null&&(e.detailRef?.sourceSeq??I(e.id))===r&&(o+=80),e.args!=null&&(o+=5),o}function q(e){const t=d(e.detailRef)??d({runId:e.runId,sourceSeq:e.sourceSeq,toolIndex:e.toolIndex}),s=T(e.sessionId);return s.length===0?null:s.map(n=>({candidate:n,score:k(n,e,t)})).filter(n=>n.score>0).sort((n,r)=>r.score-n.score||r.candidate.ts-n.candidate.ts)[0]?.candidate??null}async function M(e,t){const s=t.params,o=typeof s.sessionId=="string"?s.sessionId:"",n=typeof s.messageId=="string"?s.messageId:"";if(!o||!n){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"sessionId and messageId are required"});return}const r={sessionId:o,messageId:n,...typeof s.name=="string"?{name:s.name}:{},...typeof s.toolIndex=="number"?{toolIndex:s.toolIndex}:{},...typeof s.runId=="string"?{runId:s.runId}:{},...typeof s.sourceSeq=="number"?{sourceSeq:s.sourceSeq}:{},...s.detailRef!==void 0?{detailRef:s.detailRef}:{},maxChars:y(s.maxChars)},a=y(r.maxChars),i=q(r);if(!i||i.args==null){const S={detailStatus:"not_found",messageId:n,toolIndex:r.toolIndex??0,name:r.name,resultOmitted:!0,source:"unavailable",reason:"tool detail is not available on this machine"};e.client.sendRes({type:"res",id:t.id,ok:!0,payload:S});return}const l=i.args==null?void 0:u(i.args,a),f=D(i.args,a),g={detailStatus:"available",messageId:n,toolIndex:r.toolIndex??0,name:i.name,status:i.status,...l!==void 0?{argsSummary:l}:{},...f?{command:f}:{},resultOmitted:!0,source:"local-session-store"};e.client.sendRes({type:"res",id:t.id,ok:!0,payload:g})}export{M as handleSessionToolDetail};
@@ -22,8 +22,11 @@ export declare class SessionManager {
22
22
  private pendingTransfers;
23
23
  private managerRuntime;
24
24
  private chatQueue;
25
+ private activityProbeTimer;
25
26
  constructor(client: CliRelayClient, nativeFusion?: NativeSessionFusionService | null, cliVersion?: string | undefined);
26
27
  private getRuntime;
28
+ private publishSessionActivity;
29
+ private publishManagedActivitySnapshots;
27
30
  private reloadCustomAgents;
28
31
  handleReq(req: ReqFrame): Promise<void>;
29
32
  private evictIdleSessions;
@@ -1,261 +1 @@
1
- // @arch docs/architecture/cli/daemon.md#会话管理
2
- // @test src/__tests__/session-store.test.ts
3
- // @test src/__tests__/model-switching.test.ts
4
- import { getRegisteredAgents, unregisterAgent } from '../agents/adapter.js';
5
- import { loadConfig } from '../config/index.js';
6
- import { handleUpgradeStart, handleUpgradeStatus } from '../commands/upgrade.js';
7
- import { handleAgentsRefresh, handleModelsRefresh } from './handlers/agents.js';
8
- import { handleAgentConfigClear, handleAgentConfigGet, handleAgentConfigTest, handleAgentConfigUpsert, } from './handlers/agent-config.js';
9
- import { handleChatAbort, handleChatSend } from './handlers/chat.js';
10
- import { handleSessionRefresh } from './handlers/session-refresh.js';
11
- import { handleSessionTitleSet } from './handlers/title.js';
12
- import { cleanupPendingTransfers, handleFsLs, handleFsRead, handleFsRename, handleFsWrite, handleFsTransfer, handleFsTransferAbort, handleFsTransferChunk, handleFsTransferFinish, handleFsTransferStart, handleFsExportMarkdownPdf, handleFsArchiveZip, } from './handlers/fs.js';
13
- import { handleSkillDoctor, handleSkillInstall, handleSkillList, handleSkillSetup, handleSkillUse, } from './handlers/skills.js';
14
- import { handleRegionProbe, handleRegionSwitch, handleUpgradeSetPolicy, } from './handlers/control.js';
15
- import { ManagerRuntimeService, setManagerRuntimeService } from '../manager/runtime.js';
16
- import { ChatQueueManager } from './queue.js';
17
- import { createAuthorizedFsRoot, resolveAuthorizedPath, resolveSessionWorkDir, } from '../fs/boundary.js';
18
- // Side-effect imports to register built-in agent adapters.
19
- import '../agents/claude.js';
20
- import '../agents/codex.js';
21
- import '../agents/gemini.js';
22
- import '../agents/cursor.js';
23
- // OpenClaw support is intentionally disabled.
24
- // import '../agents/openclaw.js'
25
- import '../agents/opencode.js';
26
- import '../agents/pi.js';
27
- import '../agents/manager.js';
28
- import { registerCustomAgent } from '../agents/custom.js';
29
- const MAX_SESSIONS = 50;
30
- export { resolveSessionWorkDir } from '../fs/boundary.js';
31
- export class SessionManager {
32
- client;
33
- nativeFusion;
34
- cliVersion;
35
- sessions = new Map();
36
- /** Track processed request IDs to deduplicate replayed offline messages */
37
- processedReqIds = new Set();
38
- /** Accumulate agent text per run for messageSummary on final event */
39
- runTextAcc = new Map();
40
- /** In-flight chunked uploads: transferId → metadata */
41
- pendingTransfers = new Map();
42
- managerRuntime;
43
- chatQueue;
44
- constructor(client, nativeFusion = null, cliVersion) {
45
- this.client = client;
46
- this.nativeFusion = nativeFusion;
47
- this.cliVersion = cliVersion;
48
- this.managerRuntime = new ManagerRuntimeService({
49
- getRuntime: () => this.getRuntime(),
50
- dispatchReq: (req) => this.handleReq(req),
51
- });
52
- this.chatQueue = new ChatQueueManager({
53
- getRuntime: () => this.getRuntime(),
54
- dispatchReq: (req) => this.handleReq(req),
55
- });
56
- setManagerRuntimeService(this.managerRuntime);
57
- void this.managerRuntime.start();
58
- this.reloadCustomAgents();
59
- }
60
- getRuntime() {
61
- return {
62
- client: this.client,
63
- pendingTransfers: this.pendingTransfers,
64
- processedReqIds: this.processedReqIds,
65
- reloadCustomAgents: () => this.reloadCustomAgents(),
66
- resolvePath: (pathValue) => this.resolvePath(pathValue),
67
- resolveAuthorizedPath: (pathValue, rootPath) => this.resolveAuthorizedPath(pathValue, rootPath),
68
- runTextAcc: this.runTextAcc,
69
- sessions: this.sessions,
70
- evictIdleSessions: () => this.evictIdleSessions(),
71
- nativeFusion: this.nativeFusion,
72
- managerRuntime: this.managerRuntime,
73
- chatQueue: this.chatQueue,
74
- };
75
- }
76
- reloadCustomAgents() {
77
- for (const agentType of getRegisteredAgents()) {
78
- if (agentType.startsWith('custom:')) {
79
- unregisterAgent(agentType);
80
- }
81
- }
82
- const config = loadConfig();
83
- for (const [name, entry] of Object.entries(config.customAgents ?? {})) {
84
- registerCustomAgent(name, entry);
85
- }
86
- }
87
- async handleReq(req) {
88
- const runtime = this.getRuntime();
89
- try {
90
- switch (req.method) {
91
- case 'chat.send':
92
- await handleChatSend(runtime, req);
93
- break;
94
- case 'chat.enqueue':
95
- await this.chatQueue.handleEnqueue(req);
96
- break;
97
- case 'chat.queue.get':
98
- await this.chatQueue.handleGet(req);
99
- break;
100
- case 'chat.queue.edit':
101
- await this.chatQueue.handleEdit(req);
102
- break;
103
- case 'chat.queue.delete':
104
- await this.chatQueue.handleDelete(req);
105
- break;
106
- case 'chat.abort':
107
- await handleChatAbort(runtime, req);
108
- break;
109
- case 'session.refresh':
110
- await handleSessionRefresh(runtime, req);
111
- break;
112
- case 'session.title.set':
113
- await handleSessionTitleSet(runtime, req);
114
- break;
115
- case 'fs.ls':
116
- await handleFsLs(runtime, req);
117
- break;
118
- case 'fs.read':
119
- await handleFsRead(runtime, req);
120
- break;
121
- case 'fs.write':
122
- await handleFsWrite(runtime, req);
123
- break;
124
- case 'fs.export.markdown-pdf':
125
- await handleFsExportMarkdownPdf(runtime, req);
126
- break;
127
- case 'fs.archive.zip':
128
- await handleFsArchiveZip(runtime, req);
129
- break;
130
- case 'fs.rename':
131
- await handleFsRename(runtime, req);
132
- break;
133
- case 'skill.list':
134
- await handleSkillList(runtime, req);
135
- break;
136
- case 'skill.install':
137
- await handleSkillInstall(runtime, req);
138
- break;
139
- case 'skill.doctor':
140
- await handleSkillDoctor(runtime, req);
141
- break;
142
- case 'skill.setup':
143
- await handleSkillSetup(runtime, req);
144
- break;
145
- case 'skill.use':
146
- await handleSkillUse(runtime, req);
147
- break;
148
- case 'fs.transfer':
149
- await handleFsTransfer(runtime, req);
150
- break;
151
- case 'fs.transfer.start':
152
- await handleFsTransferStart(runtime, req);
153
- break;
154
- case 'fs.transfer.chunk':
155
- await handleFsTransferChunk(runtime, req);
156
- break;
157
- case 'fs.transfer.finish':
158
- await handleFsTransferFinish(runtime, req);
159
- break;
160
- case 'fs.transfer.abort':
161
- await handleFsTransferAbort(runtime, req);
162
- break;
163
- case 'region.probe':
164
- await handleRegionProbe(runtime, req);
165
- break;
166
- case 'region.switch':
167
- await handleRegionSwitch(runtime, req);
168
- break;
169
- case 'upgrade.start':
170
- await handleUpgradeStart(this.client, req.id, req.params.version, {
171
- currentVersion: this.cliVersion,
172
- });
173
- break;
174
- case 'upgrade.status':
175
- await handleUpgradeStatus(this.client, req.id, { currentVersion: this.cliVersion });
176
- break;
177
- case 'upgrade.set-policy':
178
- await handleUpgradeSetPolicy(runtime, req);
179
- break;
180
- case 'agents.refresh':
181
- await handleAgentsRefresh(runtime, req);
182
- break;
183
- case 'models.refresh':
184
- await handleModelsRefresh(runtime, req);
185
- break;
186
- case 'agent.config.get':
187
- await handleAgentConfigGet(runtime, req);
188
- break;
189
- case 'agent.config.upsert':
190
- await handleAgentConfigUpsert(runtime, req);
191
- break;
192
- case 'agent.config.clear':
193
- await handleAgentConfigClear(runtime, req);
194
- break;
195
- case 'agent.config.test':
196
- await handleAgentConfigTest(runtime, req);
197
- break;
198
- case 'manager.channel.get':
199
- case 'manager.channel.upsert':
200
- await runtime.managerRuntime?.handleAppReq(req);
201
- break;
202
- default:
203
- this.client.sendRes({
204
- type: 'res',
205
- id: req.id,
206
- ok: false,
207
- error: `Unknown method: ${req.method}`,
208
- });
209
- }
210
- }
211
- catch (err) {
212
- this.client.sendRes({
213
- type: 'res',
214
- id: req.id,
215
- ok: false,
216
- error: err instanceof Error ? err.message : String(err),
217
- });
218
- }
219
- }
220
- // ─── Session eviction ──────────────────────────────────────────────────────
221
- evictIdleSessions() {
222
- if (this.sessions.size < MAX_SESSIONS)
223
- return;
224
- let oldest = null;
225
- for (const [key, session] of this.sessions) {
226
- if (!oldest || session.lastActiveAt < oldest.session.lastActiveAt) {
227
- oldest = { key, session };
228
- }
229
- }
230
- if (oldest) {
231
- if (oldest.session.heartbeatTimer) {
232
- clearInterval(oldest.session.heartbeatTimer);
233
- oldest.session.heartbeatTimer = null;
234
- }
235
- oldest.session.adapter.removeAllListeners();
236
- oldest.session.adapter.stop().catch(() => { });
237
- this.sessions.delete(oldest.key);
238
- }
239
- }
240
- // ─── Helpers ──────────────────────────────────────────────────────────────
241
- resolvePath(p) {
242
- return resolveSessionWorkDir(p);
243
- }
244
- resolveAuthorizedPath(p, rootPath) {
245
- return resolveAuthorizedPath(p, createAuthorizedFsRoot(rootPath));
246
- }
247
- cleanup() {
248
- for (const [, session] of this.sessions) {
249
- if (session.heartbeatTimer) {
250
- clearInterval(session.heartbeatTimer);
251
- session.heartbeatTimer = null;
252
- }
253
- session.adapter.stop().catch(() => { });
254
- }
255
- this.sessions.clear();
256
- this.runTextAcc.clear();
257
- cleanupPendingTransfers(this.getRuntime());
258
- void this.managerRuntime.stop();
259
- setManagerRuntimeService(null);
260
- }
261
- }
1
+ import{getRegisteredAgents as r,unregisterAgent as o}from"../agents/adapter.js";import{loadConfig as h}from"../config/index.js";import{handleUpgradeStart as c,handleUpgradeStatus as l}from"../commands/upgrade.js";import{handleAgentsRefresh as u,handleModelsRefresh as d}from"./handlers/agents.js";import{handleAgentConfigClear as m,handleAgentConfigGet as p,handleAgentConfigTest as f,handleAgentConfigUpsert as g}from"./handlers/agent-config.js";import{handleChatAbort as b,handleChatSend as k}from"./handlers/chat.js";import{handleSessionRefresh as w}from"./handlers/session-refresh.js";import{handleSessionToolDetail as v}from"./handlers/tool-detail.js";import{handleSessionTitleSet as S}from"./handlers/title.js";import{cleanupPendingTransfers as A,handleFsLs as R,handleFsRead as y,handleFsRename as T,handleFsWrite as I,handleFsTransfer as P,handleFsTransferAbort as F,handleFsTransferChunk as C,handleFsTransferFinish as M,handleFsTransferStart as Q,handleFsExportMarkdownPdf as x,handleFsArchiveZip as D}from"./handlers/fs.js";import{handleSkillDoctor as z,handleSkillInstall as E,handleSkillList as U,handleSkillSetup as V,handleSkillUse as O}from"./handlers/skills.js";import{handleRegionProbe as W,handleRegionSwitch as L,handleUpgradeSetPolicy as $}from"./handlers/control.js";import{ManagerRuntimeService as G,setManagerRuntimeService as n}from"../manager/runtime.js";import{ChatQueueManager as _}from"./queue.js";import{createAuthorizedFsRoot as j,resolveAuthorizedPath as N,resolveSessionWorkDir as X}from"../fs/boundary.js";import"../agents/claude.js";import"../agents/codex.js";import"../agents/gemini.js";import"../agents/cursor.js";import"../agents/opencode.js";import"../agents/pi.js";import"../agents/manager.js";import{registerCustomAgent as Z}from"../agents/custom.js";const B=50;import{resolveSessionWorkDir as Ae}from"../fs/boundary.js";class we{client;nativeFusion;cliVersion;sessions=new Map;processedReqIds=new Set;runTextAcc=new Map;pendingTransfers=new Map;managerRuntime;chatQueue;activityProbeTimer=null;constructor(e,t=null,a){this.client=e,this.nativeFusion=t,this.cliVersion=a,this.managerRuntime=new G({getRuntime:()=>this.getRuntime(),dispatchReq:s=>this.handleReq(s)}),this.chatQueue=new _({getRuntime:()=>this.getRuntime(),dispatchReq:s=>this.handleReq(s)}),n(this.managerRuntime),this.managerRuntime.start(),this.reloadCustomAgents(),this.activityProbeTimer=setInterval(()=>{this.publishManagedActivitySnapshots().catch(s=>{console.error("[session.activity] managed probe failed",s)})},15e3),this.activityProbeTimer.unref?.()}getRuntime(){return{client:this.client,pendingTransfers:this.pendingTransfers,processedReqIds:this.processedReqIds,reloadCustomAgents:()=>this.reloadCustomAgents(),resolvePath:e=>this.resolvePath(e),resolveAuthorizedPath:(e,t)=>this.resolveAuthorizedPath(e,t),runTextAcc:this.runTextAcc,sessions:this.sessions,evictIdleSessions:()=>this.evictIdleSessions(),nativeFusion:this.nativeFusion,managerRuntime:this.managerRuntime,chatQueue:this.chatQueue,activityPublisher:{publish:(e,t)=>this.publishSessionActivity(e,t)}}}publishSessionActivity(e,t){this.client.sendEvent({type:"event",event:"session.activity",payload:{sessionId:e,activity:t}})}async publishManagedActivitySnapshots(){for(const[e,t]of this.sessions.entries()){if(!t.currentRunId||!t.adapter.getStatus)continue;const a=await t.adapter.getStatus().catch(()=>null);if(!a?.active||!a.runPhase)continue;const s=new Date().toISOString();this.publishSessionActivity(e,{sessionId:e,runId:a.runId||t.currentRunId,runPhase:a.runPhase,startedAt:new Date(t.lastActiveAt).toISOString(),updatedAt:s,canStop:a.canStop??!0});const i=t.heartbeatSeq++;this.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:e,runId:a.runId||t.currentRunId,seq:i,runPhase:a.runPhase,canStop:a.canStop??!0},seq:i,id:`agent-status-${a.runId||t.currentRunId}-${Date.now()}`})}}reloadCustomAgents(){for(const t of r())t.startsWith("custom:")&&o(t);const e=h();for(const[t,a]of Object.entries(e.customAgents??{}))Z(t,a)}async handleReq(e){const t=this.getRuntime();try{switch(e.method){case"chat.send":await k(t,e);break;case"chat.enqueue":await this.chatQueue.handleEnqueue(e);break;case"chat.queue.get":await this.chatQueue.handleGet(e);break;case"chat.queue.edit":await this.chatQueue.handleEdit(e);break;case"chat.queue.delete":await this.chatQueue.handleDelete(e);break;case"chat.abort":await b(t,e);break;case"session.refresh":await w(t,e);break;case"session.tool.detail":await v(t,e);break;case"session.title.set":await S(t,e);break;case"fs.ls":await R(t,e);break;case"fs.read":await y(t,e);break;case"fs.write":await I(t,e);break;case"fs.export.markdown-pdf":await x(t,e);break;case"fs.archive.zip":await D(t,e);break;case"fs.rename":await T(t,e);break;case"skill.list":await U(t,e);break;case"skill.install":await E(t,e);break;case"skill.doctor":await z(t,e);break;case"skill.setup":await V(t,e);break;case"skill.use":await O(t,e);break;case"fs.transfer":await P(t,e);break;case"fs.transfer.start":await Q(t,e);break;case"fs.transfer.chunk":await C(t,e);break;case"fs.transfer.finish":await M(t,e);break;case"fs.transfer.abort":await F(t,e);break;case"region.probe":await W(t,e);break;case"region.switch":await L(t,e);break;case"upgrade.start":await c(this.client,e.id,e.params.version,{currentVersion:this.cliVersion});break;case"upgrade.status":await l(this.client,e.id,{currentVersion:this.cliVersion});break;case"upgrade.set-policy":await $(t,e);break;case"agents.refresh":await u(t,e);break;case"models.refresh":await d(t,e);break;case"agent.config.get":await p(t,e);break;case"agent.config.upsert":await g(t,e);break;case"agent.config.clear":await m(t,e);break;case"agent.config.test":await f(t,e);break;case"manager.channel.get":case"manager.channel.upsert":await t.managerRuntime?.handleAppReq(e);break;default:this.client.sendRes({type:"res",id:e.id,ok:!1,error:`Unknown method: ${e.method}`})}}catch(a){this.client.sendRes({type:"res",id:e.id,ok:!1,error:a instanceof Error?a.message:String(a)})}}evictIdleSessions(){if(this.sessions.size<B)return;let e=null;for(const[t,a]of this.sessions)(!e||a.lastActiveAt<e.session.lastActiveAt)&&(e={key:t,session:a});e&&(e.session.heartbeatTimer&&(clearInterval(e.session.heartbeatTimer),e.session.heartbeatTimer=null),e.session.adapter.removeAllListeners(),e.session.adapter.stop().catch(()=>{}),this.sessions.delete(e.key))}resolvePath(e){return X(e)}resolveAuthorizedPath(e,t){return N(e,j(t))}cleanup(){this.activityProbeTimer&&(clearInterval(this.activityProbeTimer),this.activityProbeTimer=null);for(const[,e]of this.sessions)e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),e.adapter.stop().catch(()=>{});this.sessions.clear(),this.runTextAcc.clear(),A(this.getRuntime()),this.managerRuntime.stop(),n(null)}}export{we as SessionManager,Ae as resolveSessionWorkDir};
@@ -1,54 +1 @@
1
- // @arch docs/features/manager-agent.md
2
- // @test src/__tests__/manager-runtime.test.ts
3
- import fs from 'node:fs';
4
- import path from 'node:path';
5
- import { resolveShennianPath } from '../config/index.js';
6
- const PROJECTION_FILE = resolveShennianPath('session-list-projection.json');
7
- function emptyProjection() {
8
- return { updatedAt: new Date(0).toISOString(), sessions: {} };
9
- }
10
- function readProjectionFile() {
11
- try {
12
- const parsed = JSON.parse(fs.readFileSync(PROJECTION_FILE, 'utf-8'));
13
- return {
14
- updatedAt: parsed.updatedAt ?? new Date(0).toISOString(),
15
- sessions: parsed.sessions ?? {},
16
- };
17
- }
18
- catch {
19
- return emptyProjection();
20
- }
21
- }
22
- function writeProjectionFile(file) {
23
- fs.mkdirSync(path.dirname(PROJECTION_FILE), { recursive: true });
24
- fs.writeFileSync(PROJECTION_FILE, JSON.stringify(file, null, 2));
25
- }
26
- export function listProjectedSessions() {
27
- return Object.values(readProjectionFile().sessions);
28
- }
29
- export function mergeProjectedSessions(sessions) {
30
- if (!sessions?.length)
31
- return;
32
- try {
33
- const file = readProjectionFile();
34
- for (const session of sessions) {
35
- if (!session?.id)
36
- continue;
37
- if (session.deletedAt) {
38
- delete file.sessions[session.id];
39
- continue;
40
- }
41
- const existing = file.sessions[session.id];
42
- const existingMs = Date.parse(existing?.updatedAt ?? existing?.lastActivityAt ?? '');
43
- const incomingMs = Date.parse(session.updatedAt ?? session.lastActivityAt ?? '');
44
- if (!existing || !Number.isFinite(existingMs) || !Number.isFinite(incomingMs) || incomingMs >= existingMs) {
45
- file.sessions[session.id] = session;
46
- }
47
- }
48
- file.updatedAt = new Date().toISOString();
49
- writeProjectionFile(file);
50
- }
51
- catch {
52
- // Projection is a local cache. Losing it should not break chat delivery.
53
- }
54
- }
1
+ import s from"node:fs";import d from"node:path";import{resolveShennianPath as u}from"../config/index.js";const o=u("session-list-projection.json");function f(){return{updatedAt:new Date(0).toISOString(),sessions:{}}}function a(){try{const e=JSON.parse(s.readFileSync(o,"utf-8"));return{updatedAt:e.updatedAt??new Date(0).toISOString(),sessions:e.sessions??{}}}catch{return f()}}function p(e){s.mkdirSync(d.dirname(o),{recursive:!0}),s.writeFileSync(o,JSON.stringify(e,null,2))}function A(){return Object.values(a().sessions)}function g(e){if(e?.length)try{const i=a();for(const t of e){if(!t?.id)continue;if(t.deletedAt){delete i.sessions[t.id];continue}const n=i.sessions[t.id],r=Date.parse(n?.updatedAt??n?.lastActivityAt??""),c=Date.parse(t.updatedAt??t.lastActivityAt??"");(!n||!Number.isFinite(r)||!Number.isFinite(c)||c>=r)&&(i.sessions[t.id]=t)}i.updatedAt=new Date().toISOString(),p(i)}catch{}}export{A as listProjectedSessions,g as mergeProjectedSessions};