UNPKG

grix-connector

Version:

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.

3 lines (2 loc) 3.46 kB
import{mkdir as S}from"node:fs/promises";import{open as d,readFile as g,unlink as s}from"node:fs/promises";import{join as f}from"node:path";import{randomUUID as v}from"node:crypto";import{readJSONFile as k,writeJSONFileAtomic as c}from"../util/json-file.js";import{appendRotatingFileSync as w}from"../log/rotation.js";const l=20,p=3e4,y=5e3;class U{statePath;logPath;lockPath;rotationOptions;constructor(e,a,n=e+".lock",o={}){this.statePath=e,this.logPath=a,this.lockPath=n,this.rotationOptions=o}get filePath(){return this.statePath}async recordHookEvent(e){const a=P(e);await this.withStateLock(async n=>{n.recent_events.push(a),n.recent_events.sort((o,i)=>o.event_at-i.event_at),n.recent_events.length>l&&(n.recent_events=n.recent_events.slice(-l)),n.latest_event=a,n.updated_at=Date.now()}),this.appendEventLog(a)}async readState(){const e=k(this.statePath);return!e||typeof e!="object"?this.emptyState():this.normalizeState(e)}async reset(){await this.withStateLock(async()=>{await c(this.statePath,this.emptyState())})}emptyState(){return{schema_version:1,updated_at:0,latest_event:null,recent_events:[]}}normalizeState(e){const a=Array.isArray(e.recent_events)?e.recent_events:[];return{schema_version:1,updated_at:e.updated_at??0,latest_event:e.latest_event?_(e.latest_event):null,recent_events:a.filter(n=>typeof n=="object"&&n!==null).map(_)}}async withStateLock(e){await S(f(this.statePath,"..").replace(/\/[^/]+$/,""),{recursive:!0}).catch(()=>{});const a=Date.now()+y;let n=!1;for(;!n&&Date.now()<a;)try{await(await d(this.lockPath,"wx")).close(),n=!0}catch(o){if(o.code==="EEXIST"){const m=await g(this.lockPath,"utf8").catch(()=>""),r=Number(m);!isNaN(r)&&Date.now()-r>p?await s(this.lockPath).catch(()=>{}):await new Promise(u=>setTimeout(u,100))}else throw o}if(!n)throw new Error("hook-signal-store: lock acquisition timeout");try{await D(this.lockPath,String(Date.now()));const o=await this.readState(),i=await e(o);return await c(this.statePath,o),i}finally{await s(this.lockPath).catch(()=>{})}}appendEventLog(e){const a=E(e),n=`${new Date(e.event_at).toISOString()} ${a} `;try{w(this.logPath,n,this.rotationOptions)}catch{}}}function h(t){if(t!=null){if(typeof t=="string")return t;try{return JSON.stringify(t)}catch{return String(t)}}}function P(t){return{event_id:v(),hook_event_name:String(t.hook_event_name??""),event_at:Date.now(),detail:T(t),tool_input:h(t.tool_input)}}function _(t){return{event_id:String(t.event_id??""),hook_event_name:String(t.hook_event_name??""),event_at:t.event_at??0,detail:String(t.detail??""),tool_input:h(t.tool_input)}}function E(t){const e=t.hook_event_name,a=t.detail?`:${t.detail}`:"";return`${e}${a}`}function T(t){switch(String(t.hook_event_name??"")){case"SessionStart":return String(t.source??"");case"PreToolUse":case"PermissionRequest":case"PostToolUse":case"PostToolUseFailure":case"PermissionDenied":return String(t.tool_name??t.reason??"");case"Elicitation":return String(t.mode??"");case"ElicitationResult":return String(t.action??t.mode??"");case"Notification":return String(t.matcher??t.notification_type??t.type??"");case"StopFailure":return String(t.stop_reason??t.error??t.detail??"");case"Stop":return String(t.stop_reason??"");case"PreCompact":case"PostCompact":return String(t.trigger??"");case"ConfigChange":return String(t.source??"");default:return""}}async function D(t,e){const{writeFile:a}=await import("node:fs/promises");await a(t,e,"utf8")}export{U as HookSignalStore};