grix-connector 4.9.2-beta.2 → 4.9.2
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/adapter/claude/claude-adapter.js +21 -21
- package/dist/adapter/claude/claude-bridge-server.js +1 -0
- package/dist/adapter/claude/claude-tools.js +1 -0
- package/dist/adapter/claude/claude-worker-client.js +1 -0
- package/dist/adapter/claude/mcp-http-launcher.js +2 -0
- package/dist/adapter/claude/result-timeout.js +1 -0
- package/dist/adapter/deepseek/deepseek-adapter.js +6 -0
- package/dist/adapter/deepseek/index.js +1 -0
- package/dist/adapter/qwen/index.js +1 -0
- package/dist/adapter/qwen/qwen-adapter.js +4 -0
- package/dist/aibot/client.js +1 -0
- package/dist/aibot/index.js +1 -0
- package/dist/aibot/types.js +0 -0
- package/dist/assets/dsh-bridge/grix-dsh-bridge-4.9.2.tgz +0 -0
- package/dist/assets/dsh-bridge/manifest.json +6 -6
- package/dist/bridge/bridge.js +2 -8
- package/dist/bridge/send-controller.js +1 -1
- package/dist/core/access/allowlist-gate.js +1 -0
- package/dist/core/access/allowlist-store.js +1 -0
- package/dist/core/access/index.js +1 -0
- package/dist/core/file-ops/handler.js +1 -0
- package/dist/core/file-ops/list-files.js +1 -0
- package/dist/core/file-ops/types.js +0 -0
- package/dist/grix.js +8 -8
- package/dist/launcher.mjs +1 -103
- package/dist/log.js +3 -0
- package/dist/main.js +31 -0
- package/dist/mcp/stream-http/config.js +1 -0
- package/dist/mcp/stream-http/connection-binding.js +1 -0
- package/dist/mcp/stream-http/event-tool-executor.js +1 -0
- package/dist/mcp/stream-http/gateway.js +1 -0
- package/dist/mcp/stream-http/index.js +1 -0
- package/dist/mcp/stream-http/security.js +1 -0
- package/dist/mcp/stream-http/session-manager.js +1 -0
- package/dist/mcp/stream-http/tool-executor.js +1 -0
- package/dist/mcp/stream-http/tool-registry.js +1 -0
- package/dist/mcp/stream-http/tool-schemas.js +1 -0
- package/dist/runtime/daemon-lock.js +2 -2
- package/dist/service/platform-adapter.js +29 -29
- package/dist/service/process-control.js +1 -1
- package/dist/session/index.js +1 -0
- package/dist/session/manager.js +1 -0
- package/dist/transport/index.js +1 -0
- package/dist/transport/json-rpc.js +3 -0
- package/openclaw-plugin/index.js +1 -1
- package/package.json +1 -1
- package/scripts/launcher.mjs +1 -103
- package/dist/assets/dsh-bridge/grix-dsh-bridge-4.9.2-beta.2.tgz +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import{existsSync as
|
|
1
|
+
import{existsSync as b,readFileSync as G}from"node:fs";import{mkdir as k,readdir as F,readFile as P,rm as y,stat as tt,writeFile as $}from"node:fs/promises";import m from"node:os";import l from"node:path";import{GRIX_HOME_ENV as C}from"../core/config/paths.js";import{isProcessRunning as I,runCommand as f,spawnDetached as _,isWindowsElevated as et,killProcessesByCommandLine as H}from"./process-control.js";import{getServicePrefix as A,parseConfigDirFromPlistXML as nt,parseConfigDirFromSystemdUnit as rt,resolveBareDaemonMarkerPath as x,resolveLinuxUserUnitPath as it,resolveMacOSLaunchAgentPath as at}from"./service-paths.js";function g(t){return String(t??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function R(t){return`'${String(t??"").replace(/'/g,"'\\''")}'`}function X(t){return String(t??"").replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\n/g,"\\n").replace(/\r/g,"\\r")}function V(t){return`"${X(t).replace(/%/g,"%%")}"`}function O(t){return String(t??"").replace(/%/g,"%%")}function ot(t){return`"${X(t).replace(/\$/g,()=>"$$").replace(/%/g,"%%")}"`}function j(t=process.env){const e=[];for(const n of["GRIX_HEALTH_PORT","GRIX_ADMIN_PORT"]){const r=String(t[n]??"").trim();/^\d{2,5}$/.test(r)&&e.push([n,r])}return e}function Q(t){return[t.nodePath,t.cliPath,...t.configDir?["--config-dir",t.configDir]:[]]}function st(t){const e=Q(t);return`<?xml version="1.0" encoding="UTF-8"?>
|
|
2
2
|
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
3
|
<plist version="1.0">
|
|
4
4
|
<dict>
|
|
5
5
|
<key>Label</key>
|
|
6
|
-
<string>${
|
|
6
|
+
<string>${g(t.serviceID)}</string>
|
|
7
7
|
<key>ProgramArguments</key>
|
|
8
8
|
<array>
|
|
9
|
-
${
|
|
9
|
+
${e.map(n=>` <string>${g(n)}</string>`).join(`
|
|
10
10
|
`)}
|
|
11
11
|
</array>
|
|
12
12
|
<key>RunAtLoad</key>
|
|
@@ -17,33 +17,33 @@ ${n.map(e=>` <string>${w(e)}</string>`).join(`
|
|
|
17
17
|
<key>ThrottleInterval</key>
|
|
18
18
|
<integer>30</integer>
|
|
19
19
|
<key>WorkingDirectory</key>
|
|
20
|
-
<string>${
|
|
20
|
+
<string>${g(l.dirname(t.cliPath))}</string>
|
|
21
21
|
<key>EnvironmentVariables</key>
|
|
22
22
|
<dict>
|
|
23
|
-
<key>${
|
|
24
|
-
<string>${
|
|
25
|
-
${j().map(([
|
|
23
|
+
<key>${C}</key>
|
|
24
|
+
<string>${g(t.rootDir)}</string>
|
|
25
|
+
${j().map(([n,r])=>` <key>${n}</key>
|
|
26
26
|
<string>${r}</string>
|
|
27
27
|
`).join("")}${t.environmentPath?` <key>PATH</key>
|
|
28
|
-
<string>${
|
|
28
|
+
<string>${g(t.environmentPath)}</string>
|
|
29
29
|
`:""} </dict>
|
|
30
30
|
<key>StandardOutPath</key>
|
|
31
|
-
<string>${
|
|
31
|
+
<string>${g(t.stdoutPath)}</string>
|
|
32
32
|
<key>StandardErrorPath</key>
|
|
33
|
-
<string>${
|
|
33
|
+
<string>${g(t.stderrPath)}</string>
|
|
34
34
|
</dict>
|
|
35
35
|
</plist>
|
|
36
|
-
`}function
|
|
36
|
+
`}function ct(t){const e=Q(t).map(n=>ot(n)).join(" ");return`[Unit]
|
|
37
37
|
Description=grix-connector daemon (${t.serviceID})
|
|
38
38
|
After=network.target
|
|
39
39
|
|
|
40
40
|
[Service]
|
|
41
41
|
Type=simple
|
|
42
|
-
ExecStart=${
|
|
43
|
-
WorkingDirectory=${O(
|
|
44
|
-
Environment=${
|
|
45
|
-
${j().map(([
|
|
46
|
-
`).join("")}${t.environmentPath?`Environment=${
|
|
42
|
+
ExecStart=${e}
|
|
43
|
+
WorkingDirectory=${O(l.dirname(t.cliPath))}
|
|
44
|
+
Environment=${V(`${C}=${t.rootDir}`)}
|
|
45
|
+
${j().map(([n,r])=>`Environment=${n}=${r}
|
|
46
|
+
`).join("")}${t.environmentPath?`Environment=${V(`PATH=${t.environmentPath}`)}
|
|
47
47
|
`:""}
|
|
48
48
|
Restart=always
|
|
49
49
|
RestartSec=30
|
|
@@ -52,8 +52,8 @@ StandardError=append:${O(t.stderrPath)}
|
|
|
52
52
|
|
|
53
53
|
[Install]
|
|
54
54
|
WantedBy=default.target
|
|
55
|
-
`}function
|
|
56
|
-
`);return{path:
|
|
55
|
+
`}function S(t,e){const n=t.replace(/[^a-zA-Z0-9._-]/g,"_");return l.join(e,".grix",`${n}-wrapper.vbs`)}function M(t,e){const n=t.replace(/[^a-zA-Z0-9._-]/g,"_"),r=l.join(e,"AppData","Roaming","Microsoft","Windows","Start Menu","Programs","Startup");return l.join(r,`${n}.lnk`)}function p(t){return String(t??"").replace(/"/g,'""')}function D(t){return`'${String(t??"").replace(/'/g,"''")}'`}function lt(t,e){const n=l.join(l.dirname(t),l.basename(e).replace(/\.lnk$/,"-create.vbs")),r=["Option Explicit","Dim shell, shortcut",'Set shell = CreateObject("WScript.Shell")',`Set shortcut = shell.CreateShortcut("${p(e)}")`,'shortcut.TargetPath = "wscript.exe"',`shortcut.Arguments = "//B //NoLogo \\"${p(t)}\\""`,"shortcut.WindowStyle = 7","shortcut.Save",""].join(`\r
|
|
56
|
+
`);return{path:n,content:r}}async function ut(t){const{wrapperPath:e,shortcutPath:n,runCommand:r}=t,i=lt(e,n);for(let a=1;a<=3;a+=1){try{await $(i.path,i.content,"utf8"),await r("wscript.exe",[i.path,"//B","//NoLogo"])}catch{}finally{await y(i.path,{force:!0}).catch(()=>{})}if(b(n))return!0}return console.warn(`Warning: failed to create Windows logon-autostart shortcut at ${n}. The daemon will NOT start automatically after the next reboot/logon. Check Controlled Folder Access / antivirus blocking writes to the Startup folder, or create the shortcut manually.`),!1}async function dt(t){try{const e=await t("whoami",["/user","/fo","csv","/nh"],{allowFailure:!0}),n=String(e?.stdout??"").match(/"(S-1-[0-9-]+)"/);if(n)return n[1]}catch{}return null}function mt(t){const e=`//B //NoLogo "${t.wrapperPath}"`;return`<?xml version="1.0" encoding="UTF-16"?>
|
|
57
57
|
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
|
58
58
|
<RegistrationInfo>
|
|
59
59
|
<Description>grix-connector daemon autostart</Description>
|
|
@@ -61,12 +61,12 @@ WantedBy=default.target
|
|
|
61
61
|
<Triggers>
|
|
62
62
|
<LogonTrigger>
|
|
63
63
|
<Enabled>true</Enabled>
|
|
64
|
-
<UserId>${
|
|
64
|
+
<UserId>${g(t.userId)}</UserId>
|
|
65
65
|
</LogonTrigger>
|
|
66
66
|
</Triggers>
|
|
67
67
|
<Principals>
|
|
68
68
|
<Principal id="Author">
|
|
69
|
-
<UserId>${
|
|
69
|
+
<UserId>${g(t.userId)}</UserId>
|
|
70
70
|
<LogonType>InteractiveToken</LogonType>
|
|
71
71
|
<RunLevel>LeastPrivilege</RunLevel>
|
|
72
72
|
</Principal>
|
|
@@ -92,17 +92,17 @@ WantedBy=default.target
|
|
|
92
92
|
<Actions Context="Author">
|
|
93
93
|
<Exec>
|
|
94
94
|
<Command>wscript.exe</Command>
|
|
95
|
-
<Arguments>${
|
|
95
|
+
<Arguments>${g(e)}</Arguments>
|
|
96
96
|
</Exec>
|
|
97
97
|
</Actions>
|
|
98
98
|
</Task>
|
|
99
|
-
`}async function
|
|
100
|
-
`),i=Buffer.from(r,"utf16le").toString("base64"),
|
|
101
|
-
`)}function
|
|
102
|
-
${
|
|
99
|
+
`}async function ft(t){const{serviceID:e,wrapperPath:n,runCommand:r}=t,i=await dt(r)??m.userInfo().username,a=l.join(l.dirname(n),`${e}-task.xml`);try{const o=mt({userId:i,wrapperPath:n});return await $(a,"\uFEFF"+o,"utf16le"),await r("schtasks",["/Create","/TN",e,"/XML",a,"/F"]),!0}catch{return!1}finally{await y(a,{force:!0}).catch(()=>{})}}const pt=3,ht="grix-wrapper-template:";function gt(t){const e=l.join(t.rootDir,"data","upgrade-wrapper-child.json"),n=[`"${t.cliPath}"`,...t.configDir?["--config-dir",`"${t.configDir}"`]:[]],r=["$ErrorActionPreference = 'Stop'",`$recordPath = ${D(e)}`,"$exitCode = 1","try {"," [IO.Directory]::CreateDirectory([IO.Path]::GetDirectoryName($recordPath)) | Out-Null",` $p = Start-Process -FilePath ${D(t.nodePath)} -ArgumentList @(${n.map(D).join(", ")}) -WindowStyle Hidden -PassThru`,` $record = @{ pid = $p.Id; launched_at = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds(); boot_time = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() - [Environment]::TickCount64; cli_path = ${D(t.cliPath)} }`," $utf8NoBom = New-Object System.Text.UTF8Encoding($false)"," try {"," [IO.File]::WriteAllText($recordPath, ($record | ConvertTo-Json -Compress), $utf8NoBom)"," } catch {",' & "$env:SystemRoot\\System32\\taskkill.exe" /PID $p.Id /T /F 2>$null | Out-Null'," $p.WaitForExit()"," throw"," }"," $p.WaitForExit()"," $exitCode = $p.ExitCode","} finally {"," Remove-Item -LiteralPath $recordPath -Force -ErrorAction SilentlyContinue","}","exit $exitCode"].join(`
|
|
100
|
+
`),i=Buffer.from(r,"utf16le").toString("base64"),c=`"${l.join(process.env.SystemRoot||"C:\\Windows","System32","WindowsPowerShell","v1.0","powershell.exe")}" -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand ${i}`,o=l.join(t.rootDir,"bin","upgrade-guardian.mjs"),s=l.join(t.rootDir,"bin","upgrade-guardian.fallback.mjs"),u=l.join(t.rootDir,"data","upgrade-wrapper.pause.json"),d=l.join(t.rootDir,"data","upgrade-pending.json"),h=[`"${p(t.nodePath)}"`,`"${p(o)}"`,"activate","--root",`"${p(t.rootDir)}"`].join(" "),w=`${h} --wrapper-handoff`,W=[`"${p(t.nodePath)}"`,`"${p(s)}"`,"activate","--root",`"${p(t.rootDir)}"`].join(" "),T=`${W} --wrapper-handoff`;return[`' ${ht} ${pt}`,"Option Explicit","Dim shell, processEnv, fso, command, guardianCommand, handoffGuardianCommand, guardianFallbackCommand, handoffGuardianFallbackCommand, guardianPath, guardianFallbackPath, maintenanceFile, pendingFile, delayMs, rapidCount, guardianExit",`command = "${p(c)}"`,`guardianCommand = "${p(h)}"`,`handoffGuardianCommand = "${p(w)}"`,`guardianFallbackCommand = "${p(W)}"`,`handoffGuardianFallbackCommand = "${p(T)}"`,`guardianPath = "${p(o)}"`,`guardianFallbackPath = "${p(s)}"`,`maintenanceFile = "${p(u)}"`,`pendingFile = "${p(d)}"`,'Set shell = CreateObject("WScript.Shell")','Set processEnv = shell.Environment("Process")','Set fso = CreateObject("Scripting.FileSystemObject")',`processEnv("${C}") = "${p(t.rootDir)}"`,'processEnv("GRIX_WRAPPER_SUPERVISED") = "1"',...j().map(([Z,Y])=>`processEnv("${Z}") = "${Y}"`),"delayMs = 5000","rapidCount = 0","Do"," Do While fso.FileExists(maintenanceFile)"," If fso.FileExists(guardianPath) Then"," guardianExit = shell.Run(guardianCommand, 0, True)"," ElseIf fso.FileExists(guardianFallbackPath) Then"," guardianExit = shell.Run(guardianFallbackCommand, 0, True)"," Else"," On Error Resume Next"," fso.DeleteFile maintenanceFile, True"," On Error GoTo 0"," WScript.Sleep 1000"," End If"," If fso.FileExists(maintenanceFile) Then WScript.Sleep 3000"," Loop"," If fso.FileExists(pendingFile) Then"," If fso.FileExists(guardianPath) Then"," guardianExit = shell.Run(guardianCommand, 0, True)"," ElseIf fso.FileExists(guardianFallbackPath) Then"," guardianExit = shell.Run(guardianFallbackCommand, 0, True)"," End If"," End If"," shell.Run command, 0, True"," If fso.FileExists(pendingFile) Then"," If fso.FileExists(guardianPath) Then"," guardianExit = shell.Run(handoffGuardianCommand, 0, True)"," ElseIf fso.FileExists(guardianFallbackPath) Then"," guardianExit = shell.Run(handoffGuardianFallbackCommand, 0, True)"," End If"," End If"," If fso.FileExists(pendingFile) And Not PendingIsTerminal() Then"," rapidCount = 0"," WScript.Sleep delayMs"," ElseIf fso.FileExists(maintenanceFile) Then"," rapidCount = 0"," Else"," rapidCount = rapidCount + 1"," If rapidCount >= 10 Then"," WScript.Sleep 300000"," rapidCount = 0"," Else"," WScript.Sleep delayMs"," End If"," End If","Loop","","Function PendingIsTerminal()"," Dim stream, text"," PendingIsTerminal = False"," On Error Resume Next"," Set stream = fso.OpenTextFile(pendingFile, 1)"," If Err.Number <> 0 Then"," Err.Clear"," On Error GoTo 0"," Exit Function"," End If"," text = stream.ReadAll"," stream.Close"," If Err.Number <> 0 Then"," Err.Clear"," On Error GoTo 0"," Exit Function"," End If"," On Error GoTo 0",' text = Replace(Replace(Replace(text, " ", ""), vbCr, ""), vbLf, "")',' If InStr(text, """phase"":""failed""") > 0 Then PendingIsTerminal = True',' If InStr(text, """phase"":""finalizing""") > 0 Then PendingIsTerminal = True',"End Function",""].join(`\r
|
|
101
|
+
`)}function v(t){return`gui/${t??0}`}function wt(t=process.env){return t.GRIX_LAUNCHER_MANAGED==="1"||!!t.GRIX_CLAUDE_INTERNAL_API_URL}function B(t){const e=String(t?.stdout??"").trim();return String(t?.stderr??"").trim()||e||`exit=${Number(t?.exitCode??-1)}`}function N(t,e,n,r){if(t?.trim())return l.resolve(t);const i=e||n;return i?l.resolve(l.dirname(l.dirname(i))):l.join(r,".grix")}async function z(t){try{await tt(t)}catch{throw new Error(`launchd plist missing: ${t}`)}}async function J(t){const{serviceID:e,domain:n,runCommand:r}=t,i=`${n}/${e}`,a=await r("launchctl",["bootout",i],{allowFailure:!0});if(Number(a?.exitCode??-1)!==0){const c=await r("launchctl",["print",i],{allowFailure:!0});if(Number(c?.exitCode??1)===0){await new Promise(s=>setTimeout(s,250));const o=await r("launchctl",["print",i],{allowFailure:!0});if(Number(o?.exitCode??1)===0)throw new Error(`launchctl bootout ${i}: ${B(a)}`)}return}for(let c=0;c<20;c+=1){const o=await r("launchctl",["print",i],{allowFailure:!0});if(Number(o?.exitCode??1)!==0)return;await new Promise(s=>setTimeout(s,250))}throw new Error(`launchctl bootout ${i}: service remains loaded`)}async function U(t){const{serviceID:e,domain:n,definitionPath:r,runCommand:i}=t,a=`${n}/${e}`,c=await i("launchctl",["bootstrap",n,r],{allowFailure:!0});if(Number(c?.exitCode??-1)===0)return;const o=await i("launchctl",["print",a],{allowFailure:!0});Number(o?.exitCode??1)===0&&await J(t);const s=await i("launchctl",["bootstrap",n,r],{allowFailure:!0});if(Number(s?.exitCode??-1)!==0)throw new Error(`launchctl bootstrap ${n} failed: initial=${B(c)}; retry=${B(s)}`)}function yt(){return{platform:"darwin",kind:"launchd",async install({serviceID:t,nodePath:e,cliPath:n,configDir:r,rootDir:i,stdoutPath:a,stderrPath:c,environmentPath:o="",homeDir:s=m.homedir()}){const u=at(t,s),d=N(i,a,c,s);return await k(l.dirname(u),{recursive:!0}),await $(u,st({serviceID:t,nodePath:e,cliPath:n,configDir:r,rootDir:d,stdoutPath:a,stderrPath:c,environmentPath:o}),{encoding:"utf8",mode:384}),{definitionPath:u}},async start({serviceID:t,definitionPath:e,runCommand:n=f,uid:r=process.getuid?.()??0}){const i=v(r);await z(e),await U({serviceID:t,domain:i,definitionPath:e,runCommand:n})},async stop({serviceID:t,runCommand:e=f,uid:n=process.getuid?.()??0}){const r=v(n);await e("launchctl",["bootout",`${r}/${t}`],{allowFailure:!0})},async restart({serviceID:t,definitionPath:e,runCommand:n=f,uid:r=process.getuid?.()??0,env:i=process.env,reloadDefinition:a=!1}){const c=v(r),o={serviceID:t,domain:c,definitionPath:e,runCommand:n};if(await z(e),!a&&wt(i)){const s=await n("launchctl",["kickstart","-k",`${c}/${t}`],{allowFailure:!0});if(Number(s?.exitCode??-1)===0)return;await U(o);return}await J(o),await U(o)},async uninstall({serviceID:t,definitionPath:e,runCommand:n=f,uid:r=process.getuid?.()??0}){const i=v(r);await n("launchctl",["bootout",`${i}/${t}`],{allowFailure:!0}),await y(e,{force:!0})},async discoverServices({homeDir:t=m.homedir()}={}){const e=l.join(t,"Library","LaunchAgents"),n=await F(e).catch(()=>[]),r=A("darwin"),i=[];for(const a of n){if(!a.startsWith(r)||!a.endsWith(".plist"))continue;const c=a.slice(0,-6),o=l.join(e,a);let s=null;try{const u=await P(o,"utf8");s=nt(u)}catch{}i.push({serviceID:c,definitionPath:o,configDir:s})}return i},async isServiceLoaded({serviceID:t,runCommand:e=f,uid:n=process.getuid?.()??0}){const r=v(n),i=await e("launchctl",["print",`${r}/${t}`],{allowFailure:!0});return Number(i?.exitCode??1)===0}}}function $t(){return{platform:"win32",kind:"task-scheduler",async install({serviceID:t,nodePath:e,cliPath:n,configDir:r,rootDir:i,stdoutPath:a,stderrPath:c,runCommand:o=f,homeDir:s=m.homedir()}){const u=S(t,s),d=N(i,a,c,s);await k(l.dirname(u),{recursive:!0}),await $(u,gt({nodePath:e,cliPath:n,configDir:r,rootDir:d}),"utf8");let h=!1;et()&&(h=await ft({serviceID:t,wrapperPath:u,runCommand:o}));const w=M(t,s);return h?await y(w,{force:!0}).catch(()=>{}):await ut({wrapperPath:u,shortcutPath:w,runCommand:o}),{definitionPath:h?`task:${t}`:`startup:${t}`}},async start({serviceID:t,definitionPath:e,runCommand:n=f,homeDir:r=m.homedir()}){const i=S(t,r);if(e.startsWith("task:"))try{await n("schtasks",["/Run","/TN",t]);return}catch{}_("wscript.exe",["//B","//NoLogo",i])},async stop({serviceID:t,runCommand:e=f,homeDir:n=m.homedir()}){await e("schtasks",["/End","/TN",t],{allowFailure:!0});const r=`${t}-wrapper.vbs`;await H(r,{platform:"win32"})},async restart({serviceID:t,definitionPath:e,runCommand:n=f,homeDir:r=m.homedir()}){await n("schtasks",["/End","/TN",t],{allowFailure:!0});const i=`${t}-wrapper.vbs`;await H(i,{platform:"win32"});const a=S(t,r);if(e?.startsWith("task:"))try{await n("schtasks",["/Run","/TN",t]);return}catch{}_("wscript.exe",["//B","//NoLogo",a])},async uninstall({serviceID:t,runCommand:e=f,homeDir:n=m.homedir()}){await e("schtasks",["/Delete","/TN",t,"/F"],{allowFailure:!0});const r=S(t,n);await y(r,{force:!0});const i=M(t,n);await y(i,{force:!0})},async discoverServices({homeDir:t=m.homedir(),runCommand:e=f}={}){const n=A("win32"),r=await e("schtasks",["/Query","/FO","CSV","/NH"],{allowFailure:!0}),i=[];if(Number(r?.exitCode??-1)===0){const o=String(r.stdout??"").split(/\r?\n/);for(const s of o){const u=s.match(/^"([^"]+)"/);if(!u)continue;const d=u[1];if(!d.startsWith(n))continue;let h=null;try{const w=S(d,t),T=(await P(w,"utf8")).match(/--config-dir\s+""([^""]+)""/);T&&(h=T[1])}catch{}i.push({serviceID:d,definitionPath:`task:${d}`,configDir:h})}}const a=l.join(t,"AppData","Roaming","Microsoft","Windows","Start Menu","Programs","Startup"),c=await F(a).catch(()=>[]);for(const o of c){if(!o.startsWith(n)||!o.endsWith(".lnk"))continue;const s=o.slice(0,-4);if(i.some(d=>d.serviceID===s))continue;let u=null;try{const d=S(s,t),w=(await P(d,"utf8")).match(/--config-dir\s+""([^""]+)""/);w&&(u=w[1])}catch{}i.push({serviceID:s,definitionPath:`startup:${s}`,configDir:u})}return i},async isServiceLoaded({serviceID:t,runCommand:e=f,homeDir:n=m.homedir()}){const r=await e("schtasks",["/Query","/TN",t,"/NH"],{allowFailure:!0});return Number(r?.exitCode??1)===0?!0:b(M(t,n))}}}function Pt(){return{platform:"linux",kind:"systemd-user",async install({serviceID:t,nodePath:e,cliPath:n,configDir:r,rootDir:i,stdoutPath:a,stderrPath:c,environmentPath:o="",homeDir:s=m.homedir(),runCommand:u=f}){const d=it(t,s),h=N(i,a,c,s);return await k(l.dirname(d),{recursive:!0}),await $(d,ct({serviceID:t,nodePath:e,cliPath:n,configDir:r,rootDir:h,stdoutPath:a,stderrPath:c,environmentPath:o}),{encoding:"utf8",mode:384}),await u("systemctl",["--user","daemon-reload"]),await u("systemctl",["--user","enable",`${t}.service`]),{definitionPath:d}},async start({serviceID:t,runCommand:e=f}){await e("systemctl",["--user","start",`${t}.service`])},async stop({serviceID:t,runCommand:e=f}){await e("systemctl",["--user","stop",`${t}.service`],{allowFailure:!0})},async restart({serviceID:t,runCommand:e=f}){await e("systemctl",["--user","restart",`${t}.service`])},async uninstall({serviceID:t,definitionPath:e,runCommand:n=f}){await n("systemctl",["--user","stop",`${t}.service`],{allowFailure:!0}),await n("systemctl",["--user","disable",`${t}.service`],{allowFailure:!0}),await y(e,{force:!0}),await n("systemctl",["--user","daemon-reload"])},async discoverServices({homeDir:t=m.homedir()}={}){const e=l.join(t,".config","systemd","user"),n=await F(e).catch(()=>[]),r=A("linux"),i=[];for(const a of n){if(!a.startsWith(r)||!a.endsWith(".service"))continue;const c=a.slice(0,-8),o=l.join(e,a);let s=null;try{const u=await P(o,"utf8");s=rt(u)}catch{}i.push({serviceID:c,definitionPath:o,configDir:s})}return i},async isServiceLoaded({serviceID:t,runCommand:e=f}){const n=await e("systemctl",["--user","is-active",`${t}.service`],{allowFailure:!0});return String(n?.stdout??"").trim()==="active"}}}function E(t){try{const e=G(t,"utf8"),n=JSON.parse(e);if(n&&typeof n.service_id=="string"&&typeof n.node_path=="string"&&typeof n.cli_path=="string"&&typeof n.root_dir=="string")return n}catch{}return null}function xt(t){return l.join(t,"daemon.lock.json")}function L(t){const e=xt(t);try{const n=G(e,"utf8"),r=JSON.parse(n);if(typeof r?.pid=="number")return r.pid}catch{}return 0}function q(t){return l.join(t,".profile")}function St(t){return t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function Et(t){const e=[R(t.nodePath),R(t.cliPath)];t.configDir&&e.push("--config-dir",R(t.configDir));const n=`env ${C}=${R(t.rootDir)} ${e.join(" ")}`;return`${n} status >/dev/null 2>&1 || (nohup ${n} start >/dev/null 2>&1 &)`}function kt(t,e){return`# >>> grix-connector autostart [${t}] >>>
|
|
102
|
+
${e}
|
|
103
103
|
# <<< grix-connector autostart [${t}] <<<
|
|
104
|
-
`}function
|
|
105
|
-
${i}`).replace(/^\n+/,"");else{const
|
|
104
|
+
`}function K(t){const e=St(t);return new RegExp(`(?:^|\\n)# >>> grix-connector autostart \\[${e}\\] >>>\\n[\\s\\S]*?\\n# <<< grix-connector autostart \\[${e}\\] <<<\\n?`,"m")}async function Ct(t,e,n){let r="";try{r=await P(t,"utf8")}catch{}const i=kt(e,n),a=K(e);let c;if(a.test(r))c=r.replace(a,`
|
|
105
|
+
${i}`).replace(/^\n+/,"");else{const o=r.length===0||r.endsWith(`
|
|
106
106
|
`)?"":`
|
|
107
|
-
`;
|
|
108
|
-
`,{encoding:"utf8",mode:384});const h=
|
|
107
|
+
`;c=`${r}${o}${i}`}c!==r&&(await k(l.dirname(t),{recursive:!0}),await $(t,c,{encoding:"utf8"}))}async function vt(t,e){let n="";try{n=await P(t,"utf8")}catch{return}const r=K(e);if(!r.test(n))return;const i=n.replace(r,"").replace(/^\n+/,"");await $(t,i,{encoding:"utf8"})}function Tt(){return{platform:"linux",kind:"bare-daemon",async install({serviceID:t,nodePath:e,cliPath:n,configDir:r,rootDir:i,stdoutPath:a,stderrPath:c,homeDir:o=m.homedir()}){const s=x(t,o);await k(l.dirname(s),{recursive:!0});const u=N(i,a,c,o),d={schema_version:1,service_id:t,node_path:e,cli_path:n,config_dir:r??"",root_dir:u,stdout_path:a,stderr_path:c,installed_at:Date.now()};await $(s,`${JSON.stringify(d,null,2)}
|
|
108
|
+
`,{encoding:"utf8",mode:384});const h=Et({nodePath:e,cliPath:n,configDir:r??"",rootDir:u});return await Ct(q(o),t,h),{definitionPath:s}},async start({serviceID:t,homeDir:e=m.homedir()}){const n=x(t,e),r=E(n);if(!r)throw new Error(`bare-daemon marker missing: ${n}`);const i=[r.cli_path];r.config_dir&&i.push("--config-dir",r.config_dir),_(r.node_path,i,{env:{...process.env,[C]:r.root_dir}})},async stop({serviceID:t,homeDir:e=m.homedir()}){const n=x(t,e),r=E(n);if(!r)return;const i=L(r.root_dir);if(i>0&&I(i))try{process.kill(i,"SIGTERM")}catch{}},async restart(t){await this.stop(t);const e=Date.now()+5e3,n=x(t.serviceID,t.homeDir??m.homedir()),r=E(n);for(;r&&Date.now()<e;){const i=L(r.root_dir);if(i<=0||!I(i))break;await new Promise(a=>setTimeout(a,100))}await this.start(t)},async uninstall({serviceID:t,homeDir:e=m.homedir()}){const n=x(t,e),r=E(n);if(r){const i=L(r.root_dir);if(i>0&&I(i))try{process.kill(i,"SIGTERM")}catch{}}await y(n,{force:!0}),await vt(q(e),t)},async discoverServices({homeDir:t=m.homedir()}={}){const e=l.join(t,".grix","service"),n=await F(e).catch(()=>[]),r=A("linux"),i=[];for(const a of n){if(!a.startsWith(r)||!a.endsWith(".bare.json"))continue;const c=a.slice(0,-10),o=l.join(e,a),s=E(o);i.push({serviceID:c,definitionPath:o,configDir:s?.config_dir||null})}return i},async isServiceLoaded({serviceID:t,homeDir:e=m.homedir()}){const n=x(t,e),r=E(n);if(!r)return!1;const i=L(r.root_dir);return i>0&&I(i)}}}function bt(t=process.getuid?.()??0){if(process.env.GRIX_FORCE_BARE_DAEMON==="1")return!1;const e=process.env.XDG_RUNTIME_DIR;return!!(e&&b(l.join(e,"bus"))||b(`/run/user/${t}/bus`))}function Wt(t=process.platform,e={}){return t==="darwin"?yt():t==="win32"?$t():e.systemdUserAvailable??bt()?Pt():Tt()}export{ht as WINDOWS_WRAPPER_TEMPLATE_MARKER,pt as WINDOWS_WRAPPER_TEMPLATE_VERSION,gt as buildWindowsWrapperScript,j as collectServicePortEnv,Wt as getPlatformServiceAdapter,wt as isInsideManagedDaemonTree,bt as isSystemdUserBusAvailable,vt as removeProfileAutostart,M as resolveWindowsStartupShortcutPath,S as resolveWindowsWrapperPath,Ct as upsertProfileAutostart};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{execFileSync as v,execSync as m,spawn as y}from"node:child_process";import
|
|
1
|
+
import{execFileSync as v,execSync as m,spawn as y}from"node:child_process";import n from"node:process";import{setTimeout as I}from"node:timers/promises";let d;function F(e=n.platform,t=n.env){return e==="win32"&&t.GRIX_WRAPPER_SUPERVISED!=="1"}function _(){if(n.platform!=="win32")return!1;if(d!==void 0)return d;try{m("net session",{stdio:"ignore",windowsHide:!0}),d=!0}catch{d=!1}return d}function H(e,t,r={}){y(e,t,{detached:!0,stdio:"ignore",windowsHide:!0,env:r.env??n.env}).unref()}function w(e,t,r={}){const{cwd:o,env:u=n.env,allowFailure:a=!1}=r;return new Promise((c,i)=>{const l=y(e,t,{cwd:o||void 0,env:u,stdio:["ignore","pipe","pipe"],windowsHide:!0}),g=[],C=[];l.stdout&&l.stdout.on("data",s=>g.push(s)),l.stderr&&l.stderr.on("data",s=>C.push(s)),l.on("error",s=>{if(a){c({exitCode:-1,stdout:"",stderr:s instanceof Error?s.message:String(s)});return}i(s)}),l.on("close",s=>{const f=s??0,S=Buffer.concat(g).toString("utf8"),x=Buffer.concat(C).toString("utf8");if(f!==0&&!a){const E=x.trim()||S.trim()||`${e} exited with code ${f}`;i(new Error(E));return}c({exitCode:f,stdout:S,stderr:x})})})}function p(e){if(e<=0)return!1;try{return n.kill(e,0),!0}catch(t){if(t instanceof Error&&"code"in t){if(t.code==="ESRCH")return!1;if(t.code==="EPERM")return!0}return!1}}const h=3e3;function L(e){try{const o=m(`wmic process where ProcessId=${e} get CommandLine /format:list`,{encoding:"utf8",stdio:["ignore","pipe","ignore"],timeout:h,windowsHide:!0}).match(/CommandLine=(.*)/)?.[1]?.trim();if(o)return o}catch{}try{const t=`${n.env.SystemRoot||"C:\\Windows"}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`,o=v(t,["-NoProfile","-NonInteractive","-WindowStyle","Hidden","-Command",`(Get-CimInstance Win32_Process -Filter 'ProcessId=${e}').CommandLine`],{encoding:"utf8",stdio:["ignore","pipe","ignore"],timeout:h,windowsHide:!0}).trim();return o||null}catch{return null}}function T(e){if(e<=0||!Number.isInteger(e))return null;if(n.platform==="win32")return L(e);try{const r=m(`ps -ww -o command= -p ${e}`,{encoding:"utf8",stdio:["ignore","pipe","ignore"],timeout:h}).trim();return r||null}catch{return null}}async function W(e,t={}){const{platform:r=n.platform,runCommandImpl:o=w}=t;if(e<=0)return!1;if(r==="win32")return await o("taskkill",["/PID",String(e),"/T","/F"],{allowFailure:!0}),!0;try{return n.kill(-e,"SIGTERM"),!0}catch{}try{return n.kill(e,"SIGTERM"),!0}catch{return!1}}async function G(e,t={}){const{timeoutMs:r=5e3,intervalMs:o=100}=t;if(e<=0)return!0;const u=Date.now()+r;for(;Date.now()<=u;){if(!p(e))return!0;await I(o)}return!p(e)}function R(){return`${n.env.SystemRoot||"C:\\Windows"}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`}function P(e){return e.split(/\r?\n/).map(t=>parseInt(t.trim(),10)).filter(t=>t>0&&!Number.isNaN(t))}async function k(e){const t=w;if(n.platform==="win32"){const o=await t("wmic",["process","where",`CommandLine Like '%${e}%'`,"get","ProcessId"],{allowFailure:!0});if(o.exitCode===0)return P(o.stdout);const u=e.replace(/'/g,"''"),a=await t(R(),["-NoProfile","-NonInteractive","-WindowStyle","Hidden","-Command",`Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -like '*${u}*' } | Select-Object -ExpandProperty ProcessId`],{allowFailure:!0});return a.exitCode!==0?[]:P(a.stdout)}const r=await t("pgrep",["-f",e],{allowFailure:!0});return r.exitCode!==0?[]:P(r.stdout)}async function D(e,t={}){const{exceptPid:r=0,platform:o=n.platform}=t,u=await k(e),a=new Set([n.pid,n.ppid,r]),c=u.filter(i=>!a.has(i));if(c.length===0)return 0;if(o==="win32")for(const i of c)await W(i,{platform:"win32",runCommandImpl:w});else{for(const i of c)try{n.kill(i,"SIGTERM")}catch{}await I(500);for(const i of c)if(p(i))try{n.kill(i,"SIGKILL")}catch{}}return c.length}export{k as findProcessesByCommandLine,T as getProcessCommandLine,p as isProcessRunning,_ as isWindowsElevated,D as killProcessesByCommandLine,w as runCommand,F as shouldSweepWindowsWrappers,H as spawnDetached,W as terminateProcessTree,G as waitForProcessExit};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{SessionManager as a}from"./manager.js";export{a as SessionManager};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{EventEmitter as r}from"events";import{AgentProcess as c}from"../agent/process.js";import{AcpClient as l}from"../protocol/acp-client.js";class h extends r{sessions=new Map;async create(s){const e=new c,n=await e.start(s.agent),i=new l;i.on("event",a=>{this.emit("event",a)}),await i.connect({transport:n,sessionId:s.sessionId,authMethod:s.authMethod,initialMode:s.initialMode});const t=i.sessionId,o={id:t,process:e,client:i};return this.sessions.set(t,o),n.on("close",()=>{this.sessions.delete(t),this.emit("sessionClosed",t)}),this.emit("sessionCreated",t),o}get(s){return this.sessions.get(s)}list(){return[...this.sessions.values()]}async close(s){const e=this.sessions.get(s);e&&(this.sessions.delete(s),await e.process.close(),this.emit("sessionClosed",s))}async closeAll(){const s=[...this.sessions.keys()];await Promise.all(s.map(e=>this.close(e)))}get size(){return this.sessions.size}}export{h as SessionManager};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{JsonRpcTransport as o}from"./json-rpc.js";export{o as JsonRpcTransport};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{EventEmitter as p}from"events";class w extends p{nextId=1;pending=new Map;writeLock=Promise.resolve();closed=!1;_readable;_writable;_onNotify;_onRequest;constructor(t,e,r,o){super(),this._readable=t,this._writable=e,this._onNotify=r,this._onRequest=o,this.startReadLoop()}setHandlers(t,e){t&&(this._onNotify=t),e&&(this._onRequest=e)}async call(t,e,r){if(this.closed)throw new Error("transport closed");const o=this.nextId++,s=String(o),a=await new Promise((i,d)=>{if(this.pending.set(s,{resolve:i}),r){const n=()=>{this.pending.delete(s),d(new Error(`aborted: ${r.reason??"request cancelled"}`))};if(r.aborted){n();return}r.addEventListener("abort",n,{once:!0});const c=i,h=l=>{r.removeEventListener("abort",n),c(l)};this.pending.set(s,{resolve:h})}this.write({jsonrpc:"2.0",id:o,method:t,params:e??null}).catch(n=>{this.pending.delete(s),d(n)})});if(a.error){const i=new Error(a.error.message);throw i.code=a.error.code,i}return a.result}async notify(t,e){if(this.closed)throw new Error("transport closed");await this.write({jsonrpc:"2.0",method:t,params:e??null})}async respondSuccess(t,e){await this.write({jsonrpc:"2.0",id:t,result:e})}async respondError(t,e,r){await this.write({jsonrpc:"2.0",id:t,error:{code:e,message:r}})}close(){if(!this.closed){this.closed=!0;for(const[t,{resolve:e}]of this.pending)e({error:{code:-32e3,message:"transport closed"}});this.pending.clear(),this._readable.destroy(),this.emit("close")}}startReadLoop(){let t="";this._readable.on("data",e=>{t+=e.toString();const r=t.split(`
|
|
2
|
+
`);t=r.pop()??"";for(const o of r){const s=o.trim();s.length!==0&&this.dispatch(s)}}),this._readable.on("end",()=>{t.trim()&&this.dispatch(t.trim()),this.close()}),this._readable.on("error",e=>{this.emit("error",e),this.close()})}dispatch(t){let e;try{e=JSON.parse(t)}catch{return}if(e.id!=null&&!e.method){this.completePending(e.id,e.result,e.error);return}if(e.method&&(e.id==null||e.id===null)){this._onNotify?.(e.method,e.params);return}e.method&&e.id!=null&&this._onRequest?.(e.method,e.id,e.params)}completePending(t,e,r){const o=String(t),s=this.pending.get(o);s&&(this.pending.delete(o),s.resolve({result:e,error:r}))}async write(t){const e=this.writeLock;return this.writeLock=e.then(async()=>new Promise((r,o)=>{const s=JSON.stringify(t)+`
|
|
3
|
+
`;this._writable.write(s,"utf-8")?r():(this._writable.once("drain",r),this._writable.once("error",o))})),this.writeLock}}export{w as JsonRpcTransport};
|
package/openclaw-plugin/index.js
CHANGED
|
@@ -12406,7 +12406,7 @@ async function downloadPackage(params) {
|
|
|
12406
12406
|
// src/openclaw/egg/persona-installer.ts
|
|
12407
12407
|
import { join as join12 } from "node:path";
|
|
12408
12408
|
|
|
12409
|
-
//
|
|
12409
|
+
// node_modules/fflate/esm/index.mjs
|
|
12410
12410
|
import { createRequire } from "module";
|
|
12411
12411
|
var require2 = createRequire("/");
|
|
12412
12412
|
var _a;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "grix-connector",
|
|
3
|
-
"version": "4.9.2
|
|
3
|
+
"version": "4.9.2",
|
|
4
4
|
"description": "Connect local AI coding agents (Claude, Codex, Gemini, Qwen, DeepSeek, Cursor, OpenCode, Pi, OpenHuman, Reasonix) to the Grix scheduling platform. Also serves as an OpenClaw plugin for Grix channel transport.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
package/scripts/launcher.mjs
CHANGED
|
@@ -15,23 +15,15 @@
|
|
|
15
15
|
// 崩溃时间戳落在 `<store>/crashes.json`,launcher 自身被重拉也不会把计数归零。
|
|
16
16
|
// 收到 SIGTERM/SIGINT/SIGHUP 时转发给子进程并随其退出(服务停止语义)。
|
|
17
17
|
//
|
|
18
|
-
// 同一个 store 只允许一个 launcher:启动时抢 store 级单例锁,抢不到就退出,
|
|
19
|
-
// 避免两个 launcher 各自拉起一个 daemon、把对方的 daemon 记成崩溃、再用共享的
|
|
20
|
-
// crashes.json 叠加计数,几秒内把健康版本"三振出局"回滚掉。
|
|
21
|
-
//
|
|
22
18
|
// 本文件由 daemon 在启动时按 LAUNCHER_TEMPLATE_VERSION 比对并原子重写,改动行为时必须 +1。
|
|
23
|
-
export const LAUNCHER_TEMPLATE_VERSION =
|
|
19
|
+
export const LAUNCHER_TEMPLATE_VERSION = 2;
|
|
24
20
|
|
|
25
21
|
import { spawn } from 'node:child_process';
|
|
26
|
-
import { createHash } from 'node:crypto';
|
|
27
22
|
import { existsSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync, appendFileSync } from 'node:fs';
|
|
28
|
-
import { connect, createServer } from 'node:net';
|
|
29
23
|
import { dirname, join } from 'node:path';
|
|
30
24
|
import { fileURLToPath } from 'node:url';
|
|
31
25
|
|
|
32
26
|
const RESTART_EXIT_CODE = 75;
|
|
33
|
-
const LOCK_RETRY_ATTEMPTS = 20;
|
|
34
|
-
const LOCK_RETRY_INTERVAL_MS = 250;
|
|
35
27
|
const RAPID_CRASH_WINDOW_MS = 5 * 60 * 1000;
|
|
36
28
|
const RAPID_CRASH_LIMIT = 3;
|
|
37
29
|
const MAX_BACKOFF_MS = 30_000;
|
|
@@ -43,79 +35,6 @@ function resolveStoreDir() {
|
|
|
43
35
|
return dirname(real);
|
|
44
36
|
}
|
|
45
37
|
|
|
46
|
-
/**
|
|
47
|
-
* store 级单例锁的端点。Windows 用命名管道(进程消失即随之消失,不留残留);
|
|
48
|
-
* POSIX 用 store 里的 unix socket 文件——端口派生会和机器上无关服务撞车,
|
|
49
|
-
* 一旦撞上 launcher 就永远起不来,socket 路径没有这个风险。
|
|
50
|
-
*/
|
|
51
|
-
function launcherLockEndpoint(store, platform = process.platform) {
|
|
52
|
-
if (platform === 'win32') {
|
|
53
|
-
const digest = createHash('sha256').update(store).digest('hex');
|
|
54
|
-
return `\\\\.\\pipe\\grix-launcher-${digest.slice(0, 24)}`;
|
|
55
|
-
}
|
|
56
|
-
return join(store, 'launcher.lock.sock');
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function listenOnce(server, endpoint) {
|
|
60
|
-
return new Promise((resolve, reject) => {
|
|
61
|
-
const onError = (error) => {
|
|
62
|
-
if (error?.code === 'EADDRINUSE' || error?.code === 'EACCES') resolve(error.code);
|
|
63
|
-
else reject(error);
|
|
64
|
-
};
|
|
65
|
-
server.once('error', onError);
|
|
66
|
-
server.once('listening', () => {
|
|
67
|
-
server.removeListener('error', onError);
|
|
68
|
-
server.on('error', () => { /* 锁只需活到进程结束,后续错误无需处理 */ });
|
|
69
|
-
// 锁只用监听端点本身表态,探活连上来就断掉,别攒着不放。
|
|
70
|
-
server.on('connection', (socket) => socket.destroy());
|
|
71
|
-
resolve(null);
|
|
72
|
-
});
|
|
73
|
-
server.listen(endpoint);
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/** unix socket 文件在 launcher 被强杀后会留下,连得上才说明真有 launcher 在跑。 */
|
|
78
|
-
function isEndpointAlive(endpoint) {
|
|
79
|
-
return new Promise((resolve) => {
|
|
80
|
-
let settled = false;
|
|
81
|
-
const socket = connect(endpoint);
|
|
82
|
-
const done = (alive) => {
|
|
83
|
-
if (settled) return;
|
|
84
|
-
settled = true;
|
|
85
|
-
try { socket.destroy(); } catch { /* already gone */ }
|
|
86
|
-
resolve(alive);
|
|
87
|
-
};
|
|
88
|
-
socket.once('connect', () => done(true));
|
|
89
|
-
socket.once('error', () => done(false));
|
|
90
|
-
setTimeout(() => done(false), 1000).unref?.();
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* 抢 store 级单例锁。拿到返回持锁的 server(进程退出时由系统释放),
|
|
96
|
-
* 已被别的 launcher 持有返回 null。
|
|
97
|
-
*
|
|
98
|
-
* 锁机制本身出错(既不是占用也不是权限)时向上抛:调用方按放行处理——
|
|
99
|
-
* launcher 起不来比偶尔多一个 launcher 严重得多。
|
|
100
|
-
*/
|
|
101
|
-
export async function acquireLauncherStoreLock(store, deps = {}) {
|
|
102
|
-
const endpoint = deps.lockEndpoint ?? launcherLockEndpoint(store, deps.platform);
|
|
103
|
-
const create = deps.createServer ?? createServer;
|
|
104
|
-
const isSocketPath = typeof endpoint === 'string' && !endpoint.startsWith('\\\\');
|
|
105
|
-
let server = create();
|
|
106
|
-
let blocked = await listenOnce(server, endpoint);
|
|
107
|
-
if (blocked && isSocketPath) {
|
|
108
|
-
if (await isEndpointAlive(endpoint)) return null;
|
|
109
|
-
// 空壳 socket:上一轮 launcher 被强杀留下的,删掉再抢一次。
|
|
110
|
-
try { rmSync(endpoint, { force: true }); } catch { return null; }
|
|
111
|
-
server = create();
|
|
112
|
-
blocked = await listenOnce(server, endpoint);
|
|
113
|
-
}
|
|
114
|
-
if (blocked) return null;
|
|
115
|
-
server.unref?.();
|
|
116
|
-
return server;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
38
|
function readPointer(store, name) {
|
|
120
39
|
try {
|
|
121
40
|
const value = readFileSync(join(store, name), 'utf8').trim();
|
|
@@ -264,27 +183,6 @@ async function main() {
|
|
|
264
183
|
const store = resolveStoreDir();
|
|
265
184
|
const args = process.argv.slice(2);
|
|
266
185
|
mkdirSync(join(store, 'health'), { recursive: true });
|
|
267
|
-
|
|
268
|
-
// 升级交接时新旧 launcher 会短暂重叠,退几拍再判定,别把交接当成重复实例。
|
|
269
|
-
let storeLock = null;
|
|
270
|
-
let lockError = null;
|
|
271
|
-
for (let attempt = 0; attempt < LOCK_RETRY_ATTEMPTS && !storeLock; attempt++) {
|
|
272
|
-
try {
|
|
273
|
-
storeLock = await acquireLauncherStoreLock(store);
|
|
274
|
-
} catch (error) {
|
|
275
|
-
lockError = error;
|
|
276
|
-
break;
|
|
277
|
-
}
|
|
278
|
-
if (!storeLock) await sleep(LOCK_RETRY_INTERVAL_MS);
|
|
279
|
-
}
|
|
280
|
-
if (lockError) {
|
|
281
|
-
log(store, `store lock unavailable (${lockError instanceof Error ? lockError.message : lockError}); continuing without it`);
|
|
282
|
-
} else if (!storeLock) {
|
|
283
|
-
// 退出码 0:supervisor 见 0 不会当成崩溃,也不会去动 crashes.json。
|
|
284
|
-
log(store, 'another launcher already holds this store; exiting without starting a daemon');
|
|
285
|
-
process.exit(0);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
186
|
let backoffMs = 1000;
|
|
289
187
|
log(store, `started node=${process.version} store=${store} template=${LAUNCHER_TEMPLATE_VERSION}`);
|
|
290
188
|
|
|
Binary file
|