grix-connector 3.29.2 → 3.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2 +1,2 @@
1
- import{readJSONFile as P,writeJSONFileAtomic as x}from"../util/json-file.js";import{log as h}from"../log/index.js";import{SESSION_MODE_IDS as c}from"../../adapter/claude/protocol-contract.js";import{normalizeClaudeEffort as l}from"../config/claude-direct-env.js";import{chmodSync as S,copyFileSync as p,existsSync as u,renameSync as M,rmSync as b,writeFileSync as y}from"node:fs";import{randomUUID as v}from"node:crypto";const m=c.fullAuto;function o(r){const e=String(r??"").trim().toLowerCase();return e===c.approval||e===c.fullAuto?e:m}class N{bindings=new Map;filePath;legacyFilePath;writePromise=Promise.resolve();constructor(e,t){this.filePath=e??null,this.legacyFilePath=t&&t!==e?t:null}load(){if(!this.filePath)return;const e=u(this.filePath);let t=P(this.filePath),s=!1;if(e&&!Array.isArray(t)){const d=this.quarantineUnreadablePrimary();throw new Error(`Refusing to start with unreadable session bindings: primary=${this.filePath}`+(d?` quarantined=${d}`:"")+(this.legacyFilePath?` legacy=${this.legacyFilePath}`:"")+" (legacy fallback disabled to avoid clobbering newer bindings)")}if(!e&&this.legacyFilePath){s=u(this.legacyFilePath);const d=P(this.legacyFilePath);Array.isArray(d)&&(this.writeMigratedSnapshot(d),t=d,h.info("session-binding-store",`Migrated legacy bindings from ${this.legacyFilePath} to ${this.filePath}; legacy file retained`))}if(!Array.isArray(t)){if(e||s)throw new Error(`Refusing to start with unreadable session bindings: primary=${this.filePath}${this.legacyFilePath?` legacy=${this.legacyFilePath}`:""}`);return}this.bindings.clear();for(const d of t)d.aibotSessionId&&this.bindings.set(d.aibotSessionId,{...d,modeId:o(d.modeId),effort:l(d.effort),dshProviderId:typeof d.dshProviderId=="string"&&d.dshProviderId.trim()?d.dshProviderId.trim():void 0,dshModelId:typeof d.dshModelId=="string"&&d.dshModelId.trim()?d.dshModelId.trim():void 0,dshModeId:d.dshModeId===void 0?void 0:d.dshModeId==="full_auto"?"full_auto":"approval",dshMaxTokens:Number.isSafeInteger(d.dshMaxTokens)&&Number(d.dshMaxTokens)>0?Number(d.dshMaxTokens):void 0,dshAgentPreset:typeof d.dshAgentPreset=="string"&&d.dshAgentPreset.trim()?d.dshAgentPreset.trim():void 0,dshPluginIds:g(d.dshPluginIds),dshSettingsRevision:d.dshSettingsRevision===void 0?void 0:Number.isSafeInteger(d.dshSettingsRevision)&&Number(d.dshSettingsRevision)>=0?Number(d.dshSettingsRevision):0,dshProfileName:typeof d.dshProfileName=="string"&&d.dshProfileName.trim()?d.dshProfileName.trim():void 0,dshProfileSessionId:typeof d.dshProfileSessionId=="string"&&d.dshProfileSessionId.trim()?d.dshProfileSessionId.trim():void 0,dshProfileSessionCreated:d.dshProfileSessionCreated===!0,dshProfileEventCursor:Number.isSafeInteger(d.dshProfileEventCursor)&&Number(d.dshProfileEventCursor)>=0?Number(d.dshProfileEventCursor):0})}set(e,t,s){const d=this.bindings.get(e),i=o(s?.modeId??d?.modeId),n=t!==void 0?t:d?.cwd;this.bindings.set(e,{aibotSessionId:e,cwd:n,acpSessionId:d?.acpSessionId,claudeSessionId:d?.claudeSessionId,codexThreadId:d?.codexThreadId,codexModelId:d?.codexModelId,codexModeId:d?.codexModeId,codewhaleThreadId:d?.codewhaleThreadId,agyConversationId:d?.agyConversationId,codexReasoningEffort:d?.codexReasoningEffort,codexServiceTier:d?.codexServiceTier,codexSandboxMode:d?.codexSandboxMode,piSessionPath:d?.piSessionPath,modeId:i,modelId:d?.modelId,effort:d?.effort,cursorModeId:d?.cursorModeId,acpModelId:d?.acpModelId,acpModeId:d?.acpModeId,dshProviderId:d?.dshProviderId,dshModelId:d?.dshModelId,dshModeId:d?.dshModeId,dshMaxTokens:d?.dshMaxTokens,dshAgentPreset:d?.dshAgentPreset,dshPluginIds:d?.dshPluginIds,dshSettingsRevision:d?.dshSettingsRevision,dshSettingsUpdatedAt:d?.dshSettingsUpdatedAt,dshProfileName:d?.dshProfileName,dshProfileSessionId:d?.dshProfileSessionId,dshProfileSessionCreated:d?.dshProfileSessionCreated,dshProfileEventCursor:d?.dshProfileEventCursor,updatedAt:Date.now()}),this.scheduleWrite()}setAcpSessionId(e,t){this.updateBinding(e,{acpSessionId:t})}setClaudeSessionId(e,t){this.updateBinding(e,{claudeSessionId:t})}getClaudeSessionId(e){return this.bindings.get(e)?.claudeSessionId}setCodexThreadId(e,t){this.updateBinding(e,{codexThreadId:t})}getCodexThreadId(e){return this.bindings.get(e)?.codexThreadId}setAgyConversationId(e,t){this.updateBinding(e,{agyConversationId:t})}getAgyConversationId(e){return this.bindings.get(e)?.agyConversationId}setCodexContext(e,t){this.updateBinding(e,{codexModelId:t.modelId,codexModeId:t.modeId,codexReasoningEffort:t.reasoningEffort,codexServiceTier:t.serviceTier,codexSandboxMode:t.sandboxMode})}getCodexModelId(e){return this.bindings.get(e)?.codexModelId}setCodexModelId(e,t){const s=this.bindings.get(e);if(!s)return;const d=String(t??"").trim();!d||s.codexModelId===d||(this.bindings.set(e,{...s,codexModelId:d,updatedAt:Date.now()}),this.scheduleWrite())}setCodexModeId(e,t){const s=this.bindings.get(e);if(!s)return;const d=String(t??"").trim();!d||s.codexModeId===d||(this.bindings.set(e,{...s,codexModeId:d,updatedAt:Date.now()}),this.scheduleWrite())}setCodexServiceTier(e,t){const s=this.bindings.get(e);if(!s)return;const d=String(t??"").trim();!d||s.codexServiceTier===d||(this.bindings.set(e,{...s,codexServiceTier:d,updatedAt:Date.now()}),this.scheduleWrite())}getCodexModeId(e){return this.bindings.get(e)?.codexModeId}getCodexReasoningEffort(e){return this.bindings.get(e)?.codexReasoningEffort}getCodexServiceTier(e){return this.bindings.get(e)?.codexServiceTier}getCodexSandboxMode(e){return this.bindings.get(e)?.codexSandboxMode}setPiSessionPath(e,t){this.updateBinding(e,{piSessionPath:t})}getPiSessionPath(e){return this.bindings.get(e)?.piSessionPath}setCodeWhaleThreadId(e,t){this.updateBinding(e,{codewhaleThreadId:t})}getCodeWhaleThreadId(e){return this.bindings.get(e)?.codewhaleThreadId}setModeId(e,t){const s=this.bindings.get(e);if(!s)return;const d=o(t);s.modeId!==d&&(this.bindings.set(e,{...s,modeId:d,updatedAt:Date.now()}),this.scheduleWrite())}ensureModeId(e,t=m){const s=this.bindings.get(e);s&&(s.modeId||(this.bindings.set(e,{...s,modeId:o(t),updatedAt:Date.now()}),this.scheduleWrite()))}getAcpSessionId(e){return this.bindings.get(e)?.acpSessionId}getModeId(e){const t=this.bindings.get(e)?.modeId;return t?o(t):void 0}setModelId(e,t){const s=this.bindings.get(e);if(!s)return;const d=t.trim();s.modelId!==d&&(this.bindings.set(e,{...s,modelId:d,updatedAt:Date.now()}),this.scheduleWrite())}getModelId(e){return this.bindings.get(e)?.modelId}setEffort(e,t){const s=this.bindings.get(e);if(!s)return;const d=l(t);d&&s.effort!==d&&(this.bindings.set(e,{...s,effort:d,updatedAt:Date.now()}),this.scheduleWrite())}getEffort(e){return l(this.bindings.get(e)?.effort)}setCursorModeId(e,t){const s=this.bindings.get(e);if(!s)return;const d=String(t??"").trim().toLowerCase(),i=d===""?void 0:d;s.cursorModeId!==i&&(this.bindings.set(e,{...s,cursorModeId:i,updatedAt:Date.now()}),this.scheduleWrite())}getCursorModeId(e){return this.bindings.get(e)?.cursorModeId}setAcpModelId(e,t){const s=this.bindings.get(e);if(!s)return;const d=String(t??"").trim(),i=d===""?void 0:d;s.acpModelId!==i&&(this.bindings.set(e,{...s,acpModelId:i,updatedAt:Date.now()}),this.scheduleWrite())}getAcpModelId(e){return this.bindings.get(e)?.acpModelId}setAcpModeId(e,t){const s=this.bindings.get(e);if(!s)return;const d=String(t??"").trim(),i=d===""?void 0:d;s.acpModeId!==i&&(this.bindings.set(e,{...s,acpModeId:i,updatedAt:Date.now()}),this.scheduleWrite())}getAcpModeId(e){return this.bindings.get(e)?.acpModeId}getDshSettings(e){const t=this.bindings.get(e);if(t)return{...t.dshProviderId?{providerId:t.dshProviderId}:{},...t.dshModelId?{modelId:t.dshModelId}:{},modeId:t.dshModeId==="full_auto"?"full_auto":"approval",...t.dshMaxTokens?{maxTokens:t.dshMaxTokens}:{},...t.dshAgentPreset?{agentPreset:t.dshAgentPreset}:{},pluginIds:g(t.dshPluginIds),revision:t.dshSettingsRevision??0,...t.dshSettingsUpdatedAt?{updatedAt:t.dshSettingsUpdatedAt}:{}}}updateDshSettings(e,t){const s=this.bindings.get(e);if(!s)return;const d=Object.prototype.hasOwnProperty.call(t,"providerId")?String(t.providerId??"").trim()||void 0:s.dshProviderId,i=Object.prototype.hasOwnProperty.call(t,"modelId")?String(t.modelId??"").trim()||void 0:s.dshModelId,n=Object.prototype.hasOwnProperty.call(t,"modeId")?t.modeId==="full_auto"?"full_auto":"approval":s.dshModeId,a=Object.prototype.hasOwnProperty.call(t,"maxTokens")?Number.isSafeInteger(t.maxTokens)&&Number(t.maxTokens)>0?Number(t.maxTokens):void 0:s.dshMaxTokens;if(d===s.dshProviderId&&i===s.dshModelId&&n===s.dshModeId&&a===s.dshMaxTokens)return s.dshSettingsRevision??0;const f=Date.now(),I=(s.dshSettingsRevision??0)+1;return this.bindings.set(e,{...s,dshProviderId:d,dshModelId:i,dshModeId:n,dshMaxTokens:a,dshSettingsRevision:I,dshSettingsUpdatedAt:f,updatedAt:f}),this.scheduleWrite(),I}getDshSelectedProfile(e){const t=this.bindings.get(e)?.dshProfileName;return typeof t=="string"&&t.trim()?t.trim():void 0}isDshProfileLocked(e){return this.bindings.get(e)?.dshProfileSessionCreated===!0}setDshSelectedProfile(e,t){const s=this.bindings.get(e);if(!s)return!1;const d=String(t??"").trim();return d?s.dshProfileName===d?!0:s.dshProfileSessionCreated===!0?!1:(this.bindings.set(e,{...s,dshProfileName:d,dshProfileSessionId:void 0,dshProfileSessionCreated:!1,dshProfileEventCursor:0,updatedAt:Date.now()}),this.scheduleWrite(),!0):!1}getDshAgentPreset(e){const t=this.bindings.get(e)?.dshAgentPreset;return typeof t=="string"&&t.trim()?t.trim():void 0}isDshAgentPresetLocked(e){return this.bindings.get(e)?.dshProfileSessionCreated===!0}setDshAgentPreset(e,t){const s=this.bindings.get(e);if(!s||s.dshProfileSessionCreated===!0)return!1;const d=String(t??"").trim();return!d||s.dshAgentPreset===d?s.dshAgentPreset===d:(this.bindings.set(e,{...s,dshAgentPreset:d,updatedAt:Date.now()}),this.scheduleWrite(),!0)}getDshEnabledPlugins(e){return g(this.bindings.get(e)?.dshPluginIds)}setDshEnabledPlugins(e,t){const s=this.bindings.get(e);if(!s)return;const d=g(t),i=g(s.dshPluginIds);if(w(i,d))return s.dshSettingsRevision??0;const n=Date.now(),a=(s.dshSettingsRevision??0)+1;return this.bindings.set(e,{...s,dshPluginIds:d,dshSettingsRevision:a,dshSettingsUpdatedAt:n,updatedAt:n}),this.scheduleWrite(),a}getDshProfileBinding(e,t){const s=this.bindings.get(e);if(!(!s?.dshProfileSessionId||s.dshProfileName!==t))return{profileName:t,sessionId:s.dshProfileSessionId,sessionCreated:s.dshProfileSessionCreated===!0,eventCursor:s.dshProfileEventCursor??0}}setDshProfileBinding(e,t){const s=this.bindings.get(e);if(!s)return;const d=String(t.profileName).trim(),i=String(t.sessionId).trim();if(!d||!i)return;const n=Number.isSafeInteger(t.eventCursor)&&t.eventCursor>=0?t.eventCursor:0;s.dshProfileName===d&&s.dshProfileSessionId===i&&s.dshProfileSessionCreated===t.sessionCreated&&(s.dshProfileEventCursor??0)===n||(this.bindings.set(e,{...s,dshProfileName:d,dshProfileSessionId:i,dshProfileSessionCreated:t.sessionCreated,dshProfileEventCursor:n,updatedAt:Date.now()}),this.scheduleWrite())}get(e){return this.bindings.get(e)}getMostRecentlyUpdatedSessionId(e){const t=e?.requireCwd!==!1;let s,d=-1;for(const i of this.bindings.values())t&&!String(i.cwd??"").trim()||i.updatedAt>d&&(d=i.updatedAt,s=i.aibotSessionId);return s}delete(e){const t=this.bindings.get(e);t&&(h.info("session-binding-store",`delete binding session_id=${e} cwd=${t.cwd??""} codexThreadId=${t.codexThreadId??""}`),this.bindings.delete(e),this.scheduleWrite())}quarantineUnreadablePrimary(){if(!this.filePath||!u(this.filePath))return;const e=`${this.filePath}.corrupt.${Date.now()}`;try{return p(this.filePath,e),h.error("session-binding-store",`Copied unreadable primary bindings to ${e} (original retained to block legacy clobber)`),e}catch(t){h.error("session-binding-store",`Failed to copy unreadable primary ${this.filePath}: ${t instanceof Error?t.message:String(t)}`);return}}entries(){return this.bindings.entries()}async flush(){await this.writePromise}scheduleWrite(){this.filePath&&(this.writePromise=this.writePromise.then(()=>this.write()).catch(e=>{h.error("session-binding-store",`Persist failed: ${e instanceof Error?e.message:e}`)}))}async write(){if(!this.filePath)return;const e=Array.from(this.bindings.values());await x(this.filePath,e)}writeMigratedSnapshot(e){if(!this.filePath)return;const t=`${this.filePath}.${v()}.migration.tmp`;try{y(t,`${JSON.stringify(e,null,2)}
2
- `,{encoding:"utf8",mode:384,flag:"wx"}),M(t,this.filePath);try{S(this.filePath,384)}catch{}}catch(s){if(u(t))try{b(t)}catch{}throw new Error(`Failed to migrate legacy session bindings from ${this.legacyFilePath} to ${this.filePath}: ${s instanceof Error?s.message:String(s)}`)}}updateBinding(e,t){const s=this.bindings.get(e),d={aibotSessionId:e,acpSessionId:Object.prototype.hasOwnProperty.call(t,"acpSessionId")?t.acpSessionId:s?.acpSessionId,claudeSessionId:Object.prototype.hasOwnProperty.call(t,"claudeSessionId")?t.claudeSessionId:s?.claudeSessionId,codexThreadId:Object.prototype.hasOwnProperty.call(t,"codexThreadId")?t.codexThreadId:s?.codexThreadId,codexModelId:Object.prototype.hasOwnProperty.call(t,"codexModelId")?t.codexModelId:s?.codexModelId,codexModeId:Object.prototype.hasOwnProperty.call(t,"codexModeId")?t.codexModeId:s?.codexModeId,codewhaleThreadId:Object.prototype.hasOwnProperty.call(t,"codewhaleThreadId")?t.codewhaleThreadId:s?.codewhaleThreadId,agyConversationId:Object.prototype.hasOwnProperty.call(t,"agyConversationId")?t.agyConversationId:s?.agyConversationId,codexReasoningEffort:Object.prototype.hasOwnProperty.call(t,"codexReasoningEffort")?t.codexReasoningEffort:s?.codexReasoningEffort,codexServiceTier:Object.prototype.hasOwnProperty.call(t,"codexServiceTier")?t.codexServiceTier:s?.codexServiceTier,codexSandboxMode:Object.prototype.hasOwnProperty.call(t,"codexSandboxMode")?t.codexSandboxMode:s?.codexSandboxMode,piSessionPath:Object.prototype.hasOwnProperty.call(t,"piSessionPath")?t.piSessionPath:s?.piSessionPath,cwd:s?.cwd,modeId:o(s?.modeId),modelId:s?.modelId,effort:Object.prototype.hasOwnProperty.call(t,"effort")?l(t.effort):l(s?.effort),cursorModeId:s?.cursorModeId,acpModelId:s?.acpModelId,acpModeId:s?.acpModeId,dshProviderId:s?.dshProviderId,dshModelId:s?.dshModelId,dshModeId:s?.dshModeId,dshMaxTokens:s?.dshMaxTokens,dshAgentPreset:s?.dshAgentPreset,dshPluginIds:s?.dshPluginIds,dshSettingsRevision:s?.dshSettingsRevision,dshSettingsUpdatedAt:s?.dshSettingsUpdatedAt,dshProfileName:s?.dshProfileName,dshProfileSessionId:s?.dshProfileSessionId,dshProfileSessionCreated:s?.dshProfileSessionCreated,dshProfileEventCursor:s?.dshProfileEventCursor,updatedAt:Date.now()};this.bindings.set(e,d),this.scheduleWrite()}}function g(r){const e=new Set,t=[];for(const s of r??[]){const d=String(s??"").trim();!d||e.has(d)||(e.add(d),t.push(d))}return t}function w(r,e){return r.length===e.length&&r.every((t,s)=>t===e[s])}export{N as SessionBindingStore};
1
+ import{readJSONFile as x,writeJSONFileAtomic as S}from"../util/json-file.js";import{log as a}from"../log/index.js";import{SESSION_MODE_IDS as u}from"../../adapter/claude/protocol-contract.js";import{normalizeClaudeEffort as g}from"../config/claude-direct-env.js";import{chmodSync as b,copyFileSync as M,existsSync as f,renameSync as y,rmSync as v,writeFileSync as w}from"node:fs";import{randomUUID as C}from"node:crypto";const p=u.fullAuto;function o(r){const t=String(r??"").trim().toLowerCase();return t===u.approval||t===u.fullAuto?t:p}class N{bindings=new Map;filePath;legacyFilePath;writePromise=Promise.resolve();constructor(t,e){this.filePath=t??null,this.legacyFilePath=e&&e!==t?e:null}load(){if(!this.filePath)return;const t=f(this.filePath);let e=x(this.filePath),i=!1;if(t&&!Array.isArray(e)){const d=this.quarantineUnreadablePrimary();throw new Error(`Refusing to start with unreadable session bindings: primary=${this.filePath}`+(d?` quarantined=${d}`:"")+(this.legacyFilePath?` legacy=${this.legacyFilePath}`:"")+" (legacy fallback disabled to avoid clobbering newer bindings)")}if(!t&&this.legacyFilePath){i=f(this.legacyFilePath);const d=x(this.legacyFilePath);Array.isArray(d)&&(this.writeMigratedSnapshot(d),e=d,a.info("session-binding-store",`Migrated legacy bindings from ${this.legacyFilePath} to ${this.filePath}; legacy file retained`))}if(!Array.isArray(e)){if(t||i)throw new Error(`Refusing to start with unreadable session bindings: primary=${this.filePath}${this.legacyFilePath?` legacy=${this.legacyFilePath}`:""}`);return}this.bindings.clear();for(const d of e)d.aibotSessionId&&this.bindings.set(d.aibotSessionId,{...d,modeId:o(d.modeId),effort:g(d.effort),dshProviderId:typeof d.dshProviderId=="string"&&d.dshProviderId.trim()?d.dshProviderId.trim():void 0,dshModelId:typeof d.dshModelId=="string"&&d.dshModelId.trim()?d.dshModelId.trim():void 0,dshModeId:d.dshModeId===void 0?void 0:d.dshModeId==="full_auto"?"full_auto":"approval",dshThinking:d.dshThinking==="disabled"?"disabled":d.dshThinking==="enabled"?"enabled":void 0,dshReasoningEffort:d.dshReasoningEffort==="max"?"max":d.dshReasoningEffort==="high"?"high":void 0,dshMaxTokens:Number.isSafeInteger(d.dshMaxTokens)&&Number(d.dshMaxTokens)>0?Number(d.dshMaxTokens):void 0,dshAgentPreset:typeof d.dshAgentPreset=="string"&&d.dshAgentPreset.trim()?d.dshAgentPreset.trim():void 0,dshPluginIds:l(d.dshPluginIds),dshSettingsRevision:d.dshSettingsRevision===void 0?void 0:Number.isSafeInteger(d.dshSettingsRevision)&&Number(d.dshSettingsRevision)>=0?Number(d.dshSettingsRevision):0,dshProfileName:typeof d.dshProfileName=="string"&&d.dshProfileName.trim()?d.dshProfileName.trim():void 0,dshProfileSessionId:typeof d.dshProfileSessionId=="string"&&d.dshProfileSessionId.trim()?d.dshProfileSessionId.trim():void 0,dshProfileSessionCreated:d.dshProfileSessionCreated===!0,dshProfileEventCursor:Number.isSafeInteger(d.dshProfileEventCursor)&&Number(d.dshProfileEventCursor)>=0?Number(d.dshProfileEventCursor):0})}set(t,e,i){const d=this.bindings.get(t),s=o(i?.modeId??d?.modeId),n=e!==void 0?e:d?.cwd;this.bindings.set(t,{aibotSessionId:t,cwd:n,acpSessionId:d?.acpSessionId,claudeSessionId:d?.claudeSessionId,codexThreadId:d?.codexThreadId,codexModelId:d?.codexModelId,codexModeId:d?.codexModeId,codewhaleThreadId:d?.codewhaleThreadId,agyConversationId:d?.agyConversationId,codexReasoningEffort:d?.codexReasoningEffort,codexServiceTier:d?.codexServiceTier,codexSandboxMode:d?.codexSandboxMode,piSessionPath:d?.piSessionPath,modeId:s,modelId:d?.modelId,effort:d?.effort,cursorModeId:d?.cursorModeId,acpModelId:d?.acpModelId,acpModeId:d?.acpModeId,dshProviderId:d?.dshProviderId,dshModelId:d?.dshModelId,dshModeId:d?.dshModeId,dshThinking:d?.dshThinking,dshReasoningEffort:d?.dshReasoningEffort,dshMaxTokens:d?.dshMaxTokens,dshAgentPreset:d?.dshAgentPreset,dshPluginIds:d?.dshPluginIds,dshSettingsRevision:d?.dshSettingsRevision,dshSettingsUpdatedAt:d?.dshSettingsUpdatedAt,dshProfileName:d?.dshProfileName,dshProfileSessionId:d?.dshProfileSessionId,dshProfileSessionCreated:d?.dshProfileSessionCreated,dshProfileEventCursor:d?.dshProfileEventCursor,updatedAt:Date.now()}),this.scheduleWrite()}setAcpSessionId(t,e){this.updateBinding(t,{acpSessionId:e})}setClaudeSessionId(t,e){this.updateBinding(t,{claudeSessionId:e})}getClaudeSessionId(t){return this.bindings.get(t)?.claudeSessionId}setCodexThreadId(t,e){this.updateBinding(t,{codexThreadId:e})}getCodexThreadId(t){return this.bindings.get(t)?.codexThreadId}setAgyConversationId(t,e){this.updateBinding(t,{agyConversationId:e})}getAgyConversationId(t){return this.bindings.get(t)?.agyConversationId}setCodexContext(t,e){this.updateBinding(t,{codexModelId:e.modelId,codexModeId:e.modeId,codexReasoningEffort:e.reasoningEffort,codexServiceTier:e.serviceTier,codexSandboxMode:e.sandboxMode})}getCodexModelId(t){return this.bindings.get(t)?.codexModelId}setCodexModelId(t,e){const i=this.bindings.get(t);if(!i)return;const d=String(e??"").trim();!d||i.codexModelId===d||(this.bindings.set(t,{...i,codexModelId:d,updatedAt:Date.now()}),this.scheduleWrite())}setCodexModeId(t,e){const i=this.bindings.get(t);if(!i)return;const d=String(e??"").trim();!d||i.codexModeId===d||(this.bindings.set(t,{...i,codexModeId:d,updatedAt:Date.now()}),this.scheduleWrite())}setCodexServiceTier(t,e){const i=this.bindings.get(t);if(!i)return;const d=String(e??"").trim();!d||i.codexServiceTier===d||(this.bindings.set(t,{...i,codexServiceTier:d,updatedAt:Date.now()}),this.scheduleWrite())}getCodexModeId(t){return this.bindings.get(t)?.codexModeId}getCodexReasoningEffort(t){return this.bindings.get(t)?.codexReasoningEffort}getCodexServiceTier(t){return this.bindings.get(t)?.codexServiceTier}getCodexSandboxMode(t){return this.bindings.get(t)?.codexSandboxMode}setPiSessionPath(t,e){this.updateBinding(t,{piSessionPath:e})}getPiSessionPath(t){return this.bindings.get(t)?.piSessionPath}setCodeWhaleThreadId(t,e){this.updateBinding(t,{codewhaleThreadId:e})}getCodeWhaleThreadId(t){return this.bindings.get(t)?.codewhaleThreadId}setModeId(t,e){const i=this.bindings.get(t);if(!i)return;const d=o(e);i.modeId!==d&&(this.bindings.set(t,{...i,modeId:d,updatedAt:Date.now()}),this.scheduleWrite())}ensureModeId(t,e=p){const i=this.bindings.get(t);i&&(i.modeId||(this.bindings.set(t,{...i,modeId:o(e),updatedAt:Date.now()}),this.scheduleWrite()))}getAcpSessionId(t){return this.bindings.get(t)?.acpSessionId}getModeId(t){const e=this.bindings.get(t)?.modeId;return e?o(e):void 0}setModelId(t,e){const i=this.bindings.get(t);if(!i)return;const d=e.trim();i.modelId!==d&&(this.bindings.set(t,{...i,modelId:d,updatedAt:Date.now()}),this.scheduleWrite())}getModelId(t){return this.bindings.get(t)?.modelId}setEffort(t,e){const i=this.bindings.get(t);if(!i)return;const d=g(e);d&&i.effort!==d&&(this.bindings.set(t,{...i,effort:d,updatedAt:Date.now()}),this.scheduleWrite())}getEffort(t){return g(this.bindings.get(t)?.effort)}setCursorModeId(t,e){const i=this.bindings.get(t);if(!i)return;const d=String(e??"").trim().toLowerCase(),s=d===""?void 0:d;i.cursorModeId!==s&&(this.bindings.set(t,{...i,cursorModeId:s,updatedAt:Date.now()}),this.scheduleWrite())}getCursorModeId(t){return this.bindings.get(t)?.cursorModeId}setAcpModelId(t,e){const i=this.bindings.get(t);if(!i)return;const d=String(e??"").trim(),s=d===""?void 0:d;i.acpModelId!==s&&(this.bindings.set(t,{...i,acpModelId:s,updatedAt:Date.now()}),this.scheduleWrite())}getAcpModelId(t){return this.bindings.get(t)?.acpModelId}setAcpModeId(t,e){const i=this.bindings.get(t);if(!i)return;const d=String(e??"").trim(),s=d===""?void 0:d;i.acpModeId!==s&&(this.bindings.set(t,{...i,acpModeId:s,updatedAt:Date.now()}),this.scheduleWrite())}getAcpModeId(t){return this.bindings.get(t)?.acpModeId}getDshSettings(t){const e=this.bindings.get(t);if(e)return{...e.dshProviderId?{providerId:e.dshProviderId}:{},...e.dshModelId?{modelId:e.dshModelId}:{},modeId:e.dshModeId==="full_auto"?"full_auto":"approval",...e.dshThinking?{thinking:e.dshThinking}:{},...e.dshReasoningEffort?{reasoningEffort:e.dshReasoningEffort}:{},...e.dshMaxTokens?{maxTokens:e.dshMaxTokens}:{},...e.dshAgentPreset?{agentPreset:e.dshAgentPreset}:{},pluginIds:l(e.dshPluginIds),revision:e.dshSettingsRevision??0,...e.dshSettingsUpdatedAt?{updatedAt:e.dshSettingsUpdatedAt}:{}}}updateDshSettings(t,e){const i=this.bindings.get(t);if(!i)return;const d=Object.prototype.hasOwnProperty.call(e,"providerId")?String(e.providerId??"").trim()||void 0:i.dshProviderId,s=Object.prototype.hasOwnProperty.call(e,"modelId")?String(e.modelId??"").trim()||void 0:i.dshModelId,n=Object.prototype.hasOwnProperty.call(e,"modeId")?e.modeId==="full_auto"?"full_auto":"approval":i.dshModeId,h=Object.prototype.hasOwnProperty.call(e,"thinking")?e.thinking==="disabled"?"disabled":"enabled":i.dshThinking,c=Object.prototype.hasOwnProperty.call(e,"reasoningEffort")?e.reasoningEffort==="max"?"max":"high":i.dshReasoningEffort,I=Object.prototype.hasOwnProperty.call(e,"maxTokens")?Number.isSafeInteger(e.maxTokens)&&Number(e.maxTokens)>0?Number(e.maxTokens):void 0:i.dshMaxTokens;if(d===i.dshProviderId&&s===i.dshModelId&&n===i.dshModeId&&h===i.dshThinking&&c===i.dshReasoningEffort&&I===i.dshMaxTokens)return i.dshSettingsRevision??0;const P=Date.now(),m=(i.dshSettingsRevision??0)+1;return this.bindings.set(t,{...i,dshProviderId:d,dshModelId:s,dshModeId:n,dshThinking:h,dshReasoningEffort:c,dshMaxTokens:I,dshSettingsRevision:m,dshSettingsUpdatedAt:P,updatedAt:P}),this.scheduleWrite(),m}getDshSelectedProfile(t){const e=this.bindings.get(t)?.dshProfileName;return typeof e=="string"&&e.trim()?e.trim():void 0}isDshProfileLocked(t){return this.bindings.get(t)?.dshProfileSessionCreated===!0}setDshSelectedProfile(t,e){const i=this.bindings.get(t);if(!i)return!1;const d=String(e??"").trim();return d?i.dshProfileName===d?!0:i.dshProfileSessionCreated===!0?!1:(this.bindings.set(t,{...i,dshProfileName:d,dshProfileSessionId:void 0,dshProfileSessionCreated:!1,dshProfileEventCursor:0,updatedAt:Date.now()}),this.scheduleWrite(),!0):!1}getDshAgentPreset(t){const e=this.bindings.get(t)?.dshAgentPreset;return typeof e=="string"&&e.trim()?e.trim():void 0}isDshAgentPresetLocked(t){return this.bindings.get(t)?.dshProfileSessionCreated===!0}setDshAgentPreset(t,e){const i=this.bindings.get(t);if(!i||i.dshProfileSessionCreated===!0)return!1;const d=String(e??"").trim();return!d||i.dshAgentPreset===d?i.dshAgentPreset===d:(this.bindings.set(t,{...i,dshAgentPreset:d,updatedAt:Date.now()}),this.scheduleWrite(),!0)}getDshEnabledPlugins(t){return l(this.bindings.get(t)?.dshPluginIds)}setDshEnabledPlugins(t,e){const i=this.bindings.get(t);if(!i)return;const d=l(e),s=l(i.dshPluginIds);if(A(s,d))return i.dshSettingsRevision??0;const n=Date.now(),h=(i.dshSettingsRevision??0)+1;return this.bindings.set(t,{...i,dshPluginIds:d,dshSettingsRevision:h,dshSettingsUpdatedAt:n,updatedAt:n}),this.scheduleWrite(),h}getDshProfileBinding(t,e){const i=this.bindings.get(t);if(!(!i?.dshProfileSessionId||i.dshProfileName!==e))return{profileName:e,sessionId:i.dshProfileSessionId,sessionCreated:i.dshProfileSessionCreated===!0,eventCursor:i.dshProfileEventCursor??0}}setDshProfileBinding(t,e){const i=this.bindings.get(t);if(!i)return;const d=String(e.profileName).trim(),s=String(e.sessionId).trim();if(!d||!s)return;const n=Number.isSafeInteger(e.eventCursor)&&e.eventCursor>=0?e.eventCursor:0;i.dshProfileName===d&&i.dshProfileSessionId===s&&i.dshProfileSessionCreated===e.sessionCreated&&(i.dshProfileEventCursor??0)===n||(this.bindings.set(t,{...i,dshProfileName:d,dshProfileSessionId:s,dshProfileSessionCreated:e.sessionCreated,dshProfileEventCursor:n,updatedAt:Date.now()}),this.scheduleWrite())}get(t){return this.bindings.get(t)}getMostRecentlyUpdatedSessionId(t){const e=t?.requireCwd!==!1;let i,d=-1;for(const s of this.bindings.values())e&&!String(s.cwd??"").trim()||s.updatedAt>d&&(d=s.updatedAt,i=s.aibotSessionId);return i}delete(t){const e=this.bindings.get(t);e&&(a.info("session-binding-store",`delete binding session_id=${t} cwd=${e.cwd??""} codexThreadId=${e.codexThreadId??""}`),this.bindings.delete(t),this.scheduleWrite())}quarantineUnreadablePrimary(){if(!this.filePath||!f(this.filePath))return;const t=`${this.filePath}.corrupt.${Date.now()}`;try{return M(this.filePath,t),a.error("session-binding-store",`Copied unreadable primary bindings to ${t} (original retained to block legacy clobber)`),t}catch(e){a.error("session-binding-store",`Failed to copy unreadable primary ${this.filePath}: ${e instanceof Error?e.message:String(e)}`);return}}entries(){return this.bindings.entries()}async flush(){await this.writePromise}scheduleWrite(){this.filePath&&(this.writePromise=this.writePromise.then(()=>this.write()).catch(t=>{a.error("session-binding-store",`Persist failed: ${t instanceof Error?t.message:t}`)}))}async write(){if(!this.filePath)return;const t=Array.from(this.bindings.values());await S(this.filePath,t)}writeMigratedSnapshot(t){if(!this.filePath)return;const e=`${this.filePath}.${C()}.migration.tmp`;try{w(e,`${JSON.stringify(t,null,2)}
2
+ `,{encoding:"utf8",mode:384,flag:"wx"}),y(e,this.filePath);try{b(this.filePath,384)}catch{}}catch(i){if(f(e))try{v(e)}catch{}throw new Error(`Failed to migrate legacy session bindings from ${this.legacyFilePath} to ${this.filePath}: ${i instanceof Error?i.message:String(i)}`)}}updateBinding(t,e){const i=this.bindings.get(t),d={aibotSessionId:t,acpSessionId:Object.prototype.hasOwnProperty.call(e,"acpSessionId")?e.acpSessionId:i?.acpSessionId,claudeSessionId:Object.prototype.hasOwnProperty.call(e,"claudeSessionId")?e.claudeSessionId:i?.claudeSessionId,codexThreadId:Object.prototype.hasOwnProperty.call(e,"codexThreadId")?e.codexThreadId:i?.codexThreadId,codexModelId:Object.prototype.hasOwnProperty.call(e,"codexModelId")?e.codexModelId:i?.codexModelId,codexModeId:Object.prototype.hasOwnProperty.call(e,"codexModeId")?e.codexModeId:i?.codexModeId,codewhaleThreadId:Object.prototype.hasOwnProperty.call(e,"codewhaleThreadId")?e.codewhaleThreadId:i?.codewhaleThreadId,agyConversationId:Object.prototype.hasOwnProperty.call(e,"agyConversationId")?e.agyConversationId:i?.agyConversationId,codexReasoningEffort:Object.prototype.hasOwnProperty.call(e,"codexReasoningEffort")?e.codexReasoningEffort:i?.codexReasoningEffort,codexServiceTier:Object.prototype.hasOwnProperty.call(e,"codexServiceTier")?e.codexServiceTier:i?.codexServiceTier,codexSandboxMode:Object.prototype.hasOwnProperty.call(e,"codexSandboxMode")?e.codexSandboxMode:i?.codexSandboxMode,piSessionPath:Object.prototype.hasOwnProperty.call(e,"piSessionPath")?e.piSessionPath:i?.piSessionPath,cwd:i?.cwd,modeId:o(i?.modeId),modelId:i?.modelId,effort:Object.prototype.hasOwnProperty.call(e,"effort")?g(e.effort):g(i?.effort),cursorModeId:i?.cursorModeId,acpModelId:i?.acpModelId,acpModeId:i?.acpModeId,dshProviderId:i?.dshProviderId,dshModelId:i?.dshModelId,dshModeId:i?.dshModeId,dshMaxTokens:i?.dshMaxTokens,dshAgentPreset:i?.dshAgentPreset,dshPluginIds:i?.dshPluginIds,dshSettingsRevision:i?.dshSettingsRevision,dshSettingsUpdatedAt:i?.dshSettingsUpdatedAt,dshProfileName:i?.dshProfileName,dshProfileSessionId:i?.dshProfileSessionId,dshProfileSessionCreated:i?.dshProfileSessionCreated,dshProfileEventCursor:i?.dshProfileEventCursor,updatedAt:Date.now()};this.bindings.set(t,d),this.scheduleWrite()}}function l(r){const t=new Set,e=[];for(const i of r??[]){const d=String(i??"").trim();!d||t.has(d)||(t.add(d),e.push(d))}return e}function A(r,t){return r.length===t.length&&r.every((e,i)=>e===t[i])}export{N as SessionBindingStore};
@@ -1,28 +1,27 @@
1
1
  ---
2
2
  name: grix-admin
3
- description: Responsible for OpenClaw and grix-connector local configuration, binding, and runtime convergence; can create new remote API agents through the current agent's WS channel, and supports querying, creating, modifying agent categories and assigning categories to agents, reusable across agent creation and management flows.
3
+ description: Responsible for grix-connector local configuration, binding, and runtime convergence; can create new remote API agents through the current agent's WS channel, and supports querying, creating, modifying agent categories and assigning categories to agents.
4
4
  ---
5
5
 
6
6
  # Grix Agent Admin
7
7
 
8
8
  `grix-admin` is responsible for three things:
9
9
 
10
- 1. Landing existing remote agent parameters into local OpenClaw or grix-connector, and handling runtime convergence after binding.
10
+ 1. Landing existing remote agent parameters into local grix-connector and handling runtime convergence after binding.
11
11
  2. When the current main agent is already online and has the corresponding scope, creating new remote API agents through `grix_admin`'s direct actions, then continuing with local landing.
12
12
  3. During agent creation or subsequent agent management, reusing `grix_admin`'s direct actions to query categories, create categories, modify categories, and assign categories to agents.
13
13
 
14
14
  ## Entry Method
15
15
 
16
- 1. In most cases, enter this skill from `grix_admin`'s `task` entry; the first line of `task` must clearly state `bind-local`, `create-and-bind`, `category-manage`, `connector-bind-local`, or `create-and-connector-bind`.
17
- 2. Only when executing "remote API agent creation / category query / category creation / category modification / category assignment" remote steps within this skill should you directly call `grix_admin` once, without passing `task` again.
16
+ 1. In most cases, enter this skill from `grix_admin`'s `task` entry; the first line of `task` must clearly state `connector-bind-local`, `create-and-connector-bind`, or `category-manage`.
17
+ 2. Only when executing remote API agent creation or category operations within this skill should you directly call `grix_admin` once, without passing `task` again.
18
18
  3. In new flows, always explicitly pass `action` when directly calling `grix_admin`:
19
19
  - `create_agent`
20
20
  - `list_categories`
21
21
  - `create_category`
22
22
  - `update_category`
23
23
  - `assign_category`
24
- 4. The legacy direct call format for `create_agent` (passing only `agentName` and other fields without `action`) is still compatible, but should not be used in new flows.
25
- 5. Use `bind-local` / `create-and-bind` for **OpenClaw** local configuration; use `connector-bind-local` / `create-and-connector-bind` for **grix-connector** local configuration. Do not mix the two targets in a single invocation.
24
+ 4. The legacy direct call format for `create_agent` is still compatible, but should not be used in new flows.
26
25
 
27
26
  ## Agent Creation Intake
28
27
 
@@ -34,8 +33,8 @@ Before any `create_agent` API call:
34
33
  - A concise, professional `agentName` that reflects the Agent's role.
35
34
  - A professional `introduction` that states its purpose, core responsibilities, intended users or scenarios, operating expectations, and important boundaries.
36
35
  4. Preserve the user's facts and intent. Do not invent permissions, business authority, expertise, or responsibilities that the user did not grant.
37
- 5. Treat `introduction` as the Agent's behavioral specification, not promotional copy. It directly affects how the Agent behaves after creation.
38
- 6. Pass the finalized professional text in the actual `introduction` field of `action=create_agent`; do not leave it only in conversation, a summary, `soulContent`, or local persona files.
36
+ 5. Treat `introduction` as the Agent's behavioral specification, not promotional copy.
37
+ 6. Pass the finalized professional text in the actual `introduction` field of `action=create_agent`.
39
38
 
40
39
  ## Direct Action List
41
40
 
@@ -45,7 +44,7 @@ Before any `create_agent` API call:
45
44
  - `categoryId` and `categoryName` cannot be provided simultaneously
46
45
  - When `categoryName` is given, it first checks for duplicates under `parentCategoryId`; if not found, creates and assigns
47
46
  2. `action=list_categories`
48
- - Required: (none)
47
+ - Required: none
49
48
  3. `action=create_category`
50
49
  - Required: `name`, `parentId`
51
50
  - Optional: `sortOrder`
@@ -56,245 +55,106 @@ Before any `create_agent` API call:
56
55
  - Required: `agentId`, `categoryId`
57
56
  - `categoryId=0` means clear the category
58
57
 
59
- ## Mode A: bind-local (Initial Handoff from grix-register)
58
+ ## Mode A: connector-bind-local (grix-connector Local Binding)
60
59
 
61
- Input fields (written in `grix_admin.task`, all required):
60
+ Input fields (written in `grix_admin.task`):
62
61
 
63
- 1. First line must be `bind-local`
64
- 2. `agent_name`
65
- 3. `agent_id`
66
- 4. `api_endpoint`
67
- 5. `api_key`
62
+ 1. First line must be `connector-bind-local`
63
+ 2. `agent_name` (required)
64
+ 3. `agent_id` (required)
65
+ 4. `api_endpoint` (required)
66
+ 5. `api_key` (required)
67
+ 6. `client_type` (optional, default `pi`)
68
68
 
69
69
  Execution rules:
70
70
 
71
- 1. Do not perform remote creation; execute local binding directly. Do not call any script that directly modifies `openclaw.json`.
72
- 2. Prepare local directories first:
73
- - `workspace=~/.openclaw/workspace-<agent_name>`
74
- - `agentDir=~/.openclaw/agents/<agent_name>/agent`
75
- - Persona files go only in the `workspace` root: `IDENTITY.md`, `SOUL.md`, `AGENTS.md`, and optionally `USER.md` / `MEMORY.md`
76
- - Do not put persona files in `agentDir`; `agentDir` is the per-agent runtime state directory managed by OpenClaw
77
- - If `workspace` is missing required persona files, add minimal files to avoid an empty workspace for the new agent
78
- 3. Read existing configuration; if paths do not exist, treat as empty object / empty array:
79
- - `channels.grix.accounts`
80
- - `agents.list`
81
- - `tools.profile`
82
- - `tools.alsoAllow`
83
- - `tools.sessions.visibility`
84
- - To confirm existing Grix bindings, additionally use `openclaw agents bindings --agent <agent_name> --json` to view the current binding list
85
- 4. Calculate target values for this operation:
86
- - `channels.grix.accounts.<agent_name>`: write `name`, `enabled=true`, `apiKey`, `wsUrl`, `agentId`
87
- - `agents.list`: ensure entry exists with `id=<agent_name>`, `name=<agent_name>`, `workspace`, `agentDir`, `model`
88
- - Grix binding: ensure the target agent is ultimately bound to `grix:<agent_name>`
89
- - `tools.profile`: set to `"coding"`
90
- - `tools.alsoAllow`: must include at least `message`, `grix_query`, `grix_group`, `grix_register`, `grix_message_send`, `grix_message_unsend`
91
- - If the current binding target is the main agent, also ensure that agent's own `tools.alsoAllow` retains `grix_admin`, `grix_egg`, `grix_update`, `openclaw_memory_setup`; this set goes only at the agent level, not in global `tools.alsoAllow`
92
- - `tools.sessions.visibility`: set to `"agent"`
93
- - If `channels.grix.enabled=false`, change it back to `true`
94
- 5. Rules for determining `model`:
95
- - First reuse the existing `model` from that local agent's entry
96
- - If the existing entry has none, use `agents.defaults.model.primary`
97
- - If still unavailable, clearly state that model is missing, stop execution, do not guess
98
- 6. Write using official CLI item by item; do not overwrite the entire config:
99
- - `openclaw config set channels.grix.accounts.<agent_name> '<ACCOUNT_JSON>' --strict-json`
100
- - `openclaw config set agents.list '<NEXT_AGENTS_LIST_JSON>' --strict-json`
101
- - `openclaw agents bind --agent <agent_name> --bind grix:<agent_name>`
102
- - `openclaw config set tools.profile '"coding"' --strict-json`
103
- - `openclaw config set tools.alsoAllow '["message","grix_query","grix_group","grix_register","grix_message_send","grix_message_unsend"]' --strict-json`
104
- - If the current binding target is the main agent, also merge `grix_admin`, `grix_egg`, `grix_update`, `openclaw_memory_setup` into that agent's own `agents.list` record's `tools.alsoAllow`; do not put this set in global `tools.alsoAllow`
105
- - `openclaw config set tools.sessions.visibility '"agent"' --strict-json`
106
- - Only when the current config explicitly has `channels.grix.enabled` turned off, execute `openclaw config set channels.grix.enabled true --strict-json`
107
- 7. After writing, must perform static validation:
108
- - `openclaw config validate`
109
- - `openclaw config get --json channels.grix.accounts.<agent_name>`
110
- - `openclaw config get --json agents.list`
111
- - `openclaw agents bindings --agent <agent_name> --json`
112
- 8. If this invocation already has real verification conditions, must immediately perform a real routing verification; prefer reusing the current install/acceptance context, do not invent a new probe. The following situations are all considered as binding runtime not yet switched successfully:
113
- - Reply falls to the main agent
114
- - Reply behaves as the default assistant
115
- - Reply still shows old persona, old config, or obvious ID mismatch
116
- 9. Only when this `bind-local` invocation itself handles real verification, and step 7 static validation passed but step 8 real routing verification still fails, is one `openclaw gateway restart` allowed as targeted remediation; after restart, must redo the same round of real routing verification.
117
- 10. Success definition:
118
- - Can perform real verification: only "static validation passed + real routing verification passed" counts as `bind-local` complete
119
- - This invocation cannot yet perform real verification: can only clearly state "config has been written, runtime not yet tested, needs subsequent upper-level flow to continue verification"; do not claim it has fully taken effect
120
-
121
- ## Mode B: create-and-bind (Subsequent Management When Main Channel and Scope Are Available)
71
+ 1. Do not perform remote creation; execute local grix-connector binding directly.
72
+ 2. Target file: `~/.grix/config/agents.json`. If the file or directory does not exist, initialize it as `{ "agents": [] }`.
73
+ 3. Read the existing `agents` array. If an entry with the same `name` as `agent_name` already exists, update it in place; otherwise append a new entry.
74
+ 4. Write `name`, `ws_url`, `agent_id`, `api_key`, and `client_type` into the entry. Map `api_endpoint` to `ws_url`; `client_type` defaults to `pi`.
75
+ 5. Preserve valid JSON and set file permissions to `0o600`. Before writing, create a timestamped backup at `~/.grix/config/agents.json.bak.<YYYYMMDDHHMMSS>` and also set the backup file permissions to `0o600`.
76
+ 6. If the daemon is running, trigger reload through the synchronous Admin API:
77
+ - Default endpoint: `POST http://127.0.0.1:19580/api/reload`
78
+ - If the port is customized, read `~/.grix/data/admin-port`.
79
+ - If reload returns an error, report it and stop before verification.
80
+ 7. After reload succeeds, verify through `GET /api/agents` that the target entry exists and reports `alive=true`.
81
+ 8. `alive=true` only proves that the instance started. Also verify the platform connection by inspecting the latest daemon log for WebSocket success or authentication failure, or ask the owner to send a test message.
82
+ 9. If secondary verification fails, state “config loaded and instance started, but platform connection not verified”; do not claim full convergence.
83
+ 10. If real verification cannot be performed, state “config has been written, reload completed, runtime not yet tested, needs subsequent flow to continue verification”.
84
+
85
+ ## Mode B: create-and-connector-bind (Create Remote Agent Then Bind Locally)
122
86
 
123
87
  Fields written in `grix_admin.task`:
124
88
 
125
- 1. First line must be `create-and-bind`
89
+ 1. First line must be `create-and-connector-bind`
126
90
  2. `agentName` (required)
127
91
  3. `introduction` (required; professionally organized according to **Agent Creation Intake**)
128
92
  4. `isMain` (optional, default `false`)
129
- 5. `categoryId` (optional): assign the new agent directly to an existing category
130
- 6. `categoryName` (optional): create if not exists, then assign
131
- 7. `parentCategoryId` (optional): only used in the `categoryName` approach, default `0`
132
- 8. `categorySortOrder` (optional): only used when creating a category
93
+ 5. `clientType` (optional, default `pi`)
94
+ 6. `categoryId` (optional)
95
+ 7. `categoryName` (optional)
96
+ 8. `parentCategoryId` (optional, default `0`)
97
+ 9. `categorySortOrder` (optional)
133
98
 
134
99
  Execution rules:
135
100
 
136
101
  1. Confirm the current session is bound to a valid Grix account; cross-account execution is prohibited.
137
- 2. If both `categoryId` and `categoryName` are provided, report an error and stop immediately to avoid ambiguity.
138
- 3. Call `grix_admin` only once with `action=create_agent`, delegating remote creation and optional category handling to it; pass:
139
- - `action=create_agent`
140
- - `agentName`
141
- - Required `introduction`
142
- - Optional `isMain`
143
- - Optional `categoryId`
144
- - Optional `categoryName`
145
- - Optional `parentCategoryId`
146
- - Optional `categorySortOrder`
147
- 4. After remote creation succeeds, read `createdAgent.id`, `createdAgent.agent_name`, `createdAgent.api_endpoint`, `createdAgent.api_key` from the return result.
148
- 5. If the request included category information, check whether the return already includes the category assignment result; if not, proceed with supplementary steps according to `categoryId` / `categoryName` rules and explain the reason.
149
- 6. In the `categoryName` flow, if multiple categories with the exact same name appear under the same parent, stop and ask the owner to clean up categories or use an explicit `categoryId` instead.
150
- 7. After remote creation and optional category steps succeed, immediately transition to the `bind-local` local binding steps; if this invocation already has real verification context, follow the same "static validation -> real routing verification -> one restart if needed -> same-round retest" convergence rules; otherwise explicitly hand the "continue real verification" responsibility back to the upper-level flow.
151
- 8. `isMain=true` should only be used when actually creating a new main API agent; generally subsequent new agents should not enable this by default.
152
- 9. Throughout the `create-and-bind` flow, do not treat "config written successfully" as completion; only when this invocation itself handles real routing verification and verification fails should one `openclaw gateway restart` be treated as targeted remediation.
102
+ 2. If both `categoryId` and `categoryName` are provided, report an error and stop.
103
+ 3. Call `grix_admin` once with `action=create_agent`, passing `agentName`, `introduction`, and the supplied optional fields.
104
+ 4. Read `createdAgent.id`, `createdAgent.agent_name`, `createdAgent.api_endpoint`, and `createdAgent.api_key` from the result.
105
+ 5. If category assignment was requested but not completed by the return result, supplement it through the appropriate direct category actions.
106
+ 6. In the `categoryName` flow, if multiple exact matches exist under the same parent, stop and ask the owner to use an explicit `categoryId`.
107
+ 7. Continue immediately with `connector-bind-local` using the returned parameters and `clientType`.
108
+ 8. Complete synchronous reload, `alive=true` verification, and the secondary platform-connection check before claiming full convergence.
109
+ 9. `isMain=true` should only be used when actually creating a new main API agent.
153
110
 
154
111
  ## Mode C: category-manage (Category Management)
155
112
 
156
113
  Fields written in `grix_admin.task`:
157
114
 
158
115
  1. First line must be `category-manage`
159
- 2. `operation` (required): only `list`, `create`, `update`, `assign` are allowed
116
+ 2. `operation` (required): `list`, `create`, `update`, or `assign`
160
117
  3. `name` (`create` / `update` required)
161
118
  4. `parentId` (`create` / `update` required)
162
119
  5. `sortOrder` (`create` / `update` optional)
163
- 6. `categoryId` (`update` / `assign` required; for `assign`, `0` means clear)
120
+ 6. `categoryId` (`update` / `assign` required; `0` clears an assignment)
164
121
  7. `agentId` (`assign` required)
165
122
 
166
123
  Execution rules:
167
124
 
168
- 1. Strictly bound to the current session account; cross-account execution is prohibited.
169
- 2. All remote steps must only be completed through `grix_admin`'s direct actions; hand-written HTTP and temporary scripts are prohibited.
170
- 3. `operation=list`
171
- - Call `action=list_categories`
172
- 4. `operation=create`
173
- - Call `action=create_category`
174
- 5. `operation=update`
175
- - Call `action=update_category`
176
- 6. `operation=assign`
177
- - Call `action=assign_category`
178
- - `categoryId=0` explicitly means clear the agent's current category
179
- 7. If the current management task also includes "creating a new agent", prefer using `create-and-bind`; do not split remote creation into other custom flows.
125
+ 1. Strictly bind all operations to the current session account; cross-account execution is prohibited.
126
+ 2. Complete all remote steps through `grix_admin` direct actions; do not hand-write HTTP or temporary scripts.
127
+ 3. Map operations directly: `list_categories`, `create_category`, `update_category`, or `assign_category`.
128
+ 4. If the task also creates a new agent, use `create-and-connector-bind` instead.
180
129
 
181
- ## Mode D: connector-bind-local (grix-connector Local Binding)
130
+ ## Remote Creation Fallback
182
131
 
183
- Input fields (written in `grix_admin.task`, all required):
132
+ If the task has neither existing agent parameters nor an online main channel with `agent.api.create`, stop and ask the owner to create the remote agent through the backend admin path. After obtaining the parameters, proceed with `connector-bind-local`.
184
133
 
185
- 1. First line must be `connector-bind-local`
186
- 2. `agent_name`
187
- 3. `agent_id`
188
- 4. `api_endpoint`
189
- 5. `api_key`
190
- 6. `client_type` (optional, default `pi`)
134
+ ## Guardrails
191
135
 
192
- Execution rules:
136
+ 1. Never ask for a website account or password.
137
+ 2. All remote creation and category actions must go through `grix_admin` direct actions via the current account's WS channel.
138
+ 3. Never repeatedly echo the complete `api_key` in plaintext.
139
+ 4. Before writing `~/.grix/config/agents.json`, create a timestamped backup, preserve valid JSON, and set both files to `0o600`.
140
+ 5. Trigger reload through the synchronous Admin API, not the CLI reload command, so failures are visible.
141
+ 6. Do not claim full convergence until the entry exists with `alive=true` and the platform connection is verified.
193
142
 
194
- 1. Do not perform remote creation; execute local grix-connector binding directly. Do not invoke any OpenClaw CLI commands in this mode.
195
- 2. Target file: `~/.grix/config/agents.json`. If the file or directory does not exist, initialize it as `{ "agents": [] }`.
196
- 3. Read the existing `agents` array. If an entry with the same `name` as `agent_name` already exists, update it in place; otherwise append a new entry.
197
- 4. Write the following fields into the entry:
198
- - `name`: `agent_name`
199
- - `ws_url`: `api_endpoint`
200
- - `agent_id`: `agent_id`
201
- - `api_key`: `api_key`
202
- - `client_type`: `client_type` (default `pi`)
203
- 5. Preserve valid JSON and set file permissions to `0o600`. Before writing, create a timestamped backup at `~/.grix/config/agents.json.bak.<YYYYMMDDHHMMSS>` and also set the backup file permissions to `0o600`.
204
- 6. If `grix-connector` daemon is running (`grix-connector status` returns `daemon_state=running`), trigger reload via the daemon Admin API:
205
- - Default endpoint: `POST http://127.0.0.1:19580/api/reload`
206
- - The actual admin port may be overridden by `GRIX_ADMIN_PORT` or `--admin-port`; if the default fails, read `~/.grix/data/admin-port` for the current port.
207
- - This endpoint is synchronous: it waits for `manager.reload()` to finish and returns `{ ok: true, result }` or an error. Unlike the CLI `grix-connector reload`, it does not suffer from "signal sent but config not yet applied" race conditions, and it surfaces JSON parse errors or `RELOAD_UNSAFE` failures to the caller.
208
- - If the reload request returns an error, report the error and stop; do not proceed to verification.
209
- 7. After reload succeeds, verify that the daemon has loaded the new entry via `GET /api/agents`:
210
- - Find an entry where `name === agent_name`.
211
- - Confirm the entry exists and reports `alive === true`.
212
- - **Important**: `alive=true` only means the daemon has started the Agent instance; it does **not** prove the Agent has successfully connected to the Grix platform or that the API key is valid. Authentication failures do not flip `alive` to `false`.
213
- - Therefore, perform a secondary convergence check: inspect the latest daemon log in `~/.grix/log/` (files are named `grix-connector-<YYYY-MM-DD>.log`; list the directory and open the most recent one) for WebSocket connection success / authentication failure messages for this Agent, or ask the owner to send a test message to the Agent.
214
- - If the secondary check fails, state "config loaded and instance started, but platform connection not yet verified"; do not claim full convergence.
215
- 8. If this invocation cannot perform real verification, clearly state "config has been written, reload completed, runtime not yet tested, needs subsequent flow to continue verification"; do not claim it has fully taken effect.
216
- 9. Success definition:
217
- - Can perform real verification: "config written + reload succeeded + Agent entry exists with `alive=true` + platform connection verified (log or test message)" counts as `connector-bind-local` complete.
218
- - Cannot perform real verification: only "config written + reload succeeded" can be claimed.
219
-
220
- ## Mode E: create-and-connector-bind (Create Remote Agent Then Bind to grix-connector)
221
-
222
- Fields written in `grix_admin.task`:
143
+ ## Error Handling
223
144
 
224
- 1. First line must be `create-and-connector-bind`
225
- 2. `agentName` (required)
226
- 3. `introduction` (required; professionally organized according to **Agent Creation Intake**)
227
- 4. `isMain` (optional, default `false`)
228
- 5. `clientType` (optional, default `pi`)
229
- 6. `categoryId` (optional): assign the new agent directly to an existing category
230
- 7. `categoryName` (optional): create if not exists, then assign
231
- 8. `parentCategoryId` (optional): only used in the `categoryName` approach, default `0`
232
- 9. `categorySortOrder` (optional): only used when creating a category
233
-
234
- Execution rules:
235
-
236
- 1. Confirm the current session is bound to a valid Grix account; cross-account execution is prohibited.
237
- 2. If both `categoryId` and `categoryName` are provided, report an error and stop immediately to avoid ambiguity.
238
- 3. Call `grix_admin` only once with `action=create_agent`, delegating remote creation and optional category handling to it; pass:
239
- - `action=create_agent`
240
- - `agentName`
241
- - Required `introduction`
242
- - Optional `isMain`
243
- - Optional `categoryId`
244
- - Optional `categoryName`
245
- - Optional `parentCategoryId`
246
- - Optional `categorySortOrder`
247
- 4. After remote creation succeeds, read `createdAgent.id`, `createdAgent.agent_name`, `createdAgent.api_endpoint`, `createdAgent.api_key` from the return result.
248
- 5. If the request included category information, check whether the return already includes the category assignment result; if not, proceed with supplementary steps according to `categoryId` / `categoryName` rules and explain the reason.
249
- 6. In the `categoryName` flow, if multiple categories with the exact same name appear under the same parent, stop and ask the owner to clean up categories or use an explicit `categoryId` instead.
250
- 7. After remote creation and optional category steps succeed, immediately transition to the `connector-bind-local` local binding steps using the returned parameters and `clientType`.
251
- 8. Trigger reload via the synchronous Admin API `POST /api/reload` (see Mode D for port resolution and failure handling); then verify the new Agent entry exists and `alive=true` via `GET /api/agents`, and perform the secondary platform-connection verification (log inspection or test message).
252
- 9. `isMain=true` should only be used when actually creating a new main API agent; generally subsequent new agents should not enable this by default.
253
- 10. Throughout the `create-and-connector-bind` flow, do not treat "config written successfully" as completion; only when this invocation handles real verification and verification passes is it complete.
254
-
255
- ## Remote Creation Fallback Conditions
256
-
257
- If the current task has neither an existing `agent_name`, `agent_id`, `api_endpoint`, `api_key`, nor an available online main channel or `agent.api.create` permission, stop this skill first and clearly prompt the user to create the remote agent through the backend admin path. After obtaining these parameters:
258
-
259
- - For OpenClaw target, proceed with `bind-local`.
260
- - For grix-connector target, proceed with `connector-bind-local`.
261
-
262
- ## Guardrails (Applicable to All Modes)
263
-
264
- 1. Never ask user for website account/password.
265
- 2. `bind-local` mode must not call back to `grix-register` to avoid circular routing.
266
- 3. All remote creation / category-related actions must only go through `grix_admin` direct actions via the current account's WS channel; do not hand-write HTTP or fall back to legacy scripts.
267
- 4. The complete `api_key` is only passed back once; do not repeatedly echo it in plaintext.
268
- 5. Before local `openclaw config set` / `validate` succeeds, do not claim config is complete; when this invocation can perform real verification, do not claim `bind-local` / `create-and-bind` is complete until real routing verification passes.
269
- 6. During an install private chat, do not manually modify `openclaw.json` and then execute `openclaw gateway restart`.
270
- 7. Do not reference or call `grix_agent_bind.py`; for OpenClaw modes this skill only uses official OpenClaw CLI commands.
271
- 8. For `connector-bind-local` / `create-and-connector-bind`, directly reading and writing `~/.grix/config/agents.json` is the intended mechanism; this is grix-connector's own configuration domain, not a third-party product.
272
- 9. When writing `~/.grix/config/agents.json`, always create a timestamped backup first, preserve valid JSON, and set both the config file and the backup file permissions to `0o600`.
273
- 10. After writing grix-connector config, trigger reload through the synchronous Admin API `POST /api/reload` (not the CLI `grix-connector reload`) so that reload errors are visible to the caller. Verify the Agent entry exists and reports `alive=true`, and perform a secondary platform-connection check before claiming full convergence.
274
-
275
- ## Error Handling Rules
276
-
277
- 1. `bind-local` / `connector-bind-local` missing fields: clearly state which field is missing and stop.
278
- 2. `create-and-bind` / `create-and-connector-bind` missing `agentName` or `introduction`: do not call the creation API; ask for the Agent's purpose, core responsibilities, intended users, and boundaries, then professionally organize both fields.
279
- 3. `create-and-bind` / `create-and-connector-bind` with both `categoryId` and `categoryName`: clearly state the conflict and stop.
280
- 4. `category-manage` missing `operation` or operation-specific fields: clearly state which field is missing and stop.
281
- 5. Remote returns `code=4003` or message explicitly mentions `agent.api.create`: tell the owner to grant `agent.api.create` on the Agent permissions page.
282
- 6. Remote returns `code=4003` or message explicitly mentions `agent.category.list` / `agent.category.create` / `agent.category.update` / `agent.category.assign`: tell the owner to grant the corresponding scope on the Agent permissions page.
283
- 7. Missing remote agent parameters and current account cannot create: clearly require backend admin creation first.
284
- 8. Local config failure (OpenClaw): return the failed command and result, then stop; emphasize which `get` / `set` / `validate` step failed.
285
- 9. Local config failure (grix-connector): report the exact JSON parse error, write error, or `~/.grix/config/agents.json` state, then stop.
286
- 10. Synchronous Admin API reload failed (e.g., returns non-2xx, `RELOAD_UNSAFE`, JSON parse error in agents.json): report the error body and daemon status; do not proceed to verification or claim the Agent is online.
287
- 11. Admin API verification failed (Agent entry missing or not `alive=true`): report the observed entry or missing entry, then stop.
288
- 12. Secondary platform-connection verification failed (log shows authentication/connection errors, or test message not delivered): state "config loaded and instance started, but platform connection not verified"; converge as partially complete, do not claim full convergence.
289
- 13. This invocation handles real routing verification, and after one `openclaw gateway restart` retest still fails: clearly state "static config has been written, but runtime has not yet switched successfully"; converge as failed or partially complete, do not write it as success.
145
+ 1. For missing fields, clearly state which field is missing and stop.
146
+ 2. If `agentName` or a usable `introduction` is missing, ask for the Agent's purpose, responsibilities, intended users, and boundaries before remote creation.
147
+ 3. If both `categoryId` and `categoryName` are supplied, report the conflict and stop.
148
+ 4. For `code=4003`, report the exact missing `agent.api.create` or `agent.category.*` scope.
149
+ 5. Report exact JSON parse, file write, reload, or verification errors and stop at the failed phase.
150
+ 6. If the instance starts but platform verification fails, report the flow as partially complete.
290
151
 
291
152
  ## Response Style
292
153
 
293
- 1. Clearly state whether the current execution is `bind-local`, `create-and-bind`, `category-manage`, `connector-bind-local`, or `create-and-connector-bind`.
294
- 2. Report in phases: remote creation / category handling (if any) / local config writing / reload / validation results.
295
- 3. Clearly state whether local config has taken effect; if only static config succeeded but this invocation cannot perform real verification, can only write "config has been written, runtime not yet tested, needs subsequent flow to continue verification"; if only the category step succeeded, also state that clearly.
296
- 4. For grix-connector flows, explicitly report whether the synchronous Admin API reload succeeded, whether the Admin API shows the Agent entry exists with `alive=true`, and the result of any secondary platform-connection verification.
297
- 5. If remote creation succeeded but category or local binding subsequently failed, clearly state it is "partially complete"; do not generically write it as success.
154
+ 1. Clearly state whether the execution is `connector-bind-local`, `create-and-connector-bind`, or `category-manage`.
155
+ 2. Report remote creation, category handling, local config writing, reload, and validation as separate phases when applicable.
156
+ 3. Explicitly report the Admin API reload result, `alive=true` result, and secondary platform-connection result.
157
+ 4. If a later phase fails after remote creation, report the overall result as partially complete.
298
158
 
299
159
  ## References
300
160