shennian 0.4.35 → 0.4.37

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.
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": 1,
3
3
  "jsMinified": true,
4
4
  "minifier": "esbuild",
5
- "fileCount": 182,
5
+ "fileCount": 183,
6
6
  "files": [
7
7
  {
8
8
  "file": "bin/shennian.js",
@@ -241,8 +241,8 @@
241
241
  },
242
242
  {
243
243
  "file": "src/agents/pi-coding-agent.js",
244
- "beforeBytes": 24735,
245
- "afterBytes": 13377
244
+ "beforeBytes": 24853,
245
+ "afterBytes": 13461
246
246
  },
247
247
  {
248
248
  "file": "src/agents/pi-managed-extension.js",
@@ -251,8 +251,8 @@
251
251
  },
252
252
  {
253
253
  "file": "src/agents/pi-managed-model-broker.js",
254
- "beforeBytes": 8338,
255
- "afterBytes": 4484
254
+ "beforeBytes": 8635,
255
+ "afterBytes": 4663
256
256
  },
257
257
  {
258
258
  "file": "src/agents/pi-rpc-client.js",
@@ -731,8 +731,8 @@
731
731
  },
732
732
  {
733
733
  "file": "src/session/handlers/agent-workspace-validation.js",
734
- "beforeBytes": 7952,
735
- "afterBytes": 4110
734
+ "beforeBytes": 8799,
735
+ "afterBytes": 4597
736
736
  },
737
737
  {
738
738
  "file": "src/session/handlers/agents.js",
@@ -759,6 +759,11 @@
759
759
  "beforeBytes": 676,
760
760
  "afterBytes": 392
761
761
  },
762
+ {
763
+ "file": "src/session/handlers/pi-validation-session.js",
764
+ "beforeBytes": 2016,
765
+ "afterBytes": 1147
766
+ },
762
767
  {
763
768
  "file": "src/session/handlers/project-fs.js",
764
769
  "beforeBytes": 3778,
@@ -891,13 +896,13 @@
891
896
  },
892
897
  {
893
898
  "file": "src/upgrade/updater-bootstrap.js",
894
- "beforeBytes": 3367,
895
- "afterBytes": 1796
899
+ "beforeBytes": 3595,
900
+ "afterBytes": 1840
896
901
  },
897
902
  {
898
903
  "file": "src/upgrade/updater-launcher.js",
899
- "beforeBytes": 8025,
900
- "afterBytes": 3968
904
+ "beforeBytes": 9356,
905
+ "afterBytes": 4695
901
906
  },
902
907
  {
903
908
  "file": "src/upgrade/updater-protocol.js",
@@ -1,4 +1,4 @@
1
- import h from"node:fs";import u from"node:path";import{randomUUID as P}from"node:crypto";import{AgentAdapter as E,registerAgent as A}from"./adapter.js";import{resolveBuiltinCommand as v,spawnAgentCommand as k}from"./command-spec.js";import{buildAgentProcessEnv as x,buildManagedAgentProcessEnv as T,buildManagedNativeFullProcessEnv as N}from"../agent-env.js";import{loadConfig as R,resolveShennianPath as f}from"../config/index.js";import{buildInlineChannelPreamble as b,buildPlatformInstructions as M}from"./platform-instructions.js";import{isManagedAgentNativeFull as w}from"../security/managed-agent-policy.js";import{PiRpcClient as C}from"./pi-rpc-client.js";import{registerManagedAgentRuntimeSecurity as D}from"../security/managed-agent-runtime.js";import{PI_CODING_AGENT_MANAGED_RUNTIME_SECURITY as B}from"./pi-coding-agent-managed-permissions.js";import{PiManagedModelBroker as O}from"./pi-managed-model-broker.js";import{writePiManagedExtension as q}from"./pi-managed-extension.js";import{recordPiSessionOwnership as j}from"./pi-session-ownership.js";D("pi-coding-agent",B);const F=32*1024,U=256*1024,$=3e3;function g(i){return i.replace(/[^a-zA-Z0-9_.-]/g,"_").slice(0,180)||"session"}function G(i){return i.data&&typeof i.data=="object"&&!Array.isArray(i.data)?i.data:{}}function z(i){if(typeof i=="string")return i;if(!i||typeof i!="object")return"";const e=i.content;return Array.isArray(e)?e.map(s=>{if(!s||typeof s!="object")return"";const t=s;return t.type==="text"&&typeof t.text=="string"?t.text:""}).join(""):""}function y(i,e=U){return i.length<=e?i:`${i.slice(0,e)}
2
- [truncated]`}function H(i){return y(i,2e3).replace(/Bearer\s+[A-Za-z0-9._~-]+/gi,"Bearer [redacted]").replace(/\b(?:pimc|mrc)_[A-Za-z0-9_-]{20,}\b/g,"[redacted-capability]").replace(/\bsk-[A-Za-z0-9_-]{12,}\b/g,"[redacted-key]")}function X(i){try{return y(JSON.stringify(i))}catch{return"[unserializable]"}}function I(i){if(!i||typeof i!="object")return;const e=i;if(!(typeof e.input!="number"&&typeof e.output!="number"))return{inputTokens:typeof e.input=="number"?e.input:0,outputTokens:typeof e.output=="number"?e.output:0}}function L(i){const e=i?.trim();if(!e)return{};const s=e.indexOf("/");return s<=0||s===e.length-1?{modelId:e}:{provider:e.slice(0,s),modelId:e.slice(s+1)}}function Z(i,e,s,t,n,a){const l=b(s,t,n,a),c=e?.filter(o=>o.kind!=="image")??[];if(!c.length)return`${l}${i}`;const p=c.map(o=>`[${o.name}](<${o.path}>)`);return`${l}${[i,"",...p].join(`
3
- `)}`}function K(i,e){const s=[];for(const t of i??[]){if(t.kind!=="image"||!t.mimeType.startsWith("image/"))continue;let n=t.previewData?.trim()??"";const a=n.indexOf(",");if(n.startsWith("data:")&&a>=0&&(n=n.slice(a+1)),!n&&e&&t.path&&!/^https?:\/\//i.test(t.path))try{const l=h.statSync(t.path);l.isFile()&&l.size<=10*1024*1024&&(n=h.readFileSync(t.path).toString("base64"))}catch{n=""}n&&s.push({type:"image",data:n,mimeType:t.mimeType})}return s}class W extends E{type="pi-coding-agent";get recoveryCapabilities(){return{inspectTerminal:!1,reattachRun:!1,resumeRun:!1,resumeSession:!0}}process=null;rpc=null;shennianSessionId="";workDir="";sessionDir="";agentSessionId=null;externalChannel=null;extraEnv={};managedAgentPolicy=null;runId="";seq=0;active=!1;terminal=!0;stderr="";lastUsage;promptFile=null;modelBroker=null;persistNativeSession=!0;validationRoot=null;runPhase=null;configure(e){e.sessionId&&(this.shennianSessionId=e.sessionId),this.externalChannel=e.externalChannel??null,this.extraEnv=e.env??{},"managedAgentPolicy"in e&&(this.managedAgentPolicy=e.managedAgentPolicy??null)}async start(e,s,t,n){await this.stopProcess(),this.shennianSessionId=e,this.workDir=this.managedAgentPolicy?.adapterPolicy.workDir??s,this.agentSessionId=t??null;const a=f("runtime","pi-coding-agent",g(e));this.persistNativeSession=n?.persistNativeSession!==!1,this.validationRoot=this.persistNativeSession?null:u.join(a,`validation-${P()}`),this.sessionDir=this.validationRoot?u.join(this.validationRoot,"sessions"):u.join(a,"sessions"),h.mkdirSync(this.sessionDir,{recursive:!0,mode:448}),process.platform!=="win32"&&h.chmodSync(this.sessionDir,448),this.active=!1,this.terminal=!0}async send(e,s,t,n,a,l){await this.ensureProcess(a);const c=this.requireRpc(),p=await this.readState(),o=!!(p.isStreaming||p.isCompacting);(!o||!this.runId)&&(this.runId=P(),this.seq=0,this.terminal=!1,this.lastUsage=void 0,this.runPhase="thinking");const d=L(s);if(d.modelId&&!o&&(p.model?.id!==d.modelId||d.provider&&p.model?.provider!==d.provider)){if(!d.provider)throw new Error("Pi model selection must include provider/modelId");await c.request("set_model",{provider:d.provider,modelId:d.modelId})}t&&!o&&await c.request("set_thinking_level",{level:t});const r=o?l?.source==="room_activation"?"follow_up":"steer":"prompt",m={message:Z(e,n,this.workDir,this.externalChannel,this.shennianSessionId,null)},S=K(n,!this.managedAgentPolicy);S.length&&!this.modelBroker&&(m.images=S),this.modelBroker?.setTurnActive(!0);try{await c.request(r,m)}catch(_){throw this.modelBroker?.setTurnActive(!1),_}}async resume(e){await this.stopProcess(),this.agentSessionId=e}async stop(){const e=this.rpc;e&&(await e.request("clear_queue").catch(()=>{}),await e.request("abort").catch(()=>{})),!this.terminal&&this.runId&&this.emitTerminal({state:"aborted"}),await this.stopProcess()}async getStatus(){if(!this.rpc||!this.process)return{active:!1,runId:null,canStop:!1};const e=await this.readState().catch(()=>null),s=!!(e?.isStreaming||e?.isCompacting||this.active);return{active:s,runId:s?this.runId:null,runPhase:s?this.runPhase??"thinking":null,canStop:s}}async setTitle(e,s){this.rpc&&await this.rpc.request("set_session_name",{name:s.slice(0,200)})}async ensureProcess(e){if(this.process&&this.rpc)return;const s=v("pi-coding-agent");if(!s)throw new Error('Command "pi" not found. Install @earendil-works/pi-coding-agent from the official package.');const t=["--mode","rpc","--session-dir",this.sessionDir,"--offline"];this.agentSessionId&&t.push("--session",this.agentSessionId);const n=await this.prepareHostedModel();if(n&&t.push("--provider","shennian-dashscope","--model","deepseek-v4-flash-0731"),this.managedAgentPolicy&&!w(this.managedAgentPolicy)){t.push("--no-extensions","--no-skills","--no-prompt-templates","--no-context-files");const r=this.managedTools(),m=q({outputPath:u.join(f("runtime","pi-coding-agent",g(this.shennianSessionId)),"managed-extension.ts"),policy:this.managedAgentPolicy,allowRoomControl:!!this.extraEnv.SHENNIAN_MANAGED_ROOM_CONTEXT,allowManagerControl:!!this.extraEnv.SHENNIAN_MANAGER_SESSION_ID,allowHostShell:this.managedAgentPolicy.adapterPolicy.fileAccess==="host-full"});t.push("--no-builtin-tools","--extension",m),r.length?t.push("--tools",r.join(",")):t.push("--no-tools")}const a=M(this.workDir,this.externalChannel,this.shennianSessionId,e),l=f("runtime","pi-coding-agent",g(this.shennianSessionId));h.mkdirSync(l,{recursive:!0,mode:448}),this.promptFile=u.join(l,"instructions.md"),h.writeFileSync(this.promptFile,a,{encoding:"utf8",mode:384}),process.platform!=="win32"&&h.chmodSync(this.promptFile,384),t.push("--append-system-prompt",this.promptFile);const c={NO_COLOR:"1",...n?{PI_CODING_AGENT_DIR:n.configDir}:{}},p=this.managedAgentPolicy&&!w(this.managedAgentPolicy)?T({agentType:this.type,providerEnv:this.extraEnv,sessionEnv:this.extraEnv,runtimeEnv:c}):this.managedAgentPolicy?N({...c,...this.extraEnv}):x({...c,...this.extraEnv});let o;try{o=k(s,t,{cwd:this.workDir,stdio:["pipe","pipe","pipe"],env:p})}catch(r){throw await this.stopModelBroker(),r}this.process=o,this.stderr="",o.stderr?.on("data",r=>{const m=typeof r=="string"?r:r.toString("utf8");this.stderr=y(`${this.stderr}${m}`,F)});const d=new C(o);this.rpc=d,d.on("event",r=>this.handleRpcEvent(r)),d.on("protocolError",r=>this.emitError(r.message)),o.once("close",r=>{this.process===o&&(this.process=null,this.rpc=null,this.terminal||this.emitError(r===0?"Pi RPC process closed before agent_settled":`Pi RPC process exited with code ${r??"unknown"}${this.stderr?`: ${this.stderr}`:""}`),this.stopModelBroker())}),o.once("error",r=>{this.process===o&&this.emitError(r.message)});try{const r=await this.readState();if(!r.sessionId)throw new Error("Pi RPC did not return a session ID");if(r.isStreaming||r.isCompacting)throw new Error("Pi RPC session is not idle after startup");if(this.agentSessionId&&r.sessionId!==this.agentSessionId)throw new Error("Pi RPC resumed a different session");if(!r.sessionFile||!this.ownsSessionFile(r.sessionFile))throw new Error("Pi RPC returned a session file outside the Shennian-owned directory");this.agentSessionId=r.sessionId,this.persistNativeSession&&j(r.sessionId,this.sessionDir)}catch(r){throw await this.stopProcess(),r}}managedTools(){const e=this.managedAgentPolicy?.adapterPolicy;if(!e)return[];const s=["read","grep","find","ls"];(e.fileAccess==="workspace-write"||e.fileAccess==="host-full")&&s.push("edit","write");const t=!!this.extraEnv.SHENNIAN_MANAGER_SESSION_ID,n=!!this.extraEnv.SHENNIAN_MANAGED_ROOM_CONTEXT;return(t||n||e.fileAccess==="host-full")&&s.push("bash"),s}ownsSessionFile(e){const s=u.resolve(this.sessionDir),t=u.resolve(e),n=u.relative(s,t);return n!==""&&!n.startsWith("..")&&!u.isAbsolute(n)}async readState(){const e=await this.requireRpc().request("get_state");return G(e)}requireRpc(){if(!this.rpc)throw new Error("Pi RPC process is not running");return this.rpc}handleRpcEvent(e){if(e.type==="agent_start"){this.active=!0,this.runPhase="thinking",this.emitEvent({state:"start",agentSessionId:this.agentSessionId??void 0});return}if(e.type==="message_update"){this.lastUsage=I(e.usage)??this.lastUsage;const s=e.assistantMessageEvent;if(!s||typeof s!="object")return;const t=s;t.type==="text_delta"&&typeof t.delta=="string"?(this.runPhase="streaming_text",this.emitEvent({state:"delta",text:t.delta})):t.type==="thinking_delta"&&typeof t.delta=="string"&&(this.runPhase="thinking",this.emitEvent({state:"delta",text:t.delta,thinking:!0}));return}if(e.type==="tool_execution_start"){this.runPhase="tool_running",this.emitEvent({state:"tool-call",name:typeof e.toolName=="string"?e.toolName:"tool",args:e.args&&typeof e.args=="object"&&!Array.isArray(e.args)?e.args:{}});return}if(e.type==="tool_execution_end"){this.runPhase="thinking",this.emitEvent({state:"tool-result",name:typeof e.toolName=="string"?e.toolName:"tool",result:z(e.result)||X(e.result)});return}if(e.type==="tool_execution_update"){this.emitEvent({state:"notify",runPhase:"tool_running",message:"Pi tool is running"});return}if(e.type==="message_end"){const s=e.message;if(s&&typeof s=="object"){const t=s;this.lastUsage=I(t.usage)??this.lastUsage,t.stopReason==="error"&&(this.modelBroker?.setTurnActive(!1),this.emitError(typeof t.errorMessage=="string"?t.errorMessage:"Pi model request failed"))}return}if(e.type==="compaction_start"){this.runPhase="thinking",this.emitEvent({state:"notify",runPhase:"thinking",message:"Pi is compacting context"});return}if(e.type==="auto_retry_start"||e.type==="summarization_retry_scheduled"){this.runPhase="thinking",this.emitEvent({state:"notify",runPhase:"thinking",message:"Pi is retrying"});return}if(e.type==="extension_ui_request"){typeof e.id=="string"&&this.rpc?.notify("extension_ui_response",{id:e.id,cancelled:!0});return}if(e.type==="extension_error"){this.modelBroker?.setTurnActive(!1),this.emitError(typeof e.error=="string"?e.error:"Pi extension failed");return}e.type==="agent_settled"&&(this.active=!1,this.modelBroker?.setTurnActive(!1),this.emitTerminal({state:"final",agentSessionId:this.agentSessionId??void 0,usage:this.lastUsage}))}emitEvent(e){this.runId&&this.emit("agentEvent",{...e,runId:this.runId,seq:this.seq++})}emitTerminal(e){this.terminal||(this.terminal=!0,this.active=!1,this.runPhase=null,this.emitEvent(e))}emitError(e){this.modelBroker?.setTurnActive(!1),this.emitTerminal({state:"error",message:H(e)})}async prepareHostedModel(){const e=R(),s=e.machineToken?.trim();if(!s)return null;const t=new O({serverUrl:e.serverUrl?.trim()||"https://shennian.net",machineToken:s,sessionId:this.shennianSessionId}),n=await t.start(),a=f("runtime","pi-coding-agent",g(this.shennianSessionId),"agent-config");h.mkdirSync(a,{recursive:!0,mode:448}),process.platform!=="win32"&&h.chmodSync(a,448);const l=u.join(a,"models.json"),c={providers:{"shennian-dashscope":{baseUrl:n.baseUrl,api:"openai-completions",apiKey:n.apiKey,authHeader:!0,compat:{supportsDeveloperRole:!1,supportsReasoningEffort:!0},models:[{id:"deepseek-v4-flash-0731",name:"DeepSeek V4 Flash",reasoning:!0,input:["text"],thinkingLevelMap:{off:null,minimal:null,low:"low",medium:null,high:"high",xhigh:null,max:"max"}}]}}};return h.writeFileSync(l,`${JSON.stringify(c,null,2)}
4
- `,{encoding:"utf8",mode:384}),process.platform!=="win32"&&h.chmodSync(l,384),this.modelBroker=t,{configDir:a}}async stopModelBroker(){const e=this.modelBroker;this.modelBroker=null,await e?.stop().catch(()=>{})}async stopProcess(){const e=this.process;this.process=null,this.rpc?.close(),this.rpc=null,e?(e.kill("SIGTERM"),await new Promise(s=>{let t=!1;const n=()=>{t||(t=!0,s())};e.once("close",n),setTimeout(()=>{e.kill("SIGKILL"),n()},$).unref()}),await this.stopModelBroker()):await this.stopModelBroker(),this.validationRoot&&(h.rmSync(this.validationRoot,{recursive:!0,force:!0}),this.validationRoot=null)}}A("pi-coding-agent",()=>new W);export{W as PiCodingAgentAdapter};
1
+ import h from"node:fs";import u from"node:path";import{randomUUID as P}from"node:crypto";import{AgentAdapter as E,registerAgent as v}from"./adapter.js";import{resolveBuiltinCommand as A,spawnAgentCommand as k}from"./command-spec.js";import{buildAgentProcessEnv as x,buildManagedAgentProcessEnv as R,buildManagedNativeFullProcessEnv as T}from"../agent-env.js";import{loadConfig as N,resolveShennianPath as f}from"../config/index.js";import{buildInlineChannelPreamble as b,buildPlatformInstructions as M}from"./platform-instructions.js";import{isManagedAgentNativeFull as w}from"../security/managed-agent-policy.js";import{PiRpcClient as C}from"./pi-rpc-client.js";import{registerManagedAgentRuntimeSecurity as D}from"../security/managed-agent-runtime.js";import{PI_CODING_AGENT_MANAGED_RUNTIME_SECURITY as B}from"./pi-coding-agent-managed-permissions.js";import{PiManagedModelBroker as O}from"./pi-managed-model-broker.js";import{writePiManagedExtension as j}from"./pi-managed-extension.js";import{recordPiSessionOwnership as q}from"./pi-session-ownership.js";D("pi-coding-agent",B);const F=32*1024,U=256*1024,$=3e3;function g(i){return i.replace(/[^a-zA-Z0-9_.-]/g,"_").slice(0,180)||"session"}function G(i){return i.data&&typeof i.data=="object"&&!Array.isArray(i.data)?i.data:{}}function z(i){if(typeof i=="string")return i;if(!i||typeof i!="object")return"";const t=i.content;return Array.isArray(t)?t.map(s=>{if(!s||typeof s!="object")return"";const e=s;return e.type==="text"&&typeof e.text=="string"?e.text:""}).join(""):""}function y(i,t=U){return i.length<=t?i:`${i.slice(0,t)}
2
+ [truncated]`}function H(i){return y(i,2e3).replace(/Bearer\s+[A-Za-z0-9._~-]+/gi,"Bearer [redacted]").replace(/\b(?:pimc|mrc)_[A-Za-z0-9_-]{20,}\b/g,"[redacted-capability]").replace(/\bsk-[A-Za-z0-9_-]{12,}\b/g,"[redacted-key]")}function X(i){try{return y(JSON.stringify(i))}catch{return"[unserializable]"}}function I(i){if(!i||typeof i!="object")return;const t=i;if(!(typeof t.input!="number"&&typeof t.output!="number"))return{inputTokens:typeof t.input=="number"?t.input:0,outputTokens:typeof t.output=="number"?t.output:0}}function L(i){const t=i?.trim();if(!t)return{};const s=t.indexOf("/");return s<=0||s===t.length-1?{modelId:t}:{provider:t.slice(0,s),modelId:t.slice(s+1)}}function Z(i,t,s,e,n,a){const l=b(s,e,n,a),c=t?.filter(o=>o.kind!=="image")??[];if(!c.length)return`${l}${i}`;const p=c.map(o=>`[${o.name}](<${o.path}>)`);return`${l}${[i,"",...p].join(`
3
+ `)}`}function K(i,t){const s=[];for(const e of i??[]){if(e.kind!=="image"||!e.mimeType.startsWith("image/"))continue;let n=e.previewData?.trim()??"";const a=n.indexOf(",");if(n.startsWith("data:")&&a>=0&&(n=n.slice(a+1)),!n&&t&&e.path&&!/^https?:\/\//i.test(e.path))try{const l=h.statSync(e.path);l.isFile()&&l.size<=10*1024*1024&&(n=h.readFileSync(e.path).toString("base64"))}catch{n=""}n&&s.push({type:"image",data:n,mimeType:e.mimeType})}return s}class W extends E{type="pi-coding-agent";get recoveryCapabilities(){return{inspectTerminal:!1,reattachRun:!1,resumeRun:!1,resumeSession:!0}}process=null;rpc=null;shennianSessionId="";workDir="";sessionDir="";agentSessionId=null;externalChannel=null;extraEnv={};managedAgentPolicy=null;runId="";seq=0;active=!1;terminal=!0;stderr="";lastUsage;promptFile=null;modelBroker=null;persistNativeSession=!0;validationRoot=null;runPhase=null;configure(t){t.sessionId&&(this.shennianSessionId=t.sessionId),this.externalChannel=t.externalChannel??null,this.extraEnv=t.env??{},"managedAgentPolicy"in t&&(this.managedAgentPolicy=t.managedAgentPolicy??null)}async start(t,s,e,n){await this.stopProcess(),this.shennianSessionId=t,this.workDir=this.managedAgentPolicy?.adapterPolicy.workDir??s,this.agentSessionId=e??null;const a=f("runtime","pi-coding-agent",g(t));this.persistNativeSession=n?.persistNativeSession!==!1,this.validationRoot=this.persistNativeSession?null:u.join(a,`validation-${P()}`),this.sessionDir=this.validationRoot?u.join(this.validationRoot,"sessions"):u.join(a,"sessions"),h.mkdirSync(this.sessionDir,{recursive:!0,mode:448}),process.platform!=="win32"&&h.chmodSync(this.sessionDir,448),this.active=!1,this.terminal=!0}async send(t,s,e,n,a,l){await this.ensureProcess(a);const c=this.requireRpc(),p=await this.readState(),o=!!(p.isStreaming||p.isCompacting);(!o||!this.runId)&&(this.runId=P(),this.seq=0,this.terminal=!1,this.lastUsage=void 0,this.runPhase="thinking");const d=L(s);if(d.modelId&&!o&&(p.model?.id!==d.modelId||d.provider&&p.model?.provider!==d.provider)){if(!d.provider)throw new Error("Pi model selection must include provider/modelId");await c.request("set_model",{provider:d.provider,modelId:d.modelId})}e&&!o&&await c.request("set_thinking_level",{level:e});const r=o?l?.source==="room_activation"?"follow_up":"steer":"prompt",m={message:Z(t,n,this.workDir,this.externalChannel,this.shennianSessionId,null)},S=K(n,!this.managedAgentPolicy);S.length&&!this.modelBroker&&(m.images=S),this.modelBroker?.setTurnActive(!0);try{await c.request(r,m)}catch(_){throw this.modelBroker?.setTurnActive(!1),_}}async resume(t){await this.stopProcess(),this.agentSessionId=t}async stop(){const t=this.rpc;t&&(await t.request("clear_queue").catch(()=>{}),await t.request("abort").catch(()=>{})),!this.terminal&&this.runId&&this.emitTerminal({state:"aborted"}),await this.stopProcess()}async getStatus(){if(!this.rpc||!this.process)return{active:!1,runId:null,canStop:!1};const t=await this.readState().catch(()=>null),s=!!(t?.isStreaming||t?.isCompacting||this.active);return{active:s,runId:s?this.runId:null,runPhase:s?this.runPhase??"thinking":null,canStop:s}}async setTitle(t,s){this.rpc&&await this.rpc.request("set_session_name",{name:s.slice(0,200)})}async ensureProcess(t){if(this.process&&this.rpc)return;const s=A("pi-coding-agent");if(!s)throw new Error('Command "pi" not found. Install @earendil-works/pi-coding-agent from the official package.');const e=["--mode","rpc","--session-dir",this.sessionDir,"--offline"];this.agentSessionId&&e.push("--session",this.agentSessionId);const n=await this.prepareHostedModel();if(n&&e.push("--provider","shennian-dashscope","--model","deepseek-v4-flash-0731"),this.managedAgentPolicy&&!w(this.managedAgentPolicy)){e.push("--no-extensions","--no-skills","--no-prompt-templates","--no-context-files");const r=this.managedTools(),m=j({outputPath:u.join(f("runtime","pi-coding-agent",g(this.shennianSessionId)),"managed-extension.ts"),policy:this.managedAgentPolicy,allowRoomControl:!!this.extraEnv.SHENNIAN_MANAGED_ROOM_CONTEXT,allowManagerControl:!!this.extraEnv.SHENNIAN_MANAGER_SESSION_ID,allowHostShell:this.managedAgentPolicy.adapterPolicy.fileAccess==="host-full"});e.push("--no-builtin-tools","--extension",m),r.length?e.push("--tools",r.join(",")):e.push("--no-tools")}const a=M(this.workDir,this.externalChannel,this.shennianSessionId,t),l=this.validationRoot??f("runtime","pi-coding-agent",g(this.shennianSessionId));h.mkdirSync(l,{recursive:!0,mode:448}),this.promptFile=u.join(l,"instructions.md"),h.writeFileSync(this.promptFile,a,{encoding:"utf8",mode:384}),process.platform!=="win32"&&h.chmodSync(this.promptFile,384),e.push("--append-system-prompt",this.promptFile);const c={NO_COLOR:"1",...n?{PI_CODING_AGENT_DIR:n.configDir}:{}},p=this.managedAgentPolicy&&!w(this.managedAgentPolicy)?R({agentType:this.type,providerEnv:this.extraEnv,sessionEnv:this.extraEnv,runtimeEnv:c}):this.managedAgentPolicy?T({...c,...this.extraEnv}):x({...c,...this.extraEnv});let o;try{o=k(s,e,{cwd:this.workDir,stdio:["pipe","pipe","pipe"],env:p})}catch(r){throw await this.stopModelBroker(),r}this.process=o,this.stderr="",o.stderr?.on("data",r=>{const m=typeof r=="string"?r:r.toString("utf8");this.stderr=y(`${this.stderr}${m}`,F)});const d=new C(o);this.rpc=d,d.on("event",r=>this.handleRpcEvent(r)),d.on("protocolError",r=>this.emitError(r.message)),o.once("close",r=>{this.process===o&&(this.process=null,this.rpc=null,this.terminal||this.emitError(r===0?"Pi RPC process closed before agent_settled":`Pi RPC process exited with code ${r??"unknown"}${this.stderr?`: ${this.stderr}`:""}`),this.stopModelBroker())}),o.once("error",r=>{this.process===o&&this.emitError(r.message)});try{const r=await this.readState();if(!r.sessionId)throw new Error("Pi RPC did not return a session ID");if(r.isStreaming||r.isCompacting)throw new Error("Pi RPC session is not idle after startup");if(this.agentSessionId&&r.sessionId!==this.agentSessionId)throw new Error("Pi RPC resumed a different session");if(!r.sessionFile||!this.ownsSessionFile(r.sessionFile))throw new Error("Pi RPC returned a session file outside the Shennian-owned directory");this.agentSessionId=r.sessionId,this.persistNativeSession&&q(r.sessionId,this.sessionDir)}catch(r){throw await this.stopProcess(),r}}managedTools(){const t=this.managedAgentPolicy?.adapterPolicy;if(!t)return[];const s=["read","grep","find","ls"];(t.fileAccess==="workspace-write"||t.fileAccess==="host-full")&&s.push("edit","write");const e=!!this.extraEnv.SHENNIAN_MANAGER_SESSION_ID,n=!!this.extraEnv.SHENNIAN_MANAGED_ROOM_CONTEXT;return(e||n||t.fileAccess==="host-full")&&s.push("bash"),s}ownsSessionFile(t){const s=u.resolve(this.sessionDir),e=u.resolve(t),n=u.relative(s,e);return n!==""&&!n.startsWith("..")&&!u.isAbsolute(n)}async readState(){const t=await this.requireRpc().request("get_state");return G(t)}requireRpc(){if(!this.rpc)throw new Error("Pi RPC process is not running");return this.rpc}handleRpcEvent(t){if(t.type==="agent_start"){this.active=!0,this.runPhase="thinking",this.emitEvent({state:"start",agentSessionId:this.agentSessionId??void 0});return}if(t.type==="message_update"){this.lastUsage=I(t.usage)??this.lastUsage;const s=t.assistantMessageEvent;if(!s||typeof s!="object")return;const e=s;e.type==="text_delta"&&typeof e.delta=="string"?(this.runPhase="streaming_text",this.emitEvent({state:"delta",text:e.delta})):e.type==="thinking_delta"&&typeof e.delta=="string"&&(this.runPhase="thinking",this.emitEvent({state:"delta",text:e.delta,thinking:!0}));return}if(t.type==="tool_execution_start"){this.runPhase="tool_running",this.emitEvent({state:"tool-call",name:typeof t.toolName=="string"?t.toolName:"tool",args:t.args&&typeof t.args=="object"&&!Array.isArray(t.args)?t.args:{}});return}if(t.type==="tool_execution_end"){this.runPhase="thinking",this.emitEvent({state:"tool-result",name:typeof t.toolName=="string"?t.toolName:"tool",result:z(t.result)||X(t.result)});return}if(t.type==="tool_execution_update"){this.emitEvent({state:"notify",runPhase:"tool_running",message:"Pi tool is running"});return}if(t.type==="message_end"){const s=t.message;if(s&&typeof s=="object"){const e=s;this.lastUsage=I(e.usage)??this.lastUsage,e.stopReason==="error"&&(this.modelBroker?.setTurnActive(!1),this.emitError(typeof e.errorMessage=="string"?e.errorMessage:"Pi model request failed"))}return}if(t.type==="compaction_start"){this.runPhase="thinking",this.emitEvent({state:"notify",runPhase:"thinking",message:"Pi is compacting context"});return}if(t.type==="auto_retry_start"||t.type==="summarization_retry_scheduled"){this.runPhase="thinking",this.emitEvent({state:"notify",runPhase:"thinking",message:"Pi is retrying"});return}if(t.type==="extension_ui_request"){typeof t.id=="string"&&this.rpc?.notify("extension_ui_response",{id:t.id,cancelled:!0});return}if(t.type==="extension_error"){this.modelBroker?.setTurnActive(!1),this.emitError(typeof t.error=="string"?t.error:"Pi extension failed");return}t.type==="agent_settled"&&(this.active=!1,this.modelBroker?.setTurnActive(!1),this.emitTerminal({state:"final",agentSessionId:this.agentSessionId??void 0,usage:this.lastUsage}))}emitEvent(t){this.runId&&this.emit("agentEvent",{...t,runId:this.runId,seq:this.seq++})}emitTerminal(t){this.terminal||(this.terminal=!0,this.active=!1,this.runPhase=null,this.emitEvent(t))}emitError(t){this.modelBroker?.setTurnActive(!1),this.emitTerminal({state:"error",message:H(t)})}async prepareHostedModel(){const t=N(),s=t.machineToken?.trim();if(!s)return null;const e=new O({serverUrl:t.serverUrl?.trim()||"https://shennian.net",machineToken:s,sessionId:this.shennianSessionId}),n=await e.start(),a=this.validationRoot?u.join(this.validationRoot,"agent-config"):f("runtime","pi-coding-agent",g(this.shennianSessionId),"agent-config");h.mkdirSync(a,{recursive:!0,mode:448}),process.platform!=="win32"&&h.chmodSync(a,448);const l=u.join(a,"models.json"),c={providers:{"shennian-dashscope":{baseUrl:n.baseUrl,api:"openai-completions",apiKey:n.apiKey,authHeader:!0,compat:{supportsDeveloperRole:!1,supportsReasoningEffort:!0},models:[{id:"deepseek-v4-flash-0731",name:"DeepSeek V4 Flash",reasoning:!0,input:["text"],thinkingLevelMap:{off:null,minimal:null,low:"low",medium:null,high:"high",xhigh:null,max:"max"}}]}}};return h.writeFileSync(l,`${JSON.stringify(c,null,2)}
4
+ `,{encoding:"utf8",mode:384}),process.platform!=="win32"&&h.chmodSync(l,384),this.modelBroker=e,{configDir:a}}async stopModelBroker(){const t=this.modelBroker;this.modelBroker=null,await t?.stop().catch(()=>{})}async stopProcess(){const t=this.process;this.process=null,this.rpc?.close(),this.rpc=null,t?(t.kill("SIGTERM"),await new Promise(s=>{let e=!1;const n=()=>{e||(e=!0,s())};t.once("close",n),setTimeout(()=>{t.kill("SIGKILL"),n()},$).unref()}),await this.stopModelBroker()):await this.stopModelBroker(),this.validationRoot&&(h.rmSync(this.validationRoot,{recursive:!0,force:!0}),this.validationRoot=null)}}v("pi-coding-agent",()=>new W);export{W as PiCodingAgentAdapter};
@@ -1 +1 @@
1
- import f from"node:http";import{randomBytes as c,timingSafeEqual as y}from"node:crypto";const b=20*1024*1024,p=18e4;function _(a){return a?.startsWith("Bearer ")?a.slice(7):""}function g(a,t){const e=Buffer.from(a),r=Buffer.from(t);return e.length===r.length&&y(e,r)}async function v(a){const t=[];let e=0;for await(const r of a){const i=Buffer.isBuffer(r)?r:Buffer.from(r);if(e+=i.length,e>b)throw new Error("request_too_large");t.push(i)}return Buffer.concat(t)}class k{options;server=null;capability=c(32).toString("base64url");turnActive=!1;port=0;upstreamCapability=null;upstreamCapabilityExpiresAt=0;constructor(t){this.options=t}async start(){if(this.server)return this.connection();const t=f.createServer((r,i)=>{this.handle(r,i)});t.requestTimeout=p,t.headersTimeout=1e4,await new Promise((r,i)=>{t.once("error",i),t.listen(0,"127.0.0.1",()=>{t.removeListener("error",i),r()})});const e=t.address();if(!e||typeof e=="string")throw t.close(),new Error("pi_model_broker_address_unavailable");return this.server=t,this.port=e.port,this.connection()}setTurnActive(t){this.turnActive=t,t||this.revokeUpstreamCapability()}async stop(){this.turnActive=!1,await this.revokeUpstreamCapability(),this.capability=c(32).toString("base64url");const t=this.server;this.server=null,this.port=0,t&&await new Promise(e=>t.close(()=>e()))}connection(){if(!this.port)throw new Error("pi_model_broker_not_started");return{baseUrl:`http://127.0.0.1:${this.port}/v1`,apiKey:this.capability}}async handle(t,e){e.setHeader("Cache-Control","no-store");const r=_(t.headers.authorization);if(!r||!g(r,this.capability)){this.json(e,401,{error:{message:"Unauthorized",type:"authentication_error"}});return}if(t.method==="GET"&&t.url==="/v1/models"){this.json(e,200,{object:"list",data:[{id:"deepseek-v4-flash-0731",object:"model",owned_by:"shennian"}]});return}if(t.method!=="POST"||t.url!=="/v1/chat/completions"){this.json(e,404,{error:{message:"Not found",type:"invalid_request_error"}});return}if(!this.turnActive){this.json(e,403,{error:{message:"Managed model capability is not active",type:"permission_error"}});return}try{const i=await v(t),o=await this.ensureUpstreamCapability(),n=new AbortController;t.once("aborted",()=>n.abort());const h=setTimeout(()=>n.abort(),p);let s;try{s=await(this.options.fetchImpl??fetch)(`${this.options.serverUrl.replace(/\/$/,"")}/api/managed-model/v1/chat/completions`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${o}`},body:i.toString("utf8"),signal:n.signal})}finally{clearTimeout(h)}if(e.statusCode=s.status,e.setHeader("content-type",s.headers.get("content-type")||"application/json; charset=utf-8"),!s.body){e.end();return}const l=s.body.getReader();try{for(;;){const{done:u,value:d}=await l.read();if(u)break;e.write(Buffer.from(d))||await new Promise(m=>e.once("drain",m))}}finally{l.releaseLock()}e.end()}catch(i){if(e.headersSent){e.destroy();return}const o=i instanceof Error&&i.message==="request_too_large"?413:502;this.json(e,o,{error:{message:o===413?"Request too large":"Managed model request failed",type:o===413?"invalid_request_error":"upstream_error"}})}}json(t,e,r){t.statusCode=e,t.setHeader("content-type","application/json; charset=utf-8"),t.end(JSON.stringify(r))}async ensureUpstreamCapability(){if(this.upstreamCapability&&this.upstreamCapabilityExpiresAt>Date.now()+1e4)return this.upstreamCapability;const t=await(this.options.fetchImpl??fetch)(`${this.options.serverUrl.replace(/\/$/,"")}/api/managed-model/v1/capabilities`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${this.options.machineToken}`},body:JSON.stringify({sessionId:this.options.sessionId,agentType:"pi-coding-agent"})});if(!t.ok)throw new Error("managed_model_capability_issue_failed");const e=await t.json(),r=typeof e.expiresAt=="string"?Date.parse(e.expiresAt):NaN;if(typeof e.token!="string"||!/^pimc_[A-Za-z0-9_-]{43}$/.test(e.token)||e.model!=="deepseek-v4-flash-0731"||!Number.isFinite(r))throw new Error("managed_model_capability_response_invalid");return this.upstreamCapability=e.token,this.upstreamCapabilityExpiresAt=r,e.token}async revokeUpstreamCapability(){const t=this.upstreamCapability;this.upstreamCapability=null,this.upstreamCapabilityExpiresAt=0,t&&await(this.options.fetchImpl??fetch)(`${this.options.serverUrl.replace(/\/$/,"")}/api/managed-model/v1/capabilities`,{method:"DELETE",headers:{authorization:`Bearer ${t}`}}).catch(()=>{})}}export{k as PiManagedModelBroker};
1
+ import f from"node:http";import{randomBytes as c,timingSafeEqual as y}from"node:crypto";const b=20*1024*1024,p=18e4;function g(a){return a?.startsWith("Bearer ")?a.slice(7):""}function _(a,t){const e=Buffer.from(a),r=Buffer.from(t);return e.length===r.length&&y(e,r)}async function v(a){const t=[];let e=0;for await(const r of a){const i=Buffer.isBuffer(r)?r:Buffer.from(r);if(e+=i.length,e>b)throw new Error("request_too_large");t.push(i)}return Buffer.concat(t)}class C{options;server=null;capability=c(32).toString("base64url");turnActive=!1;port=0;upstreamCapability=null;upstreamCapabilityExpiresAt=0;constructor(t){this.options=t}async start(){if(this.server)return this.connection();const t=f.createServer((r,i)=>{this.handle(r,i)});t.requestTimeout=p,t.headersTimeout=1e4,await new Promise((r,i)=>{t.once("error",i),t.listen(0,"127.0.0.1",()=>{t.removeListener("error",i),r()})});const e=t.address();if(!e||typeof e=="string")throw t.close(),new Error("pi_model_broker_address_unavailable");return this.server=t,this.port=e.port,this.connection()}setTurnActive(t){this.turnActive=t,t||this.revokeUpstreamCapability()}async stop(){this.turnActive=!1,await this.revokeUpstreamCapability(),this.capability=c(32).toString("base64url");const t=this.server;this.server=null,this.port=0,t&&await new Promise(e=>t.close(()=>e()))}connection(){if(!this.port)throw new Error("pi_model_broker_not_started");return{baseUrl:`http://127.0.0.1:${this.port}/v1`,apiKey:this.capability}}async handle(t,e){e.setHeader("Cache-Control","no-store");const r=g(t.headers.authorization);if(!r||!_(r,this.capability)){this.json(e,401,{error:{message:"Unauthorized",type:"authentication_error"}});return}if(t.method==="GET"&&t.url==="/v1/models"){this.json(e,200,{object:"list",data:[{id:"deepseek-v4-flash-0731",object:"model",owned_by:"shennian"}]});return}if(t.method!=="POST"||t.url!=="/v1/chat/completions"){this.json(e,404,{error:{message:"Not found",type:"invalid_request_error"}});return}if(!this.turnActive){this.json(e,403,{error:{message:"Managed model capability is not active",type:"permission_error"}});return}try{const i=await v(t),o=await this.ensureUpstreamCapability();if(e.destroyed||!this.turnActive){await this.revokeUpstreamCapability();return}const n=new AbortController;t.once("aborted",()=>n.abort()),e.once("close",()=>n.abort());const h=setTimeout(()=>n.abort(),p);let s;try{s=await(this.options.fetchImpl??fetch)(`${this.options.serverUrl.replace(/\/$/,"")}/api/managed-model/v1/chat/completions`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${o}`},body:i.toString("utf8"),signal:n.signal})}finally{clearTimeout(h)}if(e.statusCode=s.status,e.setHeader("content-type",s.headers.get("content-type")||"application/json; charset=utf-8"),!s.body){e.end();return}const l=s.body.getReader();try{for(;;){const{done:u,value:d}=await l.read();if(u)break;e.write(Buffer.from(d))||await new Promise(m=>e.once("drain",m))}}finally{l.releaseLock()}e.end()}catch(i){if(e.headersSent){e.destroy();return}const o=i instanceof Error&&i.message==="request_too_large"?413:502;this.json(e,o,{error:{message:o===413?"Request too large":"Managed model request failed",type:o===413?"invalid_request_error":"upstream_error"}})}}json(t,e,r){t.statusCode=e,t.setHeader("content-type","application/json; charset=utf-8"),t.end(JSON.stringify(r))}async ensureUpstreamCapability(){if(this.upstreamCapability&&this.upstreamCapabilityExpiresAt>Date.now()+1e4)return this.upstreamCapability;const t=await(this.options.fetchImpl??fetch)(`${this.options.serverUrl.replace(/\/$/,"")}/api/managed-model/v1/capabilities`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${this.options.machineToken}`},signal:AbortSignal.timeout(5e3),body:JSON.stringify({sessionId:this.options.sessionId,agentType:"pi-coding-agent"})});if(!t.ok)throw new Error("managed_model_capability_issue_failed");const e=await t.json(),r=typeof e.expiresAt=="string"?Date.parse(e.expiresAt):NaN;if(typeof e.token!="string"||!/^pimc_[A-Za-z0-9_-]{43}$/.test(e.token)||e.model!=="deepseek-v4-flash-0731"||!Number.isFinite(r))throw new Error("managed_model_capability_response_invalid");return this.upstreamCapability=e.token,this.upstreamCapabilityExpiresAt=r,e.token}async revokeUpstreamCapability(){const t=this.upstreamCapability;this.upstreamCapability=null,this.upstreamCapabilityExpiresAt=0,t&&await(this.options.fetchImpl??fetch)(`${this.options.serverUrl.replace(/\/$/,"")}/api/managed-model/v1/capabilities`,{method:"DELETE",headers:{authorization:`Bearer ${t}`},signal:AbortSignal.timeout(5e3)}).catch(()=>{})}}export{C as PiManagedModelBroker};
@@ -1,3 +1,4 @@
1
+ import { type PiValidationSession } from './pi-validation-session.js';
1
2
  import { type AgentType, type ReqFrame, type SessionMode } from '@shennian/wire';
2
3
  import { type AgentAdapter } from '../../agents/adapter.js';
3
4
  import type { SessionManagerRuntime } from '../types.js';
@@ -14,6 +15,7 @@ export type AgentWorkspaceValidationResult = {
14
15
  errorCode: string;
15
16
  };
16
17
  type ValidationDependencies = {
18
+ registerPiSession?: () => Promise<PiValidationSession>;
17
19
  createAdapter?: (type: AgentType) => AgentAdapter | null;
18
20
  resolveWorkDir?: (workDir: string) => Promise<string>;
19
21
  timeoutMs?: number;
@@ -1 +1 @@
1
- import k from"node:fs/promises";import{randomUUID as A}from"node:crypto";import{isAvailableAgentType as p}from"@shennian/wire";import{createAgent as y}from"../../agents/adapter.js";import{buildManagedAgentEnv as E}from"../../agents/config-status.js";import{ManagerModeAdapter as I}from"../../agents/manager.js";import{NativeSessionLifecycleRegistry as h}from"../native-session-lifecycle.js";const b="\u8BF7\u56DE\u590D hello\uFF0C\u7528\u4E8E\u786E\u8BA4\u4F60\u5DF2\u51C6\u5907\u597D\u3002",C=6e4,D=new h;async function M(e,r={}){if(!p(e.runtimeAdapter)||e.runtimeAdapter==="manager"||e.runtimeAdapter==="pi"||e.runtimeAdapter==="openclaw")return{ok:!1,errorCode:"agent_validation_runtime_unavailable"};if(e.sessionMode==="manager"&&!["codex","claude","pi-coding-agent"].includes(e.runtimeAdapter))return{ok:!1,errorCode:"agent_validation_manager_unavailable"};let o;try{o=await(r.resolveWorkDir??T)(e.workDir)}catch(t){return{ok:!1,errorCode:S(t)}}const n=(r.createAdapter??y)(e.runtimeAdapter);if(!n)return{ok:!1,errorCode:"agent_validation_runtime_unavailable"};const i=e.sessionMode==="manager"?new I(n,e.runtimeAdapter):n,m=`agent-validation-${A()}`;let l="",u="",c=null,d=null;const w=new Promise(t=>{d=t}),g=t=>{t.agentSessionId&&(l=t.agentSessionId),t.state==="delta"&&!t.thinking&&t.text&&(u+=t.text),t.state==="final"?(t.text&&(u+=t.text),d?.({kind:"final"})):(t.state==="error"||t.state==="aborted")&&d?.({kind:"error",error:t.message??t.state})},_=t=>d?.({kind:"error",error:t});i.on("agentEvent",g),i.on("error",_),i.configure?.({sessionId:m,env:E(e.runtimeAdapter)});try{try{await i.start(m,o,null,{persistNativeSession:!1})}catch(a){return{ok:!1,errorCode:v(a,"start")}}if(e.runtimeAdapter==="pi-coding-agent")return{ok:!0};const t=new Promise(a=>{c=setTimeout(()=>a({kind:"timeout"}),r.timeoutMs??C)}),f=i.send(b,e.modelId??void 0).then(()=>w).catch(a=>({kind:"error",error:a})),s=await Promise.race([f,t]);return s.kind==="timeout"?{ok:!1,errorCode:"agent_validation_response_timeout"}:s.kind==="error"?{ok:!1,errorCode:v(s.error,"response")}:u.toLowerCase().includes("hello")?{ok:!0}:{ok:!1,errorCode:"agent_validation_response_mismatch"}}finally{c&&clearTimeout(c),i.removeListener("agentEvent",g),i.removeListener("error",_),await i.stop().catch(()=>{}),l&&(r.cleanupNativeSession??((f,s,a)=>{D.execute(f,s,{workDir:a}).catch(()=>{})}))(e.runtimeAdapter,l,o)}}async function U(e,r){const o=N(r.params);if(!o){e.client.sendRes({type:"res",id:r.id,ok:!1,error:"agent_validation_invalid_configuration"});return}const n=await M(o);e.client.sendRes(n.ok?{type:"res",id:r.id,ok:!0,payload:{ready:!0}}:{type:"res",id:r.id,ok:!1,error:n.errorCode})}async function T(e){const r=e.trim();if(!r)throw Object.assign(new Error("workdir_required"),{code:"EINVAL"});const o=await k.realpath(r);if(!(await k.stat(o)).isDirectory())throw Object.assign(new Error("workdir_not_directory"),{code:"ENOTDIR"});return o}function S(e){const r=x(e);return r==="ENOENT"?"agent_validation_workdir_not_found":r==="ENOTDIR"?"agent_validation_workdir_not_directory":r==="EACCES"||r==="EPERM"?"agent_validation_workdir_unavailable":"agent_validation_workdir_invalid"}function v(e,r){const o=e instanceof Error?e.message:String(e);return/model.*(?:not found|unavailable|unsupported|invalid)|unknown model/i.test(o)?"agent_validation_model_unavailable":/not found|enoent|command .* missing|is .* installed/i.test(o)?"agent_validation_runtime_unavailable":/unauth|authentication|not logged|login required|api key|token.*missing|forbidden|401|403/i.test(o)?"agent_validation_runtime_auth_required":r==="start"?"agent_validation_start_failed":"agent_validation_response_failed"}function x(e){return typeof e=="object"&&e&&"code"in e?String(e.code):""}function N(e){const r=typeof e.runtimeAdapter=="string"?e.runtimeAdapter:"",o=typeof e.workDir=="string"?e.workDir:"",n=e.sessionMode==="manager"?"manager":"standard";return!p(r)||!o.trim()?null:{runtimeAdapter:r,workDir:o,sessionMode:n,modelId:typeof e.modelId=="string"?e.modelId:null}}export{U as handleAgentWorkspaceValidation,M as validateAgentWorkspaceConfiguration};
1
+ import{registerPiValidationSession as I}from"./pi-validation-session.js";import y from"node:fs/promises";import{randomUUID as C}from"node:crypto";import{isAvailableAgentType as A}from"@shennian/wire";import{createAgent as b}from"../../agents/adapter.js";import{buildManagedAgentEnv as D}from"../../agents/config-status.js";import{ManagerModeAdapter as S}from"../../agents/manager.js";import{NativeSessionLifecycleRegistry as M}from"../native-session-lifecycle.js";const T="\u8BF7\u56DE\u590D hello\uFF0C\u7528\u4E8E\u786E\u8BA4\u4F60\u5DF2\u51C6\u5907\u597D\u3002",x=6e4,N=new M;async function O(e,r={}){try{return await L(e,r)}catch(t){const i=t instanceof Error?t.message:"";return{ok:!1,errorCode:i==="agent_validation_cleanup_failed"?i:p(t,"start")}}}async function L(e,r){if(!A(e.runtimeAdapter)||e.runtimeAdapter==="manager"||e.runtimeAdapter==="pi"||e.runtimeAdapter==="openclaw")return{ok:!1,errorCode:"agent_validation_runtime_unavailable"};if(e.sessionMode==="manager"&&!["codex","claude","pi-coding-agent"].includes(e.runtimeAdapter))return{ok:!1,errorCode:"agent_validation_manager_unavailable"};let t;try{t=await(r.resolveWorkDir??P)(e.workDir)}catch(o){return{ok:!1,errorCode:R(o)}}const i=(r.createAdapter??b)(e.runtimeAdapter);if(!i)return{ok:!1,errorCode:"agent_validation_runtime_unavailable"};const n=e.sessionMode==="manager"?new S(i,e.runtimeAdapter):i;let u=`agent-validation-${C()}`,d,c="",f="",m=null,l=null;const E=new Promise(o=>{l=o}),k=o=>{o.agentSessionId&&(c=o.agentSessionId),o.state==="delta"&&!o.thinking&&o.text&&(f+=o.text),o.state==="final"?(o.text&&(f+=o.text),l?.({kind:"final"})):(o.state==="error"||o.state==="aborted")&&l?.({kind:"error",error:o.message??o.state})},v=o=>l?.({kind:"error",error:o});n.on("agentEvent",k),n.on("error",v);const h=async()=>{e.runtimeAdapter==="pi-coding-agent"&&(d=await(r.registerPiSession??I)(),u=d.sessionId),n.configure?.({sessionId:u,env:D(e.runtimeAdapter)});try{await n.start(u,t,null,{persistNativeSession:!1})}catch(a){return{ok:!1,errorCode:p(a,"start")}}const o=new Promise(a=>{m=setTimeout(()=>a({kind:"timeout"}),r.timeoutMs??x)}),_=n.send(T,e.modelId??void 0).then(()=>E).catch(a=>({kind:"error",error:a})),s=await Promise.race([_,o]);return s.kind==="timeout"?{ok:!1,errorCode:"agent_validation_response_timeout"}:s.kind==="error"?{ok:!1,errorCode:p(s.error,"response")}:f.toLowerCase().includes("hello")?{ok:!0}:{ok:!1,errorCode:"agent_validation_response_mismatch"}};let w,g=!1;try{w=await h()}finally{m&&clearTimeout(m),n.removeListener("agentEvent",k),n.removeListener("error",v);try{await n.stop()}catch{d&&(g=!0)}finally{await d?.dispose().catch(()=>{g=!0})}c&&(r.cleanupNativeSession??((_,s,a)=>{N.execute(_,s,{workDir:a}).catch(()=>{})}))(e.runtimeAdapter,c,t)}return g?{ok:!1,errorCode:"agent_validation_cleanup_failed"}:w}async function H(e,r){const t=V(r.params);if(!t){e.client.sendRes({type:"res",id:r.id,ok:!1,error:"agent_validation_invalid_configuration"});return}const i=await O(t);e.client.sendRes(i.ok?{type:"res",id:r.id,ok:!0,payload:{ready:!0}}:{type:"res",id:r.id,ok:!1,error:i.errorCode})}async function P(e){const r=e.trim();if(!r)throw Object.assign(new Error("workdir_required"),{code:"EINVAL"});const t=await y.realpath(r);if(!(await y.stat(t)).isDirectory())throw Object.assign(new Error("workdir_not_directory"),{code:"ENOTDIR"});return t}function R(e){const r=W(e);return r==="ENOENT"?"agent_validation_workdir_not_found":r==="ENOTDIR"?"agent_validation_workdir_not_directory":r==="EACCES"||r==="EPERM"?"agent_validation_workdir_unavailable":"agent_validation_workdir_invalid"}function p(e,r){const t=e instanceof Error?e.message:String(e);return t==="agent_validation_server_upgrade_required"?t:/model.*(?:not found|unavailable|unsupported|invalid)|unknown model/i.test(t)?"agent_validation_model_unavailable":/not found|enoent|command .* missing|is .* installed/i.test(t)?"agent_validation_runtime_unavailable":/unauth|authentication|not logged|login required|api key|token.*missing|forbidden|401|403/i.test(t)?"agent_validation_runtime_auth_required":r==="start"?"agent_validation_start_failed":"agent_validation_response_failed"}function W(e){return typeof e=="object"&&e&&"code"in e?String(e.code):""}function V(e){const r=typeof e.runtimeAdapter=="string"?e.runtimeAdapter:"",t=typeof e.workDir=="string"?e.workDir:"",i=e.sessionMode==="manager"?"manager":"standard";return!A(r)||!t.trim()?null:{runtimeAdapter:r,workDir:t,sessionMode:i,modelId:typeof e.modelId=="string"?e.modelId:null}}export{H as handleAgentWorkspaceValidation,O as validateAgentWorkspaceConfiguration};
@@ -0,0 +1,7 @@
1
+ export type PiValidationSession = {
2
+ sessionId: string;
3
+ dispose: () => Promise<void>;
4
+ };
5
+ /** Register a short-lived, non-listable session using the daemon's normal machine auth. */
6
+ export declare function registerPiValidationSession(): Promise<PiValidationSession>;
7
+ export declare function createPiValidationSession(serverUrl: string, machineToken: string, request?: typeof fetch): Promise<PiValidationSession>;
@@ -0,0 +1 @@
1
+ import{loadConfig as s}from"../../config/index.js";import{regionToUrl as d}from"../../region.js";async function h(){const i=s();if(!i.machineToken)throw new Error("401 machine authentication required");return l(i.serverUrl||d("cn"),i.machineToken)}async function l(i,o,t=fetch){const n=`${i.replace(/\/$/,"")}/api/managed-model/v1/validation-sessions`,r=await t(n,{method:"POST",headers:{authorization:`Bearer ${o}`},signal:AbortSignal.timeout(5e3)});if(r.status===404)throw new Error("agent_validation_server_upgrade_required");if(!r.ok)throw new Error(`Pi validation registration failed (${r.status})`);const e=await r.json();if(typeof e.sessionId!="string"||!/^pi-validation-[0-9a-f-]{36}$/.test(e.sessionId))throw new Error("Pi validation registration returned an invalid session");const a=e.sessionId;return{sessionId:a,async dispose(){try{if(!(await t(`${n}/${a}`,{method:"DELETE",headers:{authorization:`Bearer ${o}`},signal:AbortSignal.timeout(5e3)})).ok)throw new Error("agent_validation_cleanup_failed")}catch{throw new Error("agent_validation_cleanup_failed")}}}}export{l as createPiValidationSession,h as registerPiValidationSession};
@@ -1,2 +1,2 @@
1
- import r from"node:fs";import e from"node:path";import{fileURLToPath as S}from"node:url";import{randomUUID as y}from"node:crypto";import{resolveShennianPath as d}from"../config/index.js";import{UPDATER_PROTOCOL_VERSION as l}from"./updater-protocol.js";function f(o,t){r.mkdirSync(e.dirname(o),{recursive:!0,mode:448});const n=`${o}.tmp-${process.pid}-${y()}`;try{r.writeFileSync(n,`${JSON.stringify(t,null,2)}
2
- `,{mode:384,flag:"wx"});const i=r.openSync(n,process.platform==="win32"?"r+":"r");try{r.fsyncSync(i)}finally{r.closeSync(i)}r.renameSync(n,o)}finally{try{r.unlinkSync(n)}catch{}}}function U(o={}){const t=o.root??d("runtime","updater"),n=e.join(t,"versions",String(l));r.mkdirSync(n,{recursive:!0,mode:448}),process.platform!=="win32"&&r.chmodSync(n,448);const i=o.assetPath??S(new URL("./updater-worker.js",import.meta.url)),s=e.join(n,"updater-worker.js"),m=r.readFileSync(i),p=(()=>{try{return r.readFileSync(s)}catch{return null}})();if(!p||!p.equals(m)){const c=`${s}.tmp-${process.pid}-${y()}`;try{r.writeFileSync(c,m,{mode:448,flag:"wx"}),process.platform!=="win32"&&r.chmodSync(c,448);const u=r.openSync(c,process.platform==="win32"?"r+":"r");try{r.fsyncSync(u)}finally{r.closeSync(u)}r.renameSync(c,s)}finally{try{r.unlinkSync(c)}catch{}}}const a={protocolVersion:l,workerPath:s,installedAt:(o.now??new Date).toISOString()};return f(e.join(t,"current.json"),a),f(e.join(t,"readiness.json"),{protocolVersion:a.protocolVersion,installedAt:a.installedAt}),a}function $(o=d("runtime","updater")){try{const t=JSON.parse(r.readFileSync(e.join(o,"current.json"),"utf8"));if(t.protocolVersion!==l||!t.workerPath)return null;const n=r.lstatSync(t.workerPath);return n.isSymbolicLink()||!n.isFile()?null:t}catch{return null}}export{U as ensureUpdaterInstalled,$ as readUpdaterReadiness};
1
+ import r from"node:fs";import e from"node:path";import{fileURLToPath as S}from"node:url";import{randomUUID as d}from"node:crypto";import{resolveShennianPath as f}from"../config/index.js";import{UPDATER_PROTOCOL_VERSION as l}from"./updater-protocol.js";function m(t,o){r.mkdirSync(e.dirname(t),{recursive:!0,mode:448});const n=`${t}.tmp-${process.pid}-${d()}`;try{r.writeFileSync(n,`${JSON.stringify(o,null,2)}
2
+ `,{mode:384,flag:"wx"});const i=r.openSync(n,process.platform==="win32"?"r+":"r");try{r.fsyncSync(i)}finally{r.closeSync(i)}r.renameSync(n,t)}finally{try{r.unlinkSync(n)}catch{}}}function U(t={}){const o=t.root??f("runtime","updater"),n=e.join(o,"versions",String(l));r.mkdirSync(n,{recursive:!0,mode:448}),process.platform!=="win32"&&r.chmodSync(n,448),m(e.join(n,"package.json"),{type:"module"});const i=t.assetPath??S(new URL("./updater-worker.js",import.meta.url)),s=e.join(n,"updater-worker.js"),p=r.readFileSync(i),u=(()=>{try{return r.readFileSync(s)}catch{return null}})();if(!u||!u.equals(p)){const c=`${s}.tmp-${process.pid}-${d()}`;try{r.writeFileSync(c,p,{mode:448,flag:"wx"}),process.platform!=="win32"&&r.chmodSync(c,448);const y=r.openSync(c,process.platform==="win32"?"r+":"r");try{r.fsyncSync(y)}finally{r.closeSync(y)}r.renameSync(c,s)}finally{try{r.unlinkSync(c)}catch{}}}const a={protocolVersion:l,workerPath:s,installedAt:(t.now??new Date).toISOString()};return m(e.join(o,"current.json"),a),m(e.join(o,"readiness.json"),{protocolVersion:a.protocolVersion,installedAt:a.installedAt}),a}function $(t=f("runtime","updater")){try{const o=JSON.parse(r.readFileSync(e.join(t,"current.json"),"utf8"));if(o.protocolVersion!==l||!o.workerPath)return null;const n=r.lstatSync(o.workerPath);return n.isSymbolicLink()||!n.isFile()?null:o}catch{return null}}export{U as ensureUpdaterInstalled,$ as readUpdaterReadiness};
@@ -1,4 +1,4 @@
1
- import { spawn, type ChildProcess } from 'node:child_process';
1
+ import { spawn, spawnSync, type ChildProcess } from 'node:child_process';
2
2
  import { ensureUpdaterInstalled } from './updater-bootstrap.js';
3
3
  import { type UpdaterJob } from './updater-protocol.js';
4
4
  export declare function createUpdaterJob(input: {
@@ -15,14 +15,18 @@ export declare function createUpdaterJob(input: {
15
15
  isolatedDaemonStart?: boolean;
16
16
  }): UpdaterJob;
17
17
  export declare function writeUpdaterJob(job: UpdaterJob, root?: string): string;
18
+ export declare function needsSystemdUpdater(platform: string, cgroup: string): boolean;
18
19
  export declare function launchUpdater(input: Parameters<typeof createUpdaterJob>[0], deps?: {
19
20
  root?: string;
20
21
  spawn?: typeof spawn;
21
22
  readiness?: ReturnType<typeof ensureUpdaterInstalled>;
22
23
  now?: () => number;
23
24
  isAlive?: (pid: number) => boolean;
25
+ serviceCgroup?: string;
26
+ platform?: NodeJS.Platform;
27
+ systemdRun?: typeof spawnSync;
24
28
  }): {
25
29
  job: UpdaterJob;
26
- process: ChildProcess;
30
+ process: ChildProcess | null;
27
31
  };
28
32
  export declare function markUpdaterHealthy(jobId: string, effectiveVersion: string, root?: string): boolean;
@@ -1,3 +1,3 @@
1
- import r from"node:fs";import l from"node:path";import{randomUUID as y}from"node:crypto";import{spawn as j}from"node:child_process";import{resolveShennianPath as p}from"../config/index.js";import{findPackageJson as x,resolveNpmGlobalPrefixFromPackageJson as I}from"./engine.js";import{ensureUpdaterInstalled as P}from"./updater-bootstrap.js";import{UPDATER_PROTOCOL_VERSION as S}from"./updater-protocol.js";function g(e,n){r.mkdirSync(l.dirname(e),{recursive:!0,mode:448});const o=`${e}.tmp-${process.pid}-${y()}`;try{r.writeFileSync(o,`${JSON.stringify(n,null,2)}
2
- `,{mode:384,flag:"wx"});const c=r.openSync(o,process.platform==="win32"?"r+":"r");try{r.fsyncSync(c)}finally{r.closeSync(c)}if(r.renameSync(o,e),process.platform!=="win32"){const t=r.openSync(l.dirname(e),"r");try{r.fsyncSync(t)}finally{r.closeSync(t)}}}finally{try{r.unlinkSync(o)}catch{}}}function O(e){const n=e.root??p("runtime","updater"),o=x(),c=e.npmPrefix??(o?I(o):null);if(!c)throw new Error("Cannot determine the active npm prefix");const t=e.jobId??y();return{protocolVersion:S,jobId:t,fromVersion:e.fromVersion,targetVersion:e.targetVersion,npmPrefix:c,npmExecutable:e.npmExecutable??(process.platform==="win32"?"npm.cmd":"npm"),nodeExecutable:e.nodeExecutable??process.execPath,packageName:"shennian",operation:e.operation??"upgrade",oldDaemonPid:e.oldDaemonPid,platform:process.platform,createdAt:(e.now??new Date).toISOString(),daemonExitTimeoutMs:15e3,commandTimeoutMs:12e4,resultPath:l.join(n,"results",`${t}.json`),attemptPath:p("upgrade-attempt.json"),...e.isolatedDaemonStart?{isolatedDaemonStart:!0}:{}}}function E(e,n=p("runtime","updater")){const o=l.join(n,"jobs");r.mkdirSync(o,{recursive:!0,mode:448});const c=l.join(o,`${e.jobId}.json`),t=`${c}.tmp-${process.pid}-${y()}`;try{r.writeFileSync(t,`${JSON.stringify(e,null,2)}
3
- `,{mode:384,flag:"wx"});const a=r.openSync(t,process.platform==="win32"?"r+":"r");try{r.fsyncSync(a)}finally{r.closeSync(a)}if(r.renameSync(t,c),process.platform!=="win32"){const s=r.openSync(o,"r");try{r.fsyncSync(s)}finally{r.closeSync(s)}}return c}finally{try{r.unlinkSync(t)}catch{}}}function U(e,n={}){const o=n.root??e.root??p("runtime","updater"),c=n.readiness??P({root:o}),t=O({...e,root:o}),a=E(t,o),s=l.join(o,"updater.lock");r.mkdirSync(o,{recursive:!0,mode:448});let i=null,f=!1;const w=()=>{if(f){try{JSON.parse(r.readFileSync(s,"utf8")).jobId===t.jobId&&r.unlinkSync(s)}catch{}f=!1}};try{try{i=r.openSync(s,"wx",384),f=!0}catch(m){if(m.code!=="EEXIST")throw m;let h=!1;try{const u=JSON.parse(r.readFileSync(s,"utf8")),k=n.isAlive??(b=>{try{return process.kill(b,0),!0}catch{return!1}});h=!!(u.pid&&k(u.pid)&&(n.now??Date.now)()-Number(u.createdAt)<30*6e4)}catch{}if(h)throw new Error("upgrade_locked");r.rmSync(s,{force:!0}),i=r.openSync(s,"wx",384),f=!0}r.writeFileSync(i,JSON.stringify({jobId:t.jobId,pid:process.pid,createdAt:(n.now??Date.now)()})),r.closeSync(i),i=null;const d=(n.spawn??j)(t.nodeExecutable,[c.workerPath,a],{detached:!0,stdio:"ignore",windowsHide:!0});return d.once?.("error",m=>{g(t.resultPath,{protocolVersion:S,jobId:t.jobId,state:"failed",fromVersion:t.fromVersion,targetVersion:t.targetVersion,errorCode:"internal_error",errorSummary:m instanceof Error?m.message.slice(0,500):"updater_spawn_failed",updatedAt:new Date().toISOString()}),w()}),d.unref(),{job:t,process:d}}catch(d){i!==null&&r.closeSync(i),w();try{r.unlinkSync(a)}catch{}throw d}}function v(e,n,o=p("runtime","updater")){if(!/^[A-Za-z0-9_-]{1,128}$/.test(e))return!1;const c=l.join(o,"results",`${e}.json`);try{const t=r.lstatSync(c);if(t.isSymbolicLink()||!t.isFile())return!1;const a=JSON.parse(r.readFileSync(c,"utf8"));return a.protocolVersion!==S||a.jobId!==e||a.state!=="starting"||a.targetVersion!==n?!1:(g(c,{...a,state:a.operation==="rollback"?"rolled_back":"healthy",effectiveVersion:n,updatedAt:new Date().toISOString()}),!0)}catch{return!1}}export{O as createUpdaterJob,U as launchUpdater,v as markUpdaterHealthy,E as writeUpdaterJob};
1
+ import e from"node:fs";import u from"node:path";import{randomUUID as S}from"node:crypto";import{spawn as j,spawnSync as P}from"node:child_process";import{resolveShennianPath as p}from"../config/index.js";import{findPackageJson as I,resolveNpmGlobalPrefixFromPackageJson as E}from"./engine.js";import{ensureUpdaterInstalled as _}from"./updater-bootstrap.js";import{UPDATER_PROTOCOL_VERSION as w}from"./updater-protocol.js";function b(r,o){e.mkdirSync(u.dirname(r),{recursive:!0,mode:448});const n=`${r}.tmp-${process.pid}-${S()}`;try{e.writeFileSync(n,`${JSON.stringify(o,null,2)}
2
+ `,{mode:384,flag:"wx"});const c=e.openSync(n,process.platform==="win32"?"r+":"r");try{e.fsyncSync(c)}finally{e.closeSync(c)}if(e.renameSync(n,r),process.platform!=="win32"){const t=e.openSync(u.dirname(r),"r");try{e.fsyncSync(t)}finally{e.closeSync(t)}}}finally{try{e.unlinkSync(n)}catch{}}}function $(r){const o=r.root??p("runtime","updater"),n=I(),c=r.npmPrefix??(n?E(n):null);if(!c)throw new Error("Cannot determine the active npm prefix");const t=r.jobId??S();return{protocolVersion:w,jobId:t,fromVersion:r.fromVersion,targetVersion:r.targetVersion,npmPrefix:c,npmExecutable:r.npmExecutable??(process.platform==="win32"?"npm.cmd":"npm"),nodeExecutable:r.nodeExecutable??process.execPath,packageName:"shennian",operation:r.operation??"upgrade",oldDaemonPid:r.oldDaemonPid,platform:process.platform,createdAt:(r.now??new Date).toISOString(),daemonExitTimeoutMs:15e3,commandTimeoutMs:12e4,resultPath:u.join(o,"results",`${t}.json`),attemptPath:p("upgrade-attempt.json"),...r.isolatedDaemonStart?{isolatedDaemonStart:!0}:{}}}function O(r,o=p("runtime","updater")){const n=u.join(o,"jobs");e.mkdirSync(n,{recursive:!0,mode:448});const c=u.join(n,`${r.jobId}.json`),t=`${c}.tmp-${process.pid}-${S()}`;try{e.writeFileSync(t,`${JSON.stringify(r,null,2)}
3
+ `,{mode:384,flag:"wx"});const s=e.openSync(t,process.platform==="win32"?"r+":"r");try{e.fsyncSync(s)}finally{e.closeSync(s)}if(e.renameSync(t,c),process.platform!=="win32"){const a=e.openSync(n,"r");try{e.fsyncSync(a)}finally{e.closeSync(a)}}return c}finally{try{e.unlinkSync(t)}catch{}}}function v(r,o){return r==="linux"&&/(?:^|\/)shennian\.service(?:\n|$)/m.test(o.trim())}function C(r,o={}){const n=o.root??r.root??p("runtime","updater"),c=o.readiness??_({root:n}),t=$({...r,root:n}),s=O(t,n),a=u.join(n,"updater.lock");e.mkdirSync(n,{recursive:!0,mode:448});let l=null,m=!1;const h=()=>{if(m){try{JSON.parse(e.readFileSync(a,"utf8")).jobId===t.jobId&&e.unlinkSync(a)}catch{}m=!1}};try{try{l=e.openSync(a,"wx",384),m=!0}catch(i){if(i.code!=="EEXIST")throw i;let g=!1;try{const y=JSON.parse(e.readFileSync(a,"utf8")),k=o.isAlive??(x=>{try{return process.kill(x,0),!0}catch{return!1}});g=!!(y.pid&&k(y.pid)&&(o.now??Date.now)()-Number(y.createdAt)<30*6e4)}catch{}if(g)throw new Error("upgrade_locked");e.rmSync(a,{force:!0}),l=e.openSync(a,"wx",384),m=!0}e.writeFileSync(l,JSON.stringify({jobId:t.jobId,pid:process.pid,createdAt:(o.now??Date.now)()})),e.closeSync(l),l=null;let d=o.serviceCgroup??"";if(process.platform==="linux"&&!o.spawn&&o.serviceCgroup===void 0)try{d=e.readFileSync("/proc/self/cgroup","utf8")}catch{}if(v(o.platform??process.platform,d)){const i=(o.systemdRun??P)("systemd-run",["--user","--quiet","--collect",`--unit=shennian-updater-${t.jobId}`,"--property=Type=exec","--property=KillMode=process",`--setenv=PATH=${process.env.PATH??"/usr/local/bin:/usr/bin:/bin"}`,"--",t.nodeExecutable,c.workerPath,s],{encoding:"utf8",stdio:"pipe",timeout:1e4});if(i.error||i.status!==0)throw new Error("updater_service_launch_failed");return{job:t,process:null}}const f=(o.spawn??j)(t.nodeExecutable,[c.workerPath,s],{detached:!0,stdio:"ignore",windowsHide:!0});return f.once?.("error",i=>{b(t.resultPath,{protocolVersion:w,jobId:t.jobId,state:"failed",fromVersion:t.fromVersion,targetVersion:t.targetVersion,errorCode:"internal_error",errorSummary:i instanceof Error?i.message.slice(0,500):"updater_spawn_failed",updatedAt:new Date().toISOString()}),h()}),f.unref(),{job:t,process:f}}catch(d){l!==null&&e.closeSync(l),h();try{e.unlinkSync(s)}catch{}throw d}}function H(r,o,n=p("runtime","updater")){if(!/^[A-Za-z0-9_-]{1,128}$/.test(r))return!1;const c=u.join(n,"results",`${r}.json`);try{const t=e.lstatSync(c);if(t.isSymbolicLink()||!t.isFile())return!1;const s=JSON.parse(e.readFileSync(c,"utf8"));return s.protocolVersion!==w||s.jobId!==r||s.state!=="starting"||s.targetVersion!==o?!1:(b(c,{...s,state:s.operation==="rollback"?"rolled_back":"healthy",effectiveVersion:o,updatedAt:new Date().toISOString()}),!0)}catch{return!1}}export{$ as createUpdaterJob,C as launchUpdater,H as markUpdaterHealthy,v as needsSystemdUpdater,O as writeUpdaterJob};
@@ -269,6 +269,8 @@ export type WeChatRpaPreflightCheck = {
269
269
  };
270
270
  export type NianSession = {
271
271
  id: string;
272
+ /** Device-local lifecycle only; never sent as canonical session metadata. */
273
+ localCreationState?: 'draft' | 'created';
272
274
  agentIdentityId?: string | null;
273
275
  machineId: string;
274
276
  userId: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shennian",
3
- "version": "0.4.35",
3
+ "version": "0.4.37",
4
4
  "description": "Shennian — AI Agent Control Plane CLI",
5
5
  "type": "module",
6
6
  "bin": {