shennian 0.5.1 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/publish-build-manifest.json +65 -70
- package/dist/src/agents/acp/adapter.js +2 -2
- package/dist/src/agents/acp/client-services.js +1 -1
- package/dist/src/agents/acp/process-client.js +1 -1
- package/dist/src/agents/config-status.js +1 -1
- package/dist/src/agents/model-registry/cache.js +1 -1
- package/dist/src/agents/model-registry/discovery.js +3 -3
- package/dist/src/agents/model-registry/opencode-api.js +1 -1
- package/dist/src/agents/model-registry/service.js +1 -1
- package/dist/src/agents/model-registry.js +1 -1
- package/dist/src/agents/opencode.js +1 -1
- package/dist/src/agents/pi-coding-agent.js +3 -4
- package/dist/src/agents/pi-turn-diagnostics.js +1 -0
- package/dist/src/agents/process-tree.js +1 -0
- package/dist/src/commands/agent-readiness.js +2 -2
- package/dist/src/commands/room.js +3 -3
- package/dist/src/daemon-ipc/agent-readiness.js +1 -1
- package/dist/src/index.js +3 -3
- package/dist/src/manager/runtime.js +4 -4
- package/dist/src/room/activation-service.js +1 -1
- package/dist/src/room/attachment-upload.js +1 -1
- package/dist/src/room/managed-room-ipc.js +2 -2
- package/dist/src/room/mirror-sync-service.js +1 -1
- package/dist/src/room/room-action-client.js +1 -1
- package/dist/src/session/handlers/agent-workspace-validation.js +1 -1
- package/dist/src/session/handlers/chat-events.js +2 -2
- package/dist/src/session/handlers/chat-recovery.js +1 -1
- package/dist/src/session/handlers/chat.js +1 -1
- package/dist/src/session/manager.js +1 -1
- package/dist/src/session/queue.js +1 -1
- package/node_modules/@shennian/wire/dist/session.js +1 -1
- package/package.json +1 -1
- package/dist/src/agents/acp/process-tree.js +0 -1
- package/dist/src/agents/pi-managed-model-broker.js +0 -1
- package/dist/src/session/handlers/pi-validation-session.js +0 -1
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import
|
|
2
|
-
[truncated]`}function
|
|
3
|
-
`)}`}function E(n,t){const s=[];for(const e of n??[]){if(e.kind!=="image"||!e.mimeType.startsWith("image/"))continue;let i=e.previewData?.trim()??"";const a=i.indexOf(",");if(i.startsWith("data:")&&a>=0&&(i=i.slice(a+1)),!i&&t&&e.path&&!/^https?:\/\//i.test(e.path))try{const l=h.statSync(e.path);l.isFile()&&l.size<=10*1024*1024&&(i=h.readFileSync(e.path).toString("base64"))}catch{i=""}i&&s.push({type:"image",data:i,mimeType:e.mimeType})}return s}class W extends v{type="pi-coding-agent";get supportsActiveInput(){return!0}async injectActiveInput(t){const s=await this.readState();if(!this.rpc||!this.runId||!s.isStreaming&&!s.isCompacting)throw new Error("pi_active_turn_unavailable");const e={message:I(t.text,t.attachments,this.workDir,this.externalChannel,this.shennianSessionId,null)},i=E(t.attachments,!this.managedAgentPolicy);i.length&&!this.modelBroker&&(e.images=i),await this.rpc.request("steer",e)}get recoveryCapabilities(){return{inspectTerminal:!1,reattachRun:!1,resumeRun:!1,resumeSession:!0}}process=null;rpc=null;shennianSessionId="";workDir="";sessionDir="";agentSessionId=null;externalChannel=null;extraEnv={};spawnedRoomContext=null;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,i){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=i?.persistNativeSession!==!1,this.validationRoot=this.persistNativeSession?null:u.join(a,`validation-${w()}`),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,i,a,l){await this.ensureProcess(a);const c=this.requireRpc(),p=await this.readState(),o=!!(p.isStreaming||p.isCompacting);(!o||!this.runId)&&(this.runId=w(),this.seq=0,this.terminal=!1,this.lastUsage=void 0,this.runPhase="thinking");const d=K(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:I(t,i,this.workDir,this.externalChannel,this.shennianSessionId,null)},S=E(i,!this.managedAgentPolicy);S.length&&!this.modelBroker&&(m.images=S),this.modelBroker?.setTurnActive(!0);try{await c.request(r,m)}catch(A){throw this.modelBroker?.setTurnActive(!1),A}}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){if(!await this.roomContextStale())return;await this.stopProcess()}const s=k("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 i=await this.prepareHostedModel();if(i&&e.push("--provider","shennian-dashscope","--model","deepseek-v4-flash-0731"),this.managedAgentPolicy&&!P(this.managedAgentPolicy)){e.push("--no-extensions","--no-skills","--no-prompt-templates","--no-context-files");const r=this.managedTools(),m=F({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=D(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",...i?{PI_CODING_AGENT_DIR:i.configDir}:{}},p=this.managedAgentPolicy&&!P(this.managedAgentPolicy)?T({agentType:this.type,providerEnv:this.extraEnv,sessionEnv:this.extraEnv,runtimeEnv:c}):this.managedAgentPolicy?C({...c,...this.extraEnv}):N({...c,...this.extraEnv});let o;try{o=R(s,e,{cwd:this.workDir,stdio:["pipe","pipe","pipe"],env:p})}catch(r){throw await this.stopModelBroker(),r}this.process=o,this.spawnedRoomContext=this.currentRoomContext(),this.stderr="",o.stderr?.on("data",r=>{const m=typeof r=="string"?r:r.toString("utf8");this.stderr=y(`${this.stderr}${m}`,$)});const d=new B(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&&U(r.sessionId,this.sessionDir)}catch(r){throw await this.stopProcess(),r}}currentRoomContext(){return this.extraEnv.SHENNIAN_MANAGED_ROOM_CONTEXT??null}async roomContextStale(){if(this.currentRoomContext()===this.spawnedRoomContext)return!1;const t=await this.readState().catch(()=>null);return!t?.isStreaming&&!t?.isCompacting}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,i=!!this.extraEnv.SHENNIAN_MANAGED_ROOM_CONTEXT;return(e||i||t.fileAccess==="host-full")&&s.push("bash"),s}ownsSessionFile(t){const s=u.resolve(this.sessionDir),e=u.resolve(t),i=u.relative(s,e);return i!==""&&!i.startsWith("..")&&!u.isAbsolute(i)}async readState(){const t=await this.requireRpc().request("get_state");return z(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=_(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:X(t.result)||Z(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=_(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:L(t)})}async prepareHostedModel(){const t=b(),s=t.machineToken?.trim();if(!s)return null;const e=new q({serverUrl:t.serverUrl?.trim()||"https://shennian.net",machineToken:s,sessionId:this.shennianSessionId}),i=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:i.baseUrl,api:"openai-completions",apiKey:i.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 i=()=>{e||(e=!0,s())};t.once("close",i),setTimeout(()=>{t.kill("SIGKILL"),i()},H).unref()}),await this.stopModelBroker()):await this.stopModelBroker(),this.validationRoot&&(h.rmSync(this.validationRoot,{recursive:!0,force:!0}),this.validationRoot=null)}}x("pi-coding-agent",()=>new W);export{W as PiCodingAgentAdapter};
|
|
1
|
+
import f from"node:fs";import p from"node:path";import{randomUUID as A}from"node:crypto";import{AgentAdapter as O,registerAgent as M}from"./adapter.js";import{resolveBuiltinCommand as $,spawnAgentCommand as j}from"./command-spec.js";import{buildAgentProcessEnv as F,buildManagedAgentProcessEnv as U,buildManagedNativeFullProcessEnv as G}from"../agent-env.js";import{resolveShennianPath as w}from"../config/index.js";import{buildInlineChannelPreamble as H,buildPlatformInstructions as B}from"./platform-instructions.js";import{isManagedAgentNativeFull as x}from"../security/managed-agent-policy.js";import{PiRpcClient as X}from"./pi-rpc-client.js";import{registerManagedAgentRuntimeSecurity as z}from"../security/managed-agent-runtime.js";import{PI_CODING_AGENT_MANAGED_RUNTIME_SECURITY as L}from"./pi-coding-agent-managed-permissions.js";import{writePiManagedExtension as W}from"./pi-managed-extension.js";import{recordPiSessionOwnership as Y}from"./pi-session-ownership.js";import{classifyPiFailure as _,createPiDiagnosticMarker as J,formatPiDiagnostic as P,inspectPiTurnConfiguration as K,sanitizePiDiagnosticDimension as S}from"./pi-turn-diagnostics.js";z("pi-coding-agent",L);const V=32*1024,Z=256*1024,Q=3e3;function E(n){return n.replace(/[^a-zA-Z0-9_.-]/g,"_").slice(0,180)||"session"}function tt(n){return n.data&&typeof n.data=="object"&&!Array.isArray(n.data)?n.data:{}}function et(n){if(typeof n=="string")return n;if(!n||typeof n!="object")return"";const t=n.content;return Array.isArray(t)?t.map(e=>{if(!e||typeof e!="object")return"";const i=e;return i.type==="text"&&typeof i.text=="string"?i.text:""}).join(""):""}function D(n,t=Z){return n.length<=t?n:`${n.slice(0,t)}
|
|
2
|
+
[truncated]`}function R(n){const t=_(n),e=t.safeDetail??"Upstream error details were omitted";return`Pi model request failed${t.httpStatus?` (HTTP ${t.httpStatus})`:""}: ${e}`}function it(n){try{return D(JSON.stringify(n))}catch{return"[unserializable]"}}function N(n){if(!n||typeof n!="object")return;const t=n;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 st(n){const t=n?.trim();if(!t)return{};const e=t.indexOf("/");return e<=0||e===t.length-1?{modelId:t}:{provider:t.slice(0,e),modelId:t.slice(e+1)}}function T(n,t,e,i,s,c){const l=H(e,i,s,c),m=t?.filter(u=>u.kind!=="image")??[];if(!m.length)return`${l}${n}`;const a=m.map(u=>`[${u.name}](<${u.path}>)`);return`${l}${[n,"",...a].join(`
|
|
3
|
+
`)}`}function k(n,t){const e=[];for(const i of n??[]){if(i.kind!=="image"||!i.mimeType.startsWith("image/"))continue;let s=i.previewData?.trim()??"";const c=s.indexOf(",");if(s.startsWith("data:")&&c>=0&&(s=s.slice(c+1)),!s&&t&&i.path&&!/^https?:\/\//i.test(i.path))try{const l=f.statSync(i.path);l.isFile()&&l.size<=10*1024*1024&&(s=f.readFileSync(i.path).toString("base64"))}catch{s=""}s&&e.push({type:"image",data:s,mimeType:i.mimeType})}return e}class nt extends O{type="pi-coding-agent";get supportsActiveInput(){return!0}async injectActiveInput(t){const e=await this.readState();if(!this.rpc||!this.runId||!e.isStreaming&&!e.isCompacting)throw new Error("pi_active_turn_unavailable");const i={message:T(t.text,t.attachments,this.workDir,this.externalChannel,this.shennianSessionId,null)},s=k(t.attachments,!this.managedAgentPolicy);s.length&&(i.images=s),await this.rpc.request("steer",i)}get recoveryCapabilities(){return{inspectTerminal:!1,reattachRun:!1,resumeRun:!1,resumeSession:!0}}process=null;rpc=null;shennianSessionId="";workDir="";sessionDir="";agentSessionId=null;externalChannel=null;extraEnv={};spawnedRoomContext=null;managedAgentPolicy=null;runId="";seq=0;active=!1;terminal=!0;stderr="";lastUsage;promptFile=null;persistNativeSession=!0;validationRoot=null;runPhase=null;processEnv={};turnDiagnostic=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,e,i,s){await this.stopProcess(),this.shennianSessionId=t,this.workDir=this.managedAgentPolicy?.adapterPolicy.workDir??e,this.agentSessionId=i??null;const c=w("runtime","pi-coding-agent",E(t));this.persistNativeSession=s?.persistNativeSession!==!1,this.validationRoot=this.persistNativeSession?null:p.join(c,`validation-${A()}`),this.sessionDir=this.validationRoot?p.join(this.validationRoot,"sessions"):p.join(c,"sessions"),f.mkdirSync(this.sessionDir,{recursive:!0,mode:448}),process.platform!=="win32"&&f.chmodSync(this.sessionDir,448),this.active=!1,this.terminal=!0}async send(t,e,i,s,c,l){const m=J(),a=st(e),u=s?.length??0,r=s?.filter(y=>y.kind==="image").length??0;let h="process_start",I=!1;try{await this.ensureProcess(c);const y=this.requireRpc();h="state_read";let o=await this.readState();const g=!!(o.isStreaming||o.isCompacting);(!g||!this.runId)&&(this.runId=A(),this.seq=0,this.terminal=!1,this.lastUsage=void 0,this.runPhase="thinking");const d=a;if(d.modelId&&!g&&(o.model?.id!==d.modelId||d.provider&&o.model?.provider!==d.provider)){if(!d.provider)throw new Error("Pi model selection must include provider/modelId");h="model_selection",await y.request("set_model",{provider:d.provider,modelId:d.modelId}),h="state_read",o=await this.readState()}i&&!g&&(h="thinking_selection",await y.request("set_thinking_level",{level:i}));const b=K({model:o.model,env:this.processEnv});this.turnDiagnostic={marker:m,phase:"request_config",...S(d.provider)?{requestedProvider:S(d.provider)}:{},...S(d.modelId)?{requestedModel:S(d.modelId)}:{},modelSelectionSource:l?.modelSelectionSource??(e?"request_model":"pi_default_model"),piObservedRequestConfig:!0,finalHttpRequestObserved:!1,...b,historyMessageCount:Number.isSafeInteger(o.messageCount)?o.messageCount:null,attachmentCount:u,imageAttachmentCount:r,toolCallCount:0,queuedIntoActiveRun:g},console.info(P(this.turnDiagnostic)),I=!0;const q=g?l?.source==="room_activation"?"follow_up":"steer":"prompt",v={message:T(t,s,this.workDir,this.externalChannel,this.shennianSessionId,null)},C=k(s,!this.managedAgentPolicy);C.length&&(v.images=C),h="prompt_dispatch",await y.request(q,v)}catch(y){const o=_(y),g=this.turnDiagnostic??{marker:m,phase:"request_config",...S(a.provider)?{requestedProvider:S(a.provider)}:{},...S(a.modelId)?{requestedModel:S(a.modelId)}:{},modelSelectionSource:l?.modelSelectionSource??(e?"request_model":"pi_default_model"),piObservedRequestConfig:!1,finalHttpRequestObserved:!1,providerConfigSource:"unknown",credentialSource:"unknown",credentialPresent:"unknown",agentDirSource:this.processEnv.PI_CODING_AGENT_DIR?"pi_agent_dir_environment":"user_home_default",historyMessageCount:null,attachmentCount:u,imageAttachmentCount:r,toolCallCount:0,queuedIntoActiveRun:!1};throw I||console.info(P(g)),console.warn(P({...g,phase:"outcome",outcome:"failed",errorStage:h,errorCode:o.errorCode,...o.httpStatus?{httpStatus:o.httpStatus}:{}})),this.turnDiagnostic=null,new Error(`Pi ${h} failed${o.httpStatus?` (HTTP ${o.httpStatus})`:""}: ${o.safeDetail??o.errorCode}`)}}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.logDiagnosticOutcome("aborted"),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),e=!!(t?.isStreaming||t?.isCompacting||this.active);return{active:e,runId:e?this.runId:null,runPhase:e?this.runPhase??"thinking":null,canStop:e}}async setTitle(t,e){this.rpc&&await this.rpc.request("set_session_name",{name:e.slice(0,200)})}async ensureProcess(t){if(this.process&&this.rpc){if(!await this.roomContextStale())return;await this.stopProcess()}const e=$("pi-coding-agent");if(!e)throw new Error('Command "pi" not found. Install @earendil-works/pi-coding-agent from the official package.');const i=["--mode","rpc","--session-dir",this.sessionDir,"--offline"];if(this.agentSessionId&&i.push("--session",this.agentSessionId),this.managedAgentPolicy&&!x(this.managedAgentPolicy)){i.push("--no-extensions","--no-skills","--no-prompt-templates","--no-context-files");const r=this.managedTools(),h=W({outputPath:p.join(w("runtime","pi-coding-agent",E(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"});i.push("--no-builtin-tools","--extension",h),r.length?i.push("--tools",r.join(",")):i.push("--no-tools")}const s=B(this.workDir,this.externalChannel,this.shennianSessionId,t),c=this.validationRoot??w("runtime","pi-coding-agent",E(this.shennianSessionId));f.mkdirSync(c,{recursive:!0,mode:448}),this.promptFile=p.join(c,"instructions.md"),f.writeFileSync(this.promptFile,s,{encoding:"utf8",mode:384}),process.platform!=="win32"&&f.chmodSync(this.promptFile,384),i.push("--append-system-prompt",this.promptFile);const l={NO_COLOR:"1"},m=this.managedAgentPolicy&&!x(this.managedAgentPolicy)?U({agentType:this.type,providerEnv:this.extraEnv,sessionEnv:this.extraEnv,runtimeEnv:l}):this.managedAgentPolicy?G({...l,...this.extraEnv}):F({...l,...this.extraEnv});this.processEnv=m;const a=j(e,i,{cwd:this.workDir,stdio:["pipe","pipe","pipe"],env:m});this.process=a,this.spawnedRoomContext=this.currentRoomContext(),this.stderr="",a.stderr?.on("data",r=>{const h=typeof r=="string"?r:r.toString("utf8");this.stderr=D(`${this.stderr}${h}`,V)});const u=new X(a);this.rpc=u,u.on("event",r=>this.handleRpcEvent(r)),u.on("protocolError",r=>this.emitError(r.message)),a.once("close",r=>{this.process===a&&(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}`:""}`))}),a.once("error",r=>{this.process===a&&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&&Y(r.sessionId,this.sessionDir)}catch(r){throw await this.stopProcess(),r}}currentRoomContext(){return this.extraEnv.SHENNIAN_MANAGED_ROOM_CONTEXT??null}async roomContextStale(){if(this.currentRoomContext()===this.spawnedRoomContext)return!1;const t=await this.readState().catch(()=>null);return!t?.isStreaming&&!t?.isCompacting}managedTools(){const t=this.managedAgentPolicy?.adapterPolicy;if(!t)return[];const e=["read","grep","find","ls"];(t.fileAccess==="workspace-write"||t.fileAccess==="host-full")&&e.push("edit","write");const i=!!this.extraEnv.SHENNIAN_MANAGER_SESSION_ID,s=!!this.extraEnv.SHENNIAN_MANAGED_ROOM_CONTEXT;return(i||s||t.fileAccess==="host-full")&&e.push("bash"),e}ownsSessionFile(t){const e=p.resolve(this.sessionDir),i=p.resolve(t),s=p.relative(e,i);return s!==""&&!s.startsWith("..")&&!p.isAbsolute(s)}async readState(){const t=await this.requireRpc().request("get_state");return tt(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=N(t.usage)??this.lastUsage;const e=t.assistantMessageEvent;if(!e||typeof e!="object")return;const i=e;i.type==="text_delta"&&typeof i.delta=="string"?(this.runPhase="streaming_text",this.emitEvent({state:"delta",text:i.delta})):i.type==="thinking_delta"&&typeof i.delta=="string"&&(this.runPhase="thinking",this.emitEvent({state:"delta",text:i.delta,thinking:!0}));return}if(t.type==="tool_execution_start"){this.turnDiagnostic&&(this.turnDiagnostic.toolCallCount+=1),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:et(t.result)||it(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 e=t.message;if(e&&typeof e=="object"){const i=e;if(this.lastUsage=N(i.usage)??this.lastUsage,i.stopReason==="error"){const s=_(i.errorMessage);this.logDiagnosticOutcome("failed",{errorStage:"pi_model_turn",errorCode:s.errorCode,...s.httpStatus?{httpStatus:s.httpStatus}:{}}),this.emitError(R(i.errorMessage))}}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.logDiagnosticOutcome("failed",{errorStage:"pi_process",errorCode:"extension_error"}),this.emitError(typeof t.error=="string"?t.error:"Pi extension failed");return}t.type==="agent_settled"&&(this.active=!1,this.logDiagnosticOutcome("completed"),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))}logDiagnosticOutcome(t,e={}){this.turnDiagnostic&&(console.info(P({...this.turnDiagnostic,phase:"outcome",outcome:t,...e})),this.turnDiagnostic=null)}emitError(t){if(this.turnDiagnostic){const e=_(t);this.logDiagnosticOutcome("failed",{errorStage:"pi_process",errorCode:e.errorCode,...e.httpStatus?{httpStatus:e.httpStatus}:{}})}this.emitTerminal({state:"error",message:t.startsWith("Pi model request failed")?t:R(t)})}async stopProcess(){const t=this.process;this.process=null,this.rpc?.close(),this.rpc=null,t&&(t.kill("SIGTERM"),await new Promise(e=>{let i=!1;const s=()=>{i||(i=!0,e())};t.once("close",s),setTimeout(()=>{t.kill("SIGKILL"),s()},Q).unref()})),this.validationRoot&&(f.rmSync(this.validationRoot,{recursive:!0,force:!0}),this.validationRoot=null)}}M("pi-coding-agent",()=>new nt);export{nt as PiCodingAgentAdapter};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import _ from"node:fs";import v from"node:os";import p from"node:path";import{randomBytes as P}from"node:crypto";const y={anthropic:["ANTHROPIC_AUTH_TOKEN","ANTHROPIC_OAUTH_TOKEN","ANTHROPIC_API_KEY"],deepseek:["DEEPSEEK_API_KEY"],openai:["OPENAI_API_KEY"],openrouter:["OPENROUTER_API_KEY"],google:["GEMINI_API_KEY"],groq:["GROQ_API_KEY"],xai:["XAI_API_KEY"],mistral:["MISTRAL_API_KEY"],opencode:["OPENCODE_API_KEY"]};function f(e,t){if(typeof e!="string")return t;const n=e.trim();return/^[A-Za-z0-9][A-Za-z0-9._:/+-]{0,159}$/.test(n)?n:t}function w(e){return f(e,"")||void 0}function A(e){try{const t=_.statSync(e);if(!t.isFile()||t.size>1024*1024)return null;const n=JSON.parse(_.readFileSync(e,"utf8"));return n&&typeof n=="object"&&!Array.isArray(n)?n:null}catch{return null}}function I(e){const t=new Set;for(const n of e.matchAll(/\$(?:\{([A-Za-z_][A-Za-z0-9_]*)\}|([A-Za-z_][A-Za-z0-9_]*))/g))t.add(n[1]??n[2]);return[...t]}function g(e,t){if(typeof e!="string"||!e.trim())return{source:"unknown",present:!1};const n=e.trim();if(n.startsWith("!"))return{source:"models_file_command",present:"unknown"};const r=I(n);return r.length?{source:"models_file_environment",present:!!r.every(i=>!!t[i])}:{source:"models_file_literal",present:!0}}function O(e,t){if(!e||typeof e!="object"||Array.isArray(e))return null;const n=e;if(n.type==="oauth")return{source:"oauth",present:typeof n.access=="string"&&n.access.length>0};if(n.type!=="api_key")return null;const r=n.env&&typeof n.env=="object"&&!Array.isArray(n.env)?Object.fromEntries(Object.entries(n.env).filter(o=>typeof o[1]=="string")):{};return{source:"auth_file",present:g(n.key,{...t,...r}).present}}function D(e){if(typeof e=="string")try{const t=new URL(e);return t.protocol!=="http:"&&t.protocol!=="https:"||!t.hostname?void 0:{scheme:t.protocol.slice(0,-1),host:t.hostname,...t.port?{port:t.port}:{}}}catch{return}}function T(e){const t=f(e.model?.provider,"unknown"),n=f(e.model?.id,"unknown"),r=e.agentDir??(e.env.PI_CODING_AGENT_DIR?.trim()||p.join(e.env.HOME?.trim()||v.homedir(),".pi","agent")),i=e.agentDir||e.env.PI_CODING_AGENT_DIR?.trim()?"pi_agent_dir_environment":"user_home_default",o=A(p.join(r,"models.json")),c=(o?.providers&&typeof o.providers=="object"&&!Array.isArray(o.providers)?o.providers:null)?.[t],s=c&&typeof c=="object"&&!Array.isArray(c)?c:null,h=s?"models_file":t==="unknown"?"unknown":"pi_runtime_catalog",E=A(p.join(r,"auth.json")),l=O(E?.[t],e.env);let a="unknown",d=y[t]||s?!1:"unknown";if(l)a=l.source,d=l.present;else if((y[t]??[]).some(u=>!!e.env[u]))a="process_environment",d=!0;else if(s&&"apiKey"in s){const u=g(s.apiKey,e.env);a=u.source,d=u.present}return{provider:t,model:n,api:f(e.model?.api,"unknown"),target:D(e.model?.baseUrl),providerConfigSource:h,credentialSource:a,credentialPresent:d,agentDirSource:i}}function k(){return P(9).toString("base64url")}function S(e){const t=e instanceof Error?e.message:typeof e=="string"?e:"",n=t.match(/(?:^|\D)([1-5]\d\d)(?:\D|$)/),r=n?Number(n[1]):void 0,o=[[/request not allowed/i,"request_not_allowed","Request not allowed"],[/provider unavailable/i,"provider_unavailable","Provider unavailable"],[/model not found/i,"model_not_found","Model not found"],[/rate[ -]?limit/i,"rate_limited","Rate limited"],[/quota/i,"quota_exceeded","Quota exceeded"],[/unauthori[sz]ed|authentication required|invalid api key/i,"authentication_rejected","Authentication rejected"],[/forbidden/i,"forbidden","Forbidden"],[/timed? out|timeout/i,"timeout","Timed out"],[/connection (?:refused|reset)|fetch failed/i,"connection_failed","Connection failed"]].find(([m])=>m.test(t));return{errorCode:r?`http_${r}`:o?.[1]??"pi_error",...r?{httpStatus:r}:{},...o?{safeDetail:o[2]}:{}}}function C(e){return`[pi.diagnostic] ${JSON.stringify(e)}`}export{S as classifyPiFailure,k as createPiDiagnosticMarker,C as formatPiDiagnostic,T as inspectPiTurnConfiguration,w as sanitizePiDiagnosticDimension};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{spawn as p}from"node:child_process";const m=process.platform!=="win32";function n(e){return e.exitCode!==null||e.signalCode!==null}function o(e,r,t){if(process.platform==="win32"){p("taskkill",["/PID",String(r),"/T",...t?["/F"]:[]],{stdio:"ignore",windowsHide:!0}).unref();return}try{process.kill(-r,t?"SIGKILL":"SIGTERM")}catch{try{e.kill(t?"SIGKILL":"SIGTERM")}catch{}}}function w(e,r=!1){!e.pid||n(e)||o(e,e.pid,r)}async function c(e,r=3e3){const t=e.pid;if(!t)return;let i=null;const u=n(e)?Promise.resolve():new Promise(a=>{e.once("exit",()=>a())}),s=async a=>{await Promise.race([u,new Promise(f=>{i=setTimeout(f,a)})]),i&&clearTimeout(i)};if(process.platform==="win32"){n(e)||o(e,t,!0),await s(r);return}n(e)||o(e,t,!1),await s(r),o(e,t,!0),n(e)||await s(1e3)}export{m as DETACH_PROCESS_GROUP,c as stopProcessTree,w as terminateProcessTree};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{callDaemonIpc as s}from"../daemon-ipc/client.js";import{DaemonIpcError as
|
|
2
|
-
`),n.ok||(process.exitCode=1)};t.command("discover").description("Read the daemon AI snapshot; use --refresh only to rerun local provider and model discovery").option("--agent <type>","Return only one discovered Agent type").option("--compact","Return capability summary without the full model list").option("--refresh","Rerun discovery; with --agent, probe only that Agent").option("--json","Print JSON (default)").action(o=>a("agent.discover",{...o.agent?{agentType:o.agent}:{},compact:o.compact===!0,refresh:o.refresh===!0})),t.command("check <agentType>").description("Send one hello test using the selected AI and model; uses provider quota, creates no managed Agent or room").requiredOption("--workdir <path>","Absolute project directory on this machine").option("--model <id>","Model returned by discovery; otherwise use the provider default").option("--json","Print JSON (default)").action((o,e)=>a("agent.check",{agentType:o,workDir:e.workdir,modelId:e.model??null}))}export{
|
|
1
|
+
import{callDaemonIpc as s}from"../daemon-ipc/client.js";import{DaemonIpcError as r}from"../daemon-ipc/protocol.js";const d={method_not_found:"Update the existing Shennian installation: Desktop through Desktop updates, standalone CLI through its own update flow. Do not install a second Client.",daemon_unavailable:"Check shennian status --json on the target computer. Do not resume an intentional pause without the user requesting it.",daemon_paused:"Shennian is paused. Ask the user to resume it before checking.",ipc_timeout:"The daemon did not answer in time, so the result is unknown; the machine is usually overloaded. Check its load and memory (uptime, free -m) and wait for the running check to end; do not repeatedly send tests. If the AI's own processes are still running afterwards, stop only those exact processes.",agent_check_in_progress:"Wait for the running check to finish before trying again.",agent_validation_runtime_unavailable:"Refresh discovery. If the tool is still missing, guide the user through its official installation on this computer.",agent_validation_runtime_auth_required:"Guide the user through the provider\u2019s own sign-in on this computer, then retry the selected AI and model.",agent_validation_model_unavailable:"Choose a model returned by discovery and check the provider account\u2019s model access.",agent_validation_quota_exceeded:"Check the provider account\u2019s credits, subscription or rate limit before another test.",agent_validation_network_failed:"Check the target computer\u2019s connection to the provider, then retry when it is available.",agent_validation_workdir_invalid:"Use an existing absolute project directory on this computer that the user allows Shennian to use.",agent_validation_workdir_not_found:"The working directory does not exist on this computer. Ask the user for an existing absolute project directory (it may have been moved, deleted or is on an unmounted drive); do not create it for them.",agent_validation_workdir_not_directory:"The working directory path is a file, not a folder. Ask the user for the project folder.",agent_validation_workdir_unavailable:"The working directory exists but this user cannot access it. Check its permissions on this computer or choose another directory.",agent_validation_start_failed:"The AI could not start, or did not finish starting within 60 seconds. Run the tool once on this computer to see its own error, fix its installation or configuration, then retry once.",agent_validation_response_timeout:"The AI started but gave no hello reply within 60 seconds and its processes were stopped. Check the computer's load and free memory first, then its provider sign-in, selected model and network; retry once and do not repeatedly send tests.",agent_validation_response_mismatch:"The AI replied but not with hello. Check the selected model; a reply that ignores simple instructions usually means a wrong or misconfigured model.",agent_validation_response_failed:"The AI started but failed while replying. Check its provider sign-in, selected model, quota and network, then retry once."},l={agent_validation_runtime_unavailable:"start",agent_validation_workdir_invalid:"start",agent_validation_workdir_not_found:"start",agent_validation_workdir_not_directory:"start",agent_validation_workdir_unavailable:"start",agent_validation_start_failed:"start",agent_validation_runtime_auth_required:"auth",agent_validation_model_unavailable:"model",agent_validation_quota_exceeded:"quota",agent_validation_network_failed:"network",agent_validation_response_timeout:"reply",agent_validation_response_mismatch:"reply",agent_validation_response_failed:"reply"},c=9e4,u="A successful check only proves one standard-mode hello reply; it does not prove the AI can send messages in a Room.",h={authState:'unknown means Shennian has no login probe for this AI; it is not "not logged in".',defaultModelId:"null means no model is marked default; it does not mean no model is available. The AI picks its own model when none is passed."};async function _(t,a,o=s){try{const e=await o({method:t,params:a,timeoutMs:c});if(t==="agent.check"&&(!e||typeof e!="object"||Array.isArray(e)||e.ready!==!0))throw new r("agent_check_invalid_response","The daemon did not confirm a successful hello reply.");return t==="agent.discover"&&e&&typeof e=="object"&&!Array.isArray(e)?{ok:!0,command:t,result:{...e,notes:h}}:t==="agent.check"&&e&&typeof e=="object"&&!Array.isArray(e)?{ok:!0,command:t,result:{...e,note:u}}:{ok:!0,command:t,result:e}}catch(e){const n=e instanceof r?e.code:"agent_check_failed",i=l[n]??(t==="agent.check"&&n==="ipc_timeout"?"reply":void 0);return{ok:!1,command:t,error:n,...i?{stage:i}:{},nextAction:d[n]??"Return to the AI preparation guide on this machine. Check the selected directory, account, model and network; do not reinstall by default."}}}function g(t){const a=async(o,e)=>{const n=await _(o,e);process.stdout.write(`${JSON.stringify(n)}
|
|
2
|
+
`),n.ok||(process.exitCode=1)};t.command("discover").description("Read the daemon AI snapshot; use --refresh only to rerun local provider and model discovery").option("--agent <type>","Return only one discovered Agent type").option("--compact","Return capability summary without the full model list").option("--refresh","Rerun discovery; with --agent, probe only that Agent").option("--json","Print JSON (default)").action(o=>a("agent.discover",{...o.agent?{agentType:o.agent}:{},compact:o.compact===!0,refresh:o.refresh===!0})),t.command("check <agentType>").description("Send one hello test using the selected AI and model; uses provider quota, creates no managed Agent or room").requiredOption("--workdir <path>","Absolute project directory on this machine").option("--model <id>","Model returned by discovery; otherwise use the provider default").option("--json","Print JSON (default)").action((o,e)=>a("agent.check",{agentType:o,workDir:e.workdir,modelId:e.model??null}))}export{c as AGENT_READINESS_IPC_TIMEOUT_MS,_ as executeAgentReadinessCommand,g as registerAgentReadinessCommands};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import
|
|
2
|
-
`)}catch(a){if(a instanceof S){g(t,a.code,a.message,a.nextAction);return}if(a instanceof q){g(t,a.code,a.message,C(a.code));return}g(t,"room_command_failed","Shennian Room command failed.",null)}}function h(){return process.env[L]?.trim()||null}async function v(e,t,o){const a=process.env[N]?.trim();if(!a||!
|
|
3
|
-
`),e.fail()}export{
|
|
1
|
+
import R from"node:crypto";import U from"node:fs";import b from"node:path";import{PublicAttachmentCache as j}from"../room/public-attachment-cache.js";import{PublicRoomClient as k,PublicRoomClientError as S}from"../room/public-room-client.js";import{callDaemonIpc as D}from"../daemon-ipc/client.js";import{DaemonIpcError as q}from"../daemon-ipc/protocol.js";import{MANAGED_ROOM_IPC_ROOT_ENV as N,resolveManagedRoomIpcPaths as P}from"../daemon-ipc/paths.js";import{MANAGED_ROOM_CONTEXT_ENV as L}from"../room/managed-context.js";import{createRoomInviteQr as $}from"../room/invite-qr.js";import{externalRoomNextAction as I,managedRoomNextAction as C}from"../room/room-error-guidance.js";import{attachmentMediaKind as T}from"../room/attachment-upload.js";const E={write:e=>{process.stdout.write(e)},fail:()=>{process.exitCode=1}};function ie(e,t={}){const o=t.client??new k,a=t.cache??new j({client:o}),s=t.io??E,u=t.createInviteQr??$,c=e.command("room").description("Use one explicitly referenced Shennian Room");c.command("schedule").description("Manage scheduled work in the current managed conversation").requiredOption("--operation <operation>","list/create/update/pause/resume/delete").option("--id <id>").option("--version <version>").option("--operation-id <operationId>").option("--time-description <timeDescription>").option("--timezone <timezone>").option("--prompt <prompt>").action(async r=>p("room.schedule",s,async()=>{const n=h();return n||i("managed_room_context_required","Scheduling is only available in managed Room context.",I()),v(n,"schedule",{...r,version:r.version?Number(r.version):void 0})})),c.command("create").description("Create a Room as the paired user").requiredOption("--name <name>","Room name").action(async r=>p("room.create",s,async()=>{w("create");const n=r.name.trim();(!n||n.length>80)&&i("invalid_room_name","Room name must contain 1 to 80 characters.");const m=await o.create(n),d=_(m.room,"invalid_room_response");typeof d.inviteUrl!="string"&&i("invalid_room_response","Shennian returned an invalid Room invitation.");const f=await u(d.inviteUrl).catch(()=>({imagePath:null,mimeType:"image/png",payload:d.inviteUrl}));return{...m,sharing:{inviteUrl:d.inviteUrl,inviteQr:f}}})),c.command("join <invite-url>").description("Join or request access through an official Room invitation").option("--message <message>","Introduction and reason required when owner approval is enabled").action(async(r,n)=>p("room.join",s,async()=>{w("join");const m=A(r,"invite"),d=n.message?.trim();return d&&d.length>500&&i("join_message_too_long","Join request message must be at most 500 characters."),o.join(m.roomId,m.inviteToken,d)})),c.command("open <room-url>").description("Open one Room where the paired user is already a member").action(async r=>p("room.open",s,async()=>{w("open");const n=A(r,"stable");return o.open(n.roomId)})),c.command("status [room-ref]").description("Show the managed Room, or one explicitly referenced Room").action(async r=>p("room.status",s,()=>{const n=h();return n?(r&&i("managed_room_ref_forbidden","Do not pass a Room reference in managed Room context."),v(n,"status",{})):o.status(x(r))})),c.command("read [room-ref]").description("Read recent, older, or newer messages from one Room").option("--before <sequence>","Read messages before this sequence").option("--after <sequence>","Read messages after this sequence").option("--limit <count>","Maximum messages, 1 to 50","50").action(async(r,n)=>{await p("room.read",s,async()=>{n.before&&n.after&&i("ambiguous_sequence_cursor","--before and --after are mutually exclusive."),n.before&&!z(n.before)&&i("invalid_sequence","--before sequence is invalid."),n.after&&!z(n.after)&&i("invalid_sequence","--after sequence is invalid.");const m=Number(n.limit);(!Number.isInteger(m)||m<1||m>50)&&i("invalid_limit","--limit must be between 1 and 50.");const d=h();if(d)return r&&i("managed_room_ref_forbidden","Do not pass a Room reference in managed Room context."),v(d,"read",{...n.before?{before:n.before}:{},...n.after?{after:n.after}:{},limit:m});const f=x(r),y=await o.read(f,{before:n.before,after:n.after,limit:m});return H(y,f,a)})}),c.command("send [room-ref]").description("Send text and up to 10 files as the paired user").option("--text <text>","Message text").option("--file <path>","Local file path; repeat for multiple files",F,[]).action(async(r,n)=>{await p("room.send",s,async()=>{const m=n.text?.trim()??"";!m&&n.file.length===0&&i("room_message_empty","Provide --text or at least one --file."),n.file.length>10&&i("room_attachments_too_many","A message can contain at most 10 files.");const d=h();if(d)return r&&i("managed_room_ref_forbidden","Do not pass a Room reference in managed Room context."),v(d,"send",{clientMessageId:`managed:${R.randomUUID()}`,text:m,files:n.file});const f=x(r),y=[];for(const O of n.file)y.push(await J(o,f,O));return o.send(f,{clientMessageId:`cli:${R.randomUUID()}`,...m?{text:m}:{},...y.length>0?{attachmentIds:y}:{}})})}),c.command("pause").description("Pause the current managed Agent in its bound Room").requiredOption("--reason <reason>","Short reason shown to all Room members").action(async r=>p("room.pause",s,()=>{const n=h();return n||i("managed_room_context_required","Pause is only available in managed Room context.",I()),v(n,"pause",{reason:r.reason})})),c.command("attachment").description("Download Room attachments through Shennian").command("get [room-ref] [attachment-id]").description("Download and verify one Room attachment").action(async(r,n)=>{await p("room.attachment.get",s,async()=>{const m=h(),d=m?r:n;return(!d||!/^ratt_[A-Za-z0-9_-]{1,191}$/.test(d))&&i("invalid_attachment_ref","attachment-id is invalid."),m?v(m,"attachment.get",{attachmentId:d}):a.materialize(x(r),d)})}),c.command("leave <room-ref>").description("Leave one explicitly referenced Room").action(async r=>p("room.leave",s,()=>(w("leave"),o.leave(M(r))))),c.command("list",{hidden:!0}).action(()=>g(s,"command_removed","Room listing is not available to Agents.",null)),c.command("search",{hidden:!0}).allowUnknownOption(!0).action(()=>g(s,"command_removed","Room search is not available to Agents.",null)),c.command("agent",{hidden:!0}).allowUnknownOption(!0).action(()=>g(s,"command_removed","Managed Agents are created in the Shennian mobile App.",null))}async function p(e,t,o){try{const a=await o();t.write(`${JSON.stringify({ok:!0,command:e,result:a})}
|
|
2
|
+
`)}catch(a){if(a instanceof S){g(t,a.code,a.message,a.nextAction);return}if(a instanceof q){g(t,a.code,a.message,C(a.code));return}g(t,"room_command_failed","Shennian Room command failed.",null)}}function h(){return process.env[L]?.trim()||null}async function v(e,t,o){const a=process.env[N]?.trim();if(!a||!b.isAbsolute(a))throw new q("managed_room_context_unavailable","Managed Room IPC context is unavailable.");return D({method:"managed-room.execute",params:{context:e,action:t,...o},paths:P(a),timeoutMs:t==="send"||t==="attachment.get"?18e4:15e3})}function w(e){h()&&i("managed_room_action_forbidden",`${e} is not available in managed Room context.`)}function x(e){return e||i("room_ref_required","Use an official Room URL returned by Shennian.",I()),M(e)}function M(e){return A(e,"stable").roomId}function A(e,t){let o;const a=t==="invite"?"invalid_invite_url":"invalid_room_ref",s=t==="invite"?"Use an official Shennian Room invitation URL.":"Use an official Room URL returned by Shennian.";try{o=new URL(e)}catch{i(a,s)}const u=o.pathname.match(/^\/spaces\/([A-Za-z0-9_-]{1,191})$/),c=o.searchParams.get("invite");return(o.protocol!=="https:"||o.hostname!=="app.shennian.net"||o.port||o.username||o.password||o.hash||!u||o.searchParams.has("access")||[...o.searchParams.keys()].some(l=>l!=="invite")||t==="stable"&&c!=null||t==="invite"&&(c==null||!/^[A-Za-z0-9_-]{32,256}$/.test(c)))&&i(a,s),{roomId:u[1],...c?{inviteToken:c}:{}}}function z(e){return/^(0|[1-9]\d{0,19})$/.test(e)}function F(e,t){return[...t,e]}async function J(e,t,o){const a=b.resolve(o);let s;try{s=await U.promises.lstat(a)}catch{i("file_not_found",`File does not exist: ${o}`)}(!s.isFile()||s.isSymbolicLink())&&i("invalid_file",`Not a regular file: ${o}`),(s.size<1||s.size>100*1024*1024)&&i("invalid_file_size","Each file must be between 1 byte and 100 MB.");const u=await U.promises.readFile(a),c=R.createHash("sha256").update(u).digest("hex"),l=Q(a),r=await e.initializeAttachment(t,{clientUploadId:`cli:${R.randomUUID()}`,name:b.basename(a),mediaKind:T(b.basename(a),l),mimeType:l,byteSize:s.size,sha256:c}),n=_(r.attachment,"invalid_attachment_response"),m=_(r.upload,"invalid_attachment_response");(typeof n.id!="string"||typeof m.url!="string"||m.method!=="PUT")&&i("invalid_attachment_response","Shennian returned an invalid attachment response.");const d=_(m.headers,"invalid_attachment_response");return Object.values(d).some(f=>typeof f!="string")&&i("invalid_attachment_response","Shennian returned invalid upload headers."),Z(m.url),await e.upload(m.url,d,u),await e.completeAttachment(t,n.id),n.id}function Q(e){const t=b.extname(e).toLowerCase();return{".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".webp":"image/webp",".gif":"image/gif",".heic":"image/heic",".heif":"image/heif",".mp3":"audio/mpeg",".wav":"audio/wav",".m4a":"audio/mp4",".mp4":"video/mp4",".mov":"video/quicktime",".pdf":"application/pdf",".txt":"text/plain",".md":"text/markdown",".json":"application/json",".csv":"text/csv",".zip":"application/zip",".docx":"application/vnd.openxmlformats-officedocument.wordprocessingml.document",".xlsx":"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}[t]??"application/octet-stream"}function H(e,t,o){return Array.isArray(e.messages)||i("invalid_server_response","Shennian returned invalid messages."),{messages:e.messages.map(a=>{const s=_(a,"invalid_server_response"),u=Array.isArray(s.attachments)?s.attachments:[];return{...s,attachments:u.map(c=>{const l=_(c,"invalid_server_response");return(typeof l.attachmentId!="string"||typeof l.name!="string"||typeof l.mimeType!="string"||typeof l.byteSize!="string"||typeof l.sha256!="string")&&i("invalid_server_response","Shennian returned invalid attachment metadata."),{...l,...o.lookup(t,l)}})}})}}function _(e,t){return(!e||typeof e!="object"||Array.isArray(e))&&i(t,"Invalid structured response."),e}function Z(e){let t;try{t=new URL(e)}catch{i("invalid_attachment_response","Invalid upload URL.")}const o=t.protocol==="http:"&&(t.hostname==="localhost"||t.hostname==="127.0.0.1");(t.protocol!=="https:"&&!o||t.username||t.password||t.hash)&&i("invalid_attachment_response","Invalid upload URL.")}function i(e,t,o=null){throw new S(e,t,o)}function g(e,t,o,a){e.write(`${JSON.stringify({ok:!1,error:t,message:o,nextAction:a})}
|
|
3
|
+
`),e.fail()}export{ie as registerRoomCommand};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isAvailableAgentType as
|
|
1
|
+
import{isAvailableAgentType as a}from"@shennian/wire";import{DaemonIpcError as n}from"./protocol.js";function g(s){let d=!1;const l=()=>{const e=s.getStatus();if(e.remoteAccessDisabled)throw new n("daemon_paused","Shennian is paused; resume it only when requested.");if(!e.running)throw new n("daemon_unavailable","Shennian is not running.")};return{"agent.discover":async e=>{l();const o=e&&typeof e=="object"&&!Array.isArray(e)&&typeof e.agentType=="string"?e.agentType:null,u=!!(e&&typeof e=="object"&&!Array.isArray(e)&&e.compact===!0),p=!!(e&&typeof e=="object"&&!Array.isArray(e)&&e.refresh===!0),y=(p?await s.discover(o??void 0):s.getSnapshot()).filter(t=>!o||t.type===o);return{modelTested:!1,compact:u,refreshed:p,agents:y.map(t=>{const i=t.models.find(r=>r.isDefault),c=(i?[i,...t.models.filter(r=>r.id!==i.id)]:t.models).slice(0,10).map(r=>({id:r.id,name:r.name,isDefault:r.isDefault===!0}));return{type:t.type,name:t.display?.name??t.type,authState:t.authState??"unknown",...t.runtime?.cliVersion?{version:t.runtime.cliVersion}:{},...t.runtime?.compatibility?{compatibility:t.runtime.compatibility}:{},...t.runtimePlatform?{runtimePlatform:t.runtimePlatform}:{},...t.runtimeIsWsl!==void 0?{runtimeIsWsl:t.runtimeIsWsl}:{},supportsStandardSession:t.supportsStandardSession===!0,...t.supportsAgent!==void 0?{supportsAgent:t.supportsAgent}:{},...t.supportedAgentExecutionModes?{supportedAgentExecutionModes:t.supportedAgentExecutionModes}:{},supportsManagedAgent:t.supportsManagedAgent===!0,supportsManagedNativeFull:t.supportsManagedNativeFull===!0,supportsRoomTools:t.supportsRoomTools===!0,supportsWorker:t.supportsWorker===!0,supportsManagerMode:t.supportsManagerMode===!0,supportsExternalWorkerTarget:t.supportsExternalWorkerTarget===!0,modelCount:t.models.length,defaultModelId:i?.id??null,...u?{}:{models:c,modelsTruncated:t.models.length>c.length}}})}},"agent.check":async e=>{if(l(),!e||typeof e!="object"||Array.isArray(e)||typeof e.agentType!="string"||!a(e.agentType)||typeof e.workDir!="string"||!e.workDir.trim()||e.modelId!=null&&typeof e.modelId!="string")throw new n("agent_validation_invalid_configuration","Select an AI and its absolute working directory.");if(d)throw new n("agent_check_in_progress","An AI reply check is already running.");d=!0;try{const o=await s.validate({runtimeAdapter:e.agentType,workDir:e.workDir,modelId:e.modelId??null,sessionMode:"standard"});if(!o.ok)throw new n(o.errorCode,o.errorCode);return{ready:!0,agentType:e.agentType,modelId:e.modelId??null,scope:"hello_reply"}}finally{d=!1}}}}export{g as createAgentReadinessHandlers};
|
package/dist/src/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{registerScheduleCommand as Se}from"./commands/schedule.js";import{resolvePersonalScheduleContext as ye}from"./schedules/personal-context.js";import{executeServerSchedule as oe,deliverServerSchedule as we}from"./schedules/server-client.js";import{Command as Ce}from"commander";import
|
|
2
|
-
`),process.exitCode=1}function qo(a){const s=[`trigger=${a.trigger??"unknown"}`,`phase=${a.phase}`,`code=${a.code??"unknown"}`,`attempt=${a.reconnectAttempt}`];return a.reason&&s.push(`reason=${a.reason}`),a.error&&s.push(`error=${a.error}`),s.join(" ")}const i=new Ce;i.name("shennian").description("Shennian \u2014 AI Agent Control Plane").version(z),i.enablePositionalOptions(),Ke(i),qe(i),Se(i),i.option("--api <url>","Server URL override").option("--name <name>","Machine name",Re.hostname()).action(async a=>{const s=p(),g=a.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??s.serverUrl??void 0;await _e(g,a.name)}),i.command("run-service",{hidden:!0}).description("(internal) Connect to relay server, called by the background service").option("--api <url>","Server URL override").action(async a=>{const s=G("env.json");try{const e=JSON.parse(F.readFileSync(s,"utf-8"));for(const[n,o]of Object.entries(e))process.env[n]||(process.env[n]=o);le()}catch{}Te(ne(process.env,p()));const g=fo();ae()&&(console.log(`[${new Date().toISOString()}] remote access disabled, service start skipped`),clearInterval(g),process.exit(0));const c=Ne(),w=co({lockPath:zo,instanceId:c});w.acquired||(console.log(`[${new Date().toISOString()}] daemon already running`+(w.ownerPid?` (PID ${w.ownerPid})`:"")+", skipping duplicate start"),clearInterval(g),process.exit(0));try{const e=Jo();if(e&&e!==process.pid)try{process.kill(e,0),console.log(`[${new Date().toISOString()}] daemon already running (PID ${e}), skipping duplicate start`),w.release(),process.exit(0)}catch{}}catch{}const K=Ve(process.env,fe)?[]:xe(process.pid);K.length>0&&(console.log(`[${new Date().toISOString()}] daemon already running (PID ${K[0]}), skipping duplicate start`),w.release(),process.exit(0)),se(process.pid,c,{version:z}),ce(process.pid,void 0,c),process.on("exit",()=>{te(process.pid,c),re(c)}),process.on("exit",w.release);let $=null;try{$=ro()}catch(e){console.error(`[upgrade] updater bootstrap unavailable: ${e instanceof Error?e.message:String(e)}`)}const D=await oo();if(D){$||(console.error("[upgrade] Crash-loop rollback blocked: updater bootstrap unavailable"),process.exit(1));try{ao({fromVersion:z,targetVersion:D.from,oldDaemonPid:process.pid,operation:"rollback",rollbackJobId:D.jobId},{readiness:$}),console.error(`[upgrade] New version crashed ${D.attemptCount} times; external rollback scheduled.`),process.stdin.resume();return}catch(e){console.error(`[upgrade] Crash-loop rollback could not start: ${e instanceof Error?e.message:String(e)}`),process.exit(1)}}try{Bo()}catch(e){console.error(`[personal-sync] data epoch initialization failed: ${e instanceof Error?e.message:String(e)}`),process.exitCode=1;return}const m=p(),u=a.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??m.serverUrl??W.cn.url,h=de(),l=await so({serverUrl:u,authToken:m.machineToken??m.accessToken}),C=l.map(e=>e.type),V=O(l),f=new Ro,U=new Po,R=new $o(f),S=new bo(u),ve=new xo({actionClient:S}),Ie=new Oo({actionClient:S});let y=null,r;const q=Vo({authority:R,actionClient:S,attachmentCache:ve,attachmentUploader:Ie,observations:U,getMachineCredentials:()=>({machineToken:p().machineToken}),managerExecutor:e=>r.executeManagedManagerAction(e),onSendOutcome:e=>r.noteManagedRoomSendOutcome(e),scheduleExecutor:(e,n,o)=>oe({serverUrl:u,machineToken:p().machineToken,sessionId:e,action:n,args:o}),onPaused:({registryKey:e,sessionId:n})=>{const t=f.getByRegistryKey(e)?.binding;t&&f.setBinding({registryKey:e,bindingId:t.bindingId,roomId:t.roomId,agentIdentityId:t.agentIdentityId,agentMembershipId:t.agentMembershipId,status:"paused",activationMode:"paused"}),R.revokeSession(e),r?.discardPendingManagedRoomMessages(n)}});let L=null,j=null,H=null;const b=await me({paths:ho(),handlers:{...Io({daemonVersion:h,protocolVersion:Co,getStatus:()=>ie(),getAgents:()=>l,completeAgentAuthentication:e=>r.resumePendingAcpAuthentication(e),startUpgrade:async e=>{if(!L)throw new Error("upgrade_unavailable");return L(e)},prepareExternalUpgrade:e=>{if(!j)throw new Error("external_upgrade_unavailable");return j(e)},cancelExternalUpgrade:()=>{if(!H)throw new Error("external_upgrade_unavailable");return H()}}),...So({getStatus:()=>ie(),getSnapshot:()=>O(l).map(e=>{const n=l.find(o=>o.type===e.type);return{...e,runtimePlatform:n?.runtimePlatform,runtimeIsWsl:n?.runtimeIsWsl}}),discover:async e=>{if(!r)throw new Error("daemon_starting");const n=await yo(!0,e);if(e){const t=l.filter(I=>I.type!==e);l.splice(0,l.length,...t,...n.detectedAgents)}else l.splice(0,l.length,...n.detectedAgents);const o=O(l);return d.sendEvent({type:"event",event:"machine.agents",payload:{agentList:l.map(t=>t.type),agents:o}}),n.agents.map(t=>{const I=n.detectedAgents.find(B=>B.type===t.type);return{...t,runtimePlatform:I?.runtimePlatform,runtimeIsWsl:I?.runtimeIsWsl}})},validate:async e=>{if(!r)throw new Error("daemon_starting");return wo(e,{ignoreNativeSession:(n,o)=>P?.ignoreNativeSource(n,o)})}})}});let k;try{k=await me({paths:vo(),handlers:{"managed-room.execute":e=>q(e),"schedule.personal":async e=>{const n=e,o=ye(n?.context);if(f.getByNianSessionId(o)?.binding)throw new Error("schedule_personal_context_required");return await oe({serverUrl:u,machineToken:p().machineToken,sessionId:o,action:n.operation,args:n})}}})}catch(e){throw await b.close(),e}let Q=!1;const _=()=>{Q||(Q=!0,Promise.allSettled([b.close(),k.close()]).finally(()=>process.exit(0)))};if(process.once("SIGINT",_),process.once("SIGTERM",_),!m.machineToken){console.error('Shennian CLI is not paired. Run "shennian pair --browser" or "shennian pair --token" first.'),await Promise.allSettled([b.close(),k.close()]),process.exitCode=1;return}process.removeListener("SIGINT",_),process.removeListener("SIGTERM",_);const Y=`${Ko(u)}/relay/machine`,M=m.machineToken;if(!M)return;try{await Ho({serverUrl:u,machineToken:M,machineId:m.machineId})}catch(e){console.error(`[personal-sync] machine authority verification failed: ${e instanceof Error?e.message:String(e)}`),await Promise.allSettled([b.close(),k.close()]),process.exit(1)}m.machineId&&mo(u,m.machineId),console.log(`[${new Date().toISOString()}] Connecting to ${Y}... (v${h}) agents: ${C.join(",")}`),T({level:"info",wsEvent:"daemon.start",metadata:{version:h,agents:C}});let P=null,E=null,N=0,v=no();const Z=Ee(m.autoUpgrade),d=new Ae({serverUrl:Y,machineToken:M,cliVersion:h,agentList:C,autoUpgradePolicy:Z,sessionSyncProtocolVersion:pe,sessionSyncCapabilities:ge,daemonInstallationId:Lo(),dataEpoch:ue,updaterProtocolVersion:1,onConnected:()=>{console.log(`[${new Date().toISOString()}] \u2713 Connected`),T({level:"info",wsEvent:"daemon.connected"}),V.some(o=>o.models.length>0)&&d.sendEvent({type:"event",event:"machine.agents",payload:{agentList:C,agents:V}}),lo(l,{serverUrl:m.serverUrl??u,authToken:m.machineToken??m.accessToken}).then(o=>{JSON.stringify(o)!==JSON.stringify(V)&&d.sendEvent({type:"event",event:"machine.agents",payload:{agentList:C,agents:o}})}).catch(()=>{}),v&&d.sendEvent({type:"event",event:"upgrade",payload:{state:"recovering",machineId:"self",from:v.from,to:v.to,...v.jobId?{jobId:v.jobId}:{},activeRunCount:r.getPendingRecoveryCount()}}),x.start(),E?.start();const e=++N,n=async()=>{try{if(await r.synchronizeSessionDeletions(),await r.recoverInterruptedRuns(),v){const o=v;v=null,o.jobId&&io(o.jobId,o.to),eo(),console.log(`[${new Date().toISOString()}] [upgrade] ${o.operation==="rollback"?"Rolled back":"Healthy"}: ${o.from} \u2192 ${o.to}`),po({kind:"upgrade",result:o.operation==="rollback"?"rolled_back":"healthy",stage:"terminal"}),d.sendEvent({type:"event",event:"upgrade",payload:{state:o.operation==="rollback"?"rolled_back":"healthy",machineId:"self",from:o.from,to:o.to,...o.jobId?{jobId:o.jobId}:{}}}),o.operation!=="rollback"&&d.sendEvent({type:"event",event:"upgrade",payload:{state:"success",machineId:"self",from:o.from,to:o.to}})}e===N&&d.getState()==="connected"&&P?.handleConnected()}catch(o){if(console.error("[session.deletions] startup catch-up failed",o),e===N&&d.getState()==="connected"){if(o instanceof Go)return;const t=o instanceof Fo?Math.max(1e3,o.retryAfterMs):5e3;setTimeout(()=>{n()},t).unref?.()}}};n()},onDisconnected:e=>{N+=1,r?.revokeProjectFsHandles(),E?.stop(),console.log(`[${new Date().toISOString()}] \u26A0 Disconnected, reconnecting... ${qo(e)}`),T({level:"warn",wsEvent:"daemon.disconnected",metadata:{code:e.code,reason:e.reason,error:e.error,phase:e.phase,trigger:e.trigger,reconnectAttempt:e.reconnectAttempt}})},onProjectFsCapabilityChanged:e=>{e||r?.revokeProjectFsHandles()},onReq:e=>{if(console.log(`[${new Date().toISOString()}] [req] ${e.method}`),T({level:"info",type:"ws",wsEvent:e.method,wsDirection:"in",traceId:e.traceId}),e.method==="room.message.deliver"){Ao(d,E,e);return}r.handleReq(e)}});P=go()?new uo(d):null,E=new Eo({registry:f,observations:U,actionClient:S,getMachineCredentials:()=>({machineToken:p().machineToken}),isSessionDeleted:e=>new To().hasSession(e),onMessages:async(e,n,o,t)=>{await y?.processMessages(e,n,o,t)},onBindingPaused:e=>{R.revokeSession(e.registryKey),e.lifecycleState==="closed"?r?.retireManagedRoomSession(e.nianSessionId).catch(()=>{console.warn("[room.sync] retired session cleanup failed")}):r?.discardPendingManagedRoomMessages(e.nianSessionId)}});const x=new to({currentVersion:h,machineId:m.machineId??"unknown-machine",serverUrl:m.serverUrl??u,getIdleState:()=>r.getUpgradeIdleState(),sessionSyncProtocolVersion:pe,dataEpoch:ue,advertisedCapabilities:ge,updaterProtocolVersion:1,startUpgrade:async(e,n)=>{const{handleUpgradeStart:o}=await import("./commands/upgrade.js");await o(d,"auto-upgrade",e,{currentVersion:h,prepareForUpgrade:t=>r.prepareForUpgrade(t),cancelUpgradePreparation:()=>r.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()},suppressResponse:!0,waitForCompletion:!0,canHandoff:n?void 0:()=>r.getUpgradeIdleState().idle})}});x.setPolicy(Z),r=new Qe(d,P,h,x,new ko({registry:f,observations:U,bindingClient:S,getMachineCredentials:()=>{const e=p();return{machineId:e.machineId,machineToken:e.machineToken}},hostProcessIdentity:()=>`daemon:${c}`}),{prepare:()=>Oe(),isDisabled:()=>ae(),shutdownAfterResponse:()=>{setTimeout(()=>{$e(),process.kill(process.pid,"SIGTERM")},100).unref?.()}},e=>R.revokeToken(e),q,null,Ye(M),()=>O(l)),L=async e=>{const n=e&&typeof e=="object"&&!Array.isArray(e)?e:{},o=typeof n.targetVersion=="string"?n.targetVersion:void 0,t=n.confirmedMajor===!0;return await Ge(d,`local-upgrade-${Date.now()}`,o,{currentVersion:h,confirmedMajor:t,prepareForUpgrade:B=>r.prepareForUpgrade(B),cancelUpgradePreparation:()=>r.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()},suppressResponse:!0})},j=e=>{const n=e&&typeof e=="object"&&!Array.isArray(e)?e:{},t=`desktop-${(typeof n.targetVersion=="string"?n.targetVersion:"unknown").replace(/[^A-Za-z0-9._-]/g,"_")}-${Date.now()}`;return{jobId:t,...r.prepareForUpgrade(t)}},H=()=>(r.cancelUpgradePreparation(),{cancelled:!0}),y=new _o({registry:f,observations:U,enqueueConversationBatch:e=>r.enqueueManagedRoomBatch(e),loadContext:async e=>{const n=p().machineToken?.trim(),o=e.binding;if(!n||!o)throw new Error("managed_room_context_unavailable");const t=await S.status({machineToken:n,roomId:o.roomId,bindingId:o.bindingId});if(!Uo(f,e)||!Do(e,t))throw new Error("managed_room_binding_unavailable");return{roomName:t.room.name,memberCount:t.room.memberCount,members:t.room.members,agentsByIdentityId:t.agent?{[t.agentIdentityId]:t.agent}:{},agentSystemPromptsByIdentityId:t.agent?{[t.agentIdentityId]:t.agent.systemPrompt??""}:{}}},issueManagedRoomContext:(e,n,o,t)=>{const I=p().machineToken;if(!I)throw new Error("machine_not_authorized");return R.issue({registryKey:e,machineCredential:I,activationCauseMessageId:n,allowedActions:o,propagateWorkerRoomOrigin:t})},audit:new No,resolveManagedAgentPolicy:e=>Mo({...e,runtime:l.find(n=>n.type===e.registration.runtimeAdapter)})}),r.scheduleHandler=e=>we({serverUrl:u,machineToken:p().machineToken,req:e,client:d,queue:r.getScheduleQueue(),registry:f}),r.getScheduleQueue().validateScheduledTarget=async e=>{if(!e.bindingId||!e.roomId)return"ready";if(f.getByNianSessionId(e.sessionId)?.binding?.bindingId!==e.bindingId)return"ended";const o=await S.status({machineToken:p().machineToken,roomId:e.roomId,bindingId:e.bindingId});return o.status==="active"&&o.room.status==="active"?"ready":"ended"},r.setManagedRoomTurnPreparer(e=>y.prepareConversationTurn(e)),r.setManagedAgentDirectTurnPreparer((e,n)=>y.prepareDirectConversationTurn(e,n)),r.setManagedRecoveryPreparer(async e=>e.source==="managed_direct"?y.prepareDirectConversationTurn(e.sessionId,jo):e.source==="manager_event"?y.prepareDirectConversationTurn(e.sessionId,{allowedRoomActions:[],managerEvent:!0}):e.source!=="managed_room"||!e.roomContextRegistryKey||!e.activationCauseMessageId||!e.rootCauseMessageId||!e.activationSequence||e.activationHopCount===null?null:y.prepareConversationTurn([{registryKey:e.roomContextRegistryKey,causeMessageId:e.activationCauseMessageId,rootCauseMessageId:e.rootCauseMessageId,sequence:e.activationSequence,hopCount:e.activationHopCount}])),F.mkdirSync(fe,{recursive:!0}),se(process.pid,c,{version:h}),ce(process.pid,void 0,c),d.connect(),process.stdin.resume();let X=!1;const ee=async()=>{if(!X){X=!0,console.log(A.gray(`
|
|
3
|
-
Disconnecting...`)),T({level:"info",wsEvent:"daemon.stop"}),clearInterval(g);try{await r.cleanup(),x.stop(),P?.stop(),
|
|
1
|
+
import{registerScheduleCommand as Se}from"./commands/schedule.js";import{resolvePersonalScheduleContext as ye}from"./schedules/personal-context.js";import{executeServerSchedule as oe,deliverServerSchedule as we}from"./schedules/server-client.js";import{Command as Ce}from"commander";import E from"chalk";import Re from"node:os";import F from"node:fs";import{loadConfig as p,saveConfig as be,getConfigPath as ke,getShennianDir as Pe,resolveShennianPath as G,normalizeAutoUpgradePolicy as Ae}from"./config/index.js";import{CliRelayClient as Ee}from"./relay/client.js";import{collectKnownLogSecrets as ne,installCliLogRedaction as Te,sanitizeCliLogValue as De}from"./log-redaction.js";import{registerPairCommand as Ue,runSmartStart as Me}from"./commands/pair.js";import{registerLogoutCommand as _e}from"./commands/logout.js";import{clearDaemonPidIfOwner as te,createDaemonInstanceId as Ne,clearDaemonLauncher as re,findRunningDaemonProcessIds as xe,isRemoteAccessDisabled as ae,prepareRemoteAccessPause as Oe,stopPreparedRemoteAccessService as $e,isIsolatedDaemonE2eInstance as Ve,getDaemonStatus as ie,registerDaemonCommand as Le,writeDaemonPid as se,writeDaemonLauncher as ce}from"./commands/daemon.js";import{registerRuntimeCommand as je}from"./commands/runtime.js";import{registerAgentCommand as He}from"./commands/agent.js";import{registerManagerCommand as Be}from"./commands/manager.js";import{registerWorkerCommand as Fe}from"./commands/worker.js";import{handleUpgradeStart as Ge,registerUpgradeCommand as We}from"./commands/upgrade.js";import{registerToolsCommand as ze}from"./commands/tools.js";import{registerQueueCommand as Je}from"./commands/queue.js";import{registerIntegrationCommand as Ke}from"./commands/integration.js";import{registerRoomCommand as qe}from"./commands/room.js";import{SessionManager as Qe}from"./session/manager.js";import{createTrustedProjectFsRequestResolver as Ye}from"./session/handlers/project-fs.js";import{SERVERS as W,regionToUrl as Ze,urlToRegion as Xe}from"./region.js";import{clearUpgradeAttempt as eo,getCurrentVersion as de,handleStartupCrashCheck as oo,readUpgradeAttempt as no}from"./upgrade/engine.js";import{AutoUpgradeScheduler as to}from"./upgrade/scheduler.js";import{ensureUpdaterInstalled as ro}from"./upgrade/updater-bootstrap.js";import{launchUpdater as ao,markUpdaterHealthy as io}from"./upgrade/updater-launcher.js";import{detectAgents as so}from"./agents/detect.js";import{augmentProcessPath as le}from"./env-path.js";import{acquireDaemonInstanceLock as co}from"./commands/daemon-instance-lock.js";le();const z=de();import{getCachedAgentInfos as O,resolveAgentInfos as lo}from"./agents/model-registry.js";import{initCliLogReporter as mo,reportLog as T,reportUpgradeAggregateMetric as po}from"./log-reporter.js";import{isNativeFusionEnabled as go}from"./native-fusion/config.js";import{NativeSessionFusionService as uo}from"./native-fusion/service.js";import{startDaemonLogRetention as fo}from"./daemon-log.js";import{startDaemonIpcServer as me}from"./daemon-ipc/server.js";import{resolveDaemonIpcPaths as ho,resolveManagedRoomIpcPaths as vo}from"./daemon-ipc/paths.js";import{createDaemonControlHandlers as Io}from"./daemon-ipc/control.js";import{createAgentReadinessHandlers as So}from"./daemon-ipc/agent-readiness.js";import{scanMachineAgents as yo}from"./session/handlers/agents.js";import{validateAgentWorkspaceConfiguration as wo}from"./session/handlers/agent-workspace-validation.js";import{DAEMON_IPC_VERSION as Co}from"./daemon-ipc/protocol.js";import{RoomSessionRegistry as Ro}from"./room/session-registry.js";import{HttpRoomBindingClient as bo}from"./room/room-binding-client.js";import{TrustedManagedRoomBinder as ko}from"./room/managed-room-binding.js";import{RoomHostSessionObservationStore as Po}from"./room/host-session-observations.js";import{RoomMirrorSyncService as Ao}from"./room/mirror-sync-service.js";import{handleLiveRoomDelivery as Eo}from"./room/live-delivery.js";import{NativeSourceDeletionStore as To}from"./native-fusion/native-source-deletions.js";import{matchesRoomOwner as Do,isCurrentRegistration as Uo}from"./room/registration-authority.js";import{RoomActivationService as Mo,resolveManagedAgentPolicy as _o}from"./room/activation-service.js";import{RoomActivationAudit as No}from"./room/activation-audit.js";import{RoomAttachmentCache as xo}from"./room/attachment-cache.js";import{RoomAttachmentUploader as Oo}from"./room/attachment-upload.js";import{ManagedRoomContextAuthority as $o}from"./room/managed-context.js";import{createManagedRoomExecutor as Vo}from"./room/managed-room-ipc.js";import{SESSION_SYNC_PROTOCOL_VERSION as pe,SESSION_SYNC_ADVERTISED_CAPABILITIES as ge}from"@shennian/wire";import{getDaemonInstallationId as Lo}from"./session/store.js";import{OWNER_DIRECT_TURN_OPTIONS as jo}from"./session/queue-helpers.js";import{synchronizeMachinePersonalAuthority as Ho}from"./personal-sync/machine-authority.js";import{CLI_SESSION_SYNC_DATA_EPOCH as ue,ensurePersonalDataEpoch as Bo}from"./personal-sync/epoch.js";import{DeletionSyncRetryWaitError as Fo,DeletionSyncTerminalError as Go}from"./session/deletion-sync-state.js";const fe=Pe(),Wo=G("daemon.pid"),zo=G("daemon.instance.lock");function Jo(){try{const a=F.readFileSync(Wo,"utf-8").trim();if(a.startsWith("{")){const g=JSON.parse(a),c=Number(g.pid);return Number.isInteger(c)&&c>0?c:null}const s=parseInt(a,10);return Number.isInteger(s)&&s>0?s:null}catch{return null}}function Ko(a){return a.replace(/^https:\/\//,"wss://").replace(/^http:\/\//,"ws://")}function J(){process.stdout.write(`${JSON.stringify({ok:!1,error:"integration_command_retired",message:"Room MCP and Hooks were retired. Use the Shennian Skill and CLI.",nextAction:{type:"open_install_guide",url:"https://shennian.net/install.md"}})}
|
|
2
|
+
`),process.exitCode=1}function qo(a){const s=[`trigger=${a.trigger??"unknown"}`,`phase=${a.phase}`,`code=${a.code??"unknown"}`,`attempt=${a.reconnectAttempt}`];return a.reason&&s.push(`reason=${a.reason}`),a.error&&s.push(`error=${a.error}`),s.join(" ")}const i=new Ce;i.name("shennian").description("Shennian \u2014 AI Agent Control Plane").version(z),i.enablePositionalOptions(),Ke(i),qe(i),Se(i),i.option("--api <url>","Server URL override").option("--name <name>","Machine name",Re.hostname()).action(async a=>{const s=p(),g=a.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??s.serverUrl??void 0;await Me(g,a.name)}),i.command("run-service",{hidden:!0}).description("(internal) Connect to relay server, called by the background service").option("--api <url>","Server URL override").action(async a=>{const s=G("env.json");try{const e=JSON.parse(F.readFileSync(s,"utf-8"));for(const[n,o]of Object.entries(e))process.env[n]||(process.env[n]=o);le()}catch{}Te(ne(process.env,p()));const g=fo();ae()&&(console.log(`[${new Date().toISOString()}] remote access disabled, service start skipped`),clearInterval(g),process.exit(0));const c=Ne(),w=co({lockPath:zo,instanceId:c});w.acquired||(console.log(`[${new Date().toISOString()}] daemon already running`+(w.ownerPid?` (PID ${w.ownerPid})`:"")+", skipping duplicate start"),clearInterval(g),process.exit(0));try{const e=Jo();if(e&&e!==process.pid)try{process.kill(e,0),console.log(`[${new Date().toISOString()}] daemon already running (PID ${e}), skipping duplicate start`),w.release(),process.exit(0)}catch{}}catch{}const K=Ve(process.env,fe)?[]:xe(process.pid);K.length>0&&(console.log(`[${new Date().toISOString()}] daemon already running (PID ${K[0]}), skipping duplicate start`),w.release(),process.exit(0)),se(process.pid,c,{version:z}),ce(process.pid,void 0,c),process.on("exit",()=>{te(process.pid,c),re(c)}),process.on("exit",w.release);let $=null;try{$=ro()}catch(e){console.error(`[upgrade] updater bootstrap unavailable: ${e instanceof Error?e.message:String(e)}`)}const D=await oo();if(D){$||(console.error("[upgrade] Crash-loop rollback blocked: updater bootstrap unavailable"),process.exit(1));try{ao({fromVersion:z,targetVersion:D.from,oldDaemonPid:process.pid,operation:"rollback",rollbackJobId:D.jobId},{readiness:$}),console.error(`[upgrade] New version crashed ${D.attemptCount} times; external rollback scheduled.`),process.stdin.resume();return}catch(e){console.error(`[upgrade] Crash-loop rollback could not start: ${e instanceof Error?e.message:String(e)}`),process.exit(1)}}try{Bo()}catch(e){console.error(`[personal-sync] data epoch initialization failed: ${e instanceof Error?e.message:String(e)}`),process.exitCode=1;return}const m=p(),u=a.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??m.serverUrl??W.cn.url,h=de(),l=await so({serverUrl:u,authToken:m.machineToken??m.accessToken}),C=l.map(e=>e.type),V=O(l),f=new Ro,U=new Po,R=new $o(f),S=new bo(u),ve=new xo({actionClient:S}),Ie=new Oo({actionClient:S});let y=null,r;const q=Vo({authority:R,actionClient:S,attachmentCache:ve,attachmentUploader:Ie,observations:U,getMachineCredentials:()=>({machineToken:p().machineToken}),managerExecutor:e=>r.executeManagedManagerAction(e),onSendOutcome:e=>r.noteManagedRoomSendOutcome(e),getActiveRunId:(e,n)=>r.getManagedRoomActiveRunId(e,n),scheduleExecutor:(e,n,o)=>oe({serverUrl:u,machineToken:p().machineToken,sessionId:e,action:n,args:o}),onPaused:({registryKey:e,sessionId:n})=>{const t=f.getByRegistryKey(e)?.binding;t&&f.setBinding({registryKey:e,bindingId:t.bindingId,roomId:t.roomId,agentIdentityId:t.agentIdentityId,agentMembershipId:t.agentMembershipId,status:"paused",activationMode:"paused"}),R.revokeSession(e),r?.discardPendingManagedRoomMessages(n)}});let L=null,j=null,H=null;const b=await me({paths:ho(),handlers:{...Io({daemonVersion:h,protocolVersion:Co,getStatus:()=>ie(),getAgents:()=>l,completeAgentAuthentication:e=>r.resumePendingAcpAuthentication(e),startUpgrade:async e=>{if(!L)throw new Error("upgrade_unavailable");return L(e)},prepareExternalUpgrade:e=>{if(!j)throw new Error("external_upgrade_unavailable");return j(e)},cancelExternalUpgrade:()=>{if(!H)throw new Error("external_upgrade_unavailable");return H()}}),...So({getStatus:()=>ie(),getSnapshot:()=>O(l).map(e=>{const n=l.find(o=>o.type===e.type);return{...e,runtimePlatform:n?.runtimePlatform,runtimeIsWsl:n?.runtimeIsWsl}}),discover:async e=>{if(!r)throw new Error("daemon_starting");const n=await yo(!0,e);if(e){const t=l.filter(I=>I.type!==e);l.splice(0,l.length,...t,...n.detectedAgents)}else l.splice(0,l.length,...n.detectedAgents);const o=O(l);return d.sendEvent({type:"event",event:"machine.agents",payload:{agentList:l.map(t=>t.type),agents:o}}),n.agents.map(t=>{const I=n.detectedAgents.find(B=>B.type===t.type);return{...t,runtimePlatform:I?.runtimePlatform,runtimeIsWsl:I?.runtimeIsWsl}})},validate:async e=>{if(!r)throw new Error("daemon_starting");return wo(e,{ignoreNativeSession:(n,o)=>P?.ignoreNativeSource(n,o)})}})}});let k;try{k=await me({paths:vo(),handlers:{"managed-room.execute":e=>q(e),"schedule.personal":async e=>{const n=e,o=ye(n?.context);if(f.getByNianSessionId(o)?.binding)throw new Error("schedule_personal_context_required");return await oe({serverUrl:u,machineToken:p().machineToken,sessionId:o,action:n.operation,args:n})}}})}catch(e){throw await b.close(),e}let Q=!1;const M=()=>{Q||(Q=!0,Promise.allSettled([b.close(),k.close()]).finally(()=>process.exit(0)))};if(process.once("SIGINT",M),process.once("SIGTERM",M),!m.machineToken){console.error('Shennian CLI is not paired. Run "shennian pair --browser" or "shennian pair --token" first.'),await Promise.allSettled([b.close(),k.close()]),process.exitCode=1;return}process.removeListener("SIGINT",M),process.removeListener("SIGTERM",M);const Y=`${Ko(u)}/relay/machine`,_=m.machineToken;if(!_)return;try{await Ho({serverUrl:u,machineToken:_,machineId:m.machineId})}catch(e){console.error(`[personal-sync] machine authority verification failed: ${e instanceof Error?e.message:String(e)}`),await Promise.allSettled([b.close(),k.close()]),process.exit(1)}m.machineId&&mo(u,m.machineId),console.log(`[${new Date().toISOString()}] Connecting to ${Y}... (v${h}) agents: ${C.join(",")}`),T({level:"info",wsEvent:"daemon.start",metadata:{version:h,agents:C}});let P=null,A=null,N=0,v=no();const Z=Ae(m.autoUpgrade),d=new Ee({serverUrl:Y,machineToken:_,cliVersion:h,agentList:C,autoUpgradePolicy:Z,sessionSyncProtocolVersion:pe,sessionSyncCapabilities:ge,daemonInstallationId:Lo(),dataEpoch:ue,updaterProtocolVersion:1,onConnected:()=>{console.log(`[${new Date().toISOString()}] \u2713 Connected`),T({level:"info",wsEvent:"daemon.connected"}),V.some(o=>o.models.length>0)&&d.sendEvent({type:"event",event:"machine.agents",payload:{agentList:C,agents:V}}),lo(l,{serverUrl:m.serverUrl??u,authToken:m.machineToken??m.accessToken}).then(o=>{JSON.stringify(o)!==JSON.stringify(V)&&d.sendEvent({type:"event",event:"machine.agents",payload:{agentList:C,agents:o}})}).catch(()=>{}),v&&d.sendEvent({type:"event",event:"upgrade",payload:{state:"recovering",machineId:"self",from:v.from,to:v.to,...v.jobId?{jobId:v.jobId}:{},activeRunCount:r.getPendingRecoveryCount()}}),x.start(),A?.start();const e=++N,n=async()=>{try{if(await r.synchronizeSessionDeletions(),await r.recoverInterruptedRuns(),v){const o=v;v=null,o.jobId&&io(o.jobId,o.to),eo(),console.log(`[${new Date().toISOString()}] [upgrade] ${o.operation==="rollback"?"Rolled back":"Healthy"}: ${o.from} \u2192 ${o.to}`),po({kind:"upgrade",result:o.operation==="rollback"?"rolled_back":"healthy",stage:"terminal"}),d.sendEvent({type:"event",event:"upgrade",payload:{state:o.operation==="rollback"?"rolled_back":"healthy",machineId:"self",from:o.from,to:o.to,...o.jobId?{jobId:o.jobId}:{}}}),o.operation!=="rollback"&&d.sendEvent({type:"event",event:"upgrade",payload:{state:"success",machineId:"self",from:o.from,to:o.to}})}e===N&&d.getState()==="connected"&&P?.handleConnected()}catch(o){if(console.error("[session.deletions] startup catch-up failed",o),e===N&&d.getState()==="connected"){if(o instanceof Go)return;const t=o instanceof Fo?Math.max(1e3,o.retryAfterMs):5e3;setTimeout(()=>{n()},t).unref?.()}}};n()},onDisconnected:e=>{N+=1,r?.revokeProjectFsHandles(),A?.stop(),console.log(`[${new Date().toISOString()}] \u26A0 Disconnected, reconnecting... ${qo(e)}`),T({level:"warn",wsEvent:"daemon.disconnected",metadata:{code:e.code,reason:e.reason,error:e.error,phase:e.phase,trigger:e.trigger,reconnectAttempt:e.reconnectAttempt}})},onProjectFsCapabilityChanged:e=>{e||r?.revokeProjectFsHandles()},onReq:e=>{if(console.log(`[${new Date().toISOString()}] [req] ${e.method}`),T({level:"info",type:"ws",wsEvent:e.method,wsDirection:"in",traceId:e.traceId}),e.method==="room.message.deliver"){Eo(d,A,e);return}r.handleReq(e)}});P=go()?new uo(d):null,A=new Ao({registry:f,observations:U,actionClient:S,getMachineCredentials:()=>({machineToken:p().machineToken}),isSessionDeleted:e=>new To().hasSession(e),onMessages:async(e,n,o,t)=>{await y?.processMessages(e,n,o,t)},onBindingPaused:e=>{e.lifecycleState==="closed"?(R.revokeSession(e.registryKey),r?.retireManagedRoomSession(e.nianSessionId).catch(()=>{console.warn("[room.sync] retired session cleanup failed")})):r?.discardPendingManagedRoomMessages(e.nianSessionId)}});const x=new to({currentVersion:h,machineId:m.machineId??"unknown-machine",serverUrl:m.serverUrl??u,getIdleState:()=>r.getUpgradeIdleState(),sessionSyncProtocolVersion:pe,dataEpoch:ue,advertisedCapabilities:ge,updaterProtocolVersion:1,startUpgrade:async(e,n)=>{const{handleUpgradeStart:o}=await import("./commands/upgrade.js");await o(d,"auto-upgrade",e,{currentVersion:h,prepareForUpgrade:t=>r.prepareForUpgrade(t),cancelUpgradePreparation:()=>r.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()},suppressResponse:!0,waitForCompletion:!0,canHandoff:n?void 0:()=>r.getUpgradeIdleState().idle})}});x.setPolicy(Z),r=new Qe(d,P,h,x,new ko({registry:f,observations:U,bindingClient:S,getMachineCredentials:()=>{const e=p();return{machineId:e.machineId,machineToken:e.machineToken}},hostProcessIdentity:()=>`daemon:${c}`}),{prepare:()=>Oe(),isDisabled:()=>ae(),shutdownAfterResponse:()=>{setTimeout(()=>{$e(),process.kill(process.pid,"SIGTERM")},100).unref?.()}},e=>R.revokeToken(e),q,null,Ye(_),()=>O(l)),L=async e=>{const n=e&&typeof e=="object"&&!Array.isArray(e)?e:{},o=typeof n.targetVersion=="string"?n.targetVersion:void 0,t=n.confirmedMajor===!0;return await Ge(d,`local-upgrade-${Date.now()}`,o,{currentVersion:h,confirmedMajor:t,prepareForUpgrade:B=>r.prepareForUpgrade(B),cancelUpgradePreparation:()=>r.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()},suppressResponse:!0})},j=e=>{const n=e&&typeof e=="object"&&!Array.isArray(e)?e:{},t=`desktop-${(typeof n.targetVersion=="string"?n.targetVersion:"unknown").replace(/[^A-Za-z0-9._-]/g,"_")}-${Date.now()}`;return{jobId:t,...r.prepareForUpgrade(t)}},H=()=>(r.cancelUpgradePreparation(),{cancelled:!0}),y=new Mo({registry:f,observations:U,enqueueConversationBatch:e=>r.enqueueManagedRoomBatch(e),loadContext:async e=>{const n=p().machineToken?.trim(),o=e.binding;if(!n||!o)throw new Error("managed_room_context_unavailable");const t=await S.status({machineToken:n,roomId:o.roomId,bindingId:o.bindingId});if(!Uo(f,e)||!Do(e,t))throw new Error("managed_room_binding_unavailable");return{roomName:t.room.name,memberCount:t.room.memberCount,members:t.room.members,agentsByIdentityId:t.agent?{[t.agentIdentityId]:t.agent}:{},effectiveModelsBySessionId:t.effectiveModelId!==void 0?{[e.nianSessionId]:t.effectiveModelId}:void 0,agentSystemPromptsByIdentityId:t.agent?{[t.agentIdentityId]:t.agent.systemPrompt??""}:{}}},issueManagedRoomContext:(e,n,o,t)=>{const I=p().machineToken;if(!I)throw new Error("machine_not_authorized");return R.issue({registryKey:e,machineCredential:I,activationCauseMessageId:n,allowedActions:o,propagateWorkerRoomOrigin:t})},audit:new No,resolveManagedAgentPolicy:e=>_o({...e,runtime:l.find(n=>n.type===e.registration.runtimeAdapter)})}),r.scheduleHandler=e=>we({serverUrl:u,machineToken:p().machineToken,req:e,client:d,queue:r.getScheduleQueue(),registry:f}),r.getScheduleQueue().validateScheduledTarget=async e=>{if(!e.bindingId||!e.roomId)return"ready";if(f.getByNianSessionId(e.sessionId)?.binding?.bindingId!==e.bindingId)return"ended";const o=await S.status({machineToken:p().machineToken,roomId:e.roomId,bindingId:e.bindingId});return o.status==="active"&&o.room.status==="active"?"ready":"ended"},r.setManagedRoomTurnPreparer(e=>y.prepareConversationTurn(e)),r.setManagedAgentDirectTurnPreparer((e,n)=>y.prepareDirectConversationTurn(e,n)),r.setManagedRecoveryPreparer(async e=>e.source==="managed_direct"?y.prepareDirectConversationTurn(e.sessionId,jo):e.source==="manager_event"?y.prepareDirectConversationTurn(e.sessionId,{allowedRoomActions:[],managerEvent:!0}):e.source!=="managed_room"||!e.roomContextRegistryKey||!e.activationCauseMessageId||!e.rootCauseMessageId||!e.activationSequence||e.activationHopCount===null?null:y.prepareConversationTurn([{registryKey:e.roomContextRegistryKey,causeMessageId:e.activationCauseMessageId,rootCauseMessageId:e.rootCauseMessageId,sequence:e.activationSequence,hopCount:e.activationHopCount}])),F.mkdirSync(fe,{recursive:!0}),se(process.pid,c,{version:h}),ce(process.pid,void 0,c),d.connect(),process.stdin.resume();let X=!1;const ee=async()=>{if(!X){X=!0,console.log(E.gray(`
|
|
3
|
+
Disconnecting...`)),T({level:"info",wsEvent:"daemon.stop"}),clearInterval(g);try{await r.cleanup(),x.stop(),P?.stop(),A?.stop(),d.disconnect(),await Promise.allSettled([b.close(),k.close()]),te(process.pid,c),re(c)}finally{process.exit(0)}}};process.on("SIGINT",()=>{ee()}),process.on("SIGTERM",()=>{ee()})}),i.command("workbuddy-session-hook",{hidden:!0}).description("(retired) WorkBuddy Room Hook").allowUnknownOption(!0).action(()=>J()),i.command("room-session-hook",{hidden:!0}).description("(internal) Authorize the official Room tool for one trusted Agent Session").argument("<runtime>","Trusted runtime adapter").allowUnknownOption(!0).action(()=>J()),i.command("room-tool-server",{hidden:!0}).description("(internal) Run the official Shennian Room MCP tool").allowUnknownOption(!0).action(()=>J());const he=i.command("config").description("View or modify configuration");he.command("show",{isDefault:!0}).description("Show current configuration").action(()=>{console.log(`Config file: ${ke()}`);const a=p(),s=De(a,ne(process.env,a));if(a.serverUrl){const g=Xe(a.serverUrl);console.log(`Region: ${g} (${W[g].label})`)}console.log(JSON.stringify(s,null,2))}),he.command("set").description("Update a config value").argument("<key>",'Config key (e.g. "server")').argument("<value>",'Config value (e.g. "cn" or "global")').action((a,s)=>{const g=p();if(a==="server"){s!=="cn"&&s!=="global"&&(console.error(E.red('\u2717 Value must be "cn" or "global"')),process.exit(1));const c=s;g.serverUrl=Ze(c),be(g),console.log(E.green(`\u2713 Server set to ${W[c].label}`)),console.log(E.yellow(" Restart the background service for this to take effect: shennian stop && shennian start"));return}console.error(E.red(`\u2717 Unknown config key: ${a}. Supported: server`)),process.exit(1)}),Ue(i),_e(i),Le(i),je(i),He(i),Be(i),Fe(i),We(i),ze(i),Je(i),i.parse();
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import _ from"node:http";import{createHash as N,randomBytes as W,randomUUID as k}from"node:crypto";import
|
|
2
|
-
`).trim()}function G(g){return g==="worker.final"?"completed":g==="worker.error"?"failed":g==="worker.aborted"?"stopped":"progress"}function F(g,e){return`manager-inbox:${N("sha256").update(g).update("\0").update(e.map(r=>r.id).join("\0")).digest("hex").slice(0,32)}`}async function J(g){const e=[];let t=0;for await(const r of g){const n=Buffer.isBuffer(r)?r:Buffer.from(r);if(t+=n.length,t>j)throw new Error("Request body too large");e.push(n)}return e.length===0?{}:JSON.parse(Buffer.concat(e).toString("utf8"))}function K(g,e,t,r){g.client.sendRes({type:"res",id:e,ok:t,...t?{payload:r}:{error:String(r.error||"unknown error")}})}function x(){return V()?M.join(A.tmpdir(),"shennian-vitest-runtime",String(process.pid),"manager-ipc.json"):P("runtime","manager-ipc.json")}function V(){return(process.env.VITEST==="true"||!!process.env.VITEST_WORKER_ID)&&!process.env.SHENNIAN_HOME?.trim()}class le{opts;managedPolicies=new Map;registry=new T;channelRuntime;server=null;ipcUrl=null;ipcToken=W(24).toString("hex");healthTimer=null;startPromise=null;workerTextAcc=new Map;pendingWorkerOrigins=new Map;prepareWorkerTurnOrigin(e,t){t?this.pendingWorkerOrigins.set(e,t):this.pendingWorkerOrigins.delete(e)}constructor(e){this.opts=e,this.channelRuntime=e.channelRuntime??new D}async start(){return this.startPromise?this.startPromise:(this.startPromise=this.doStart(),this.startPromise)}async doStart(){if(this.server)return;this.registry.clearWorkerRunOrigins(),this.server=_.createServer((t,r)=>{this.handleIpc(t,r)}),this.server.requestTimeout=y,this.server.timeout=y,this.server.keepAliveTimeout=y,this.server.headersTimeout=y+5e3,await new Promise((t,r)=>{this.server.once("error",r),this.server.listen(0,"127.0.0.1",()=>t())});const e=this.server.address();typeof e=="object"&&e&&(this.ipcUrl=`http://127.0.0.1:${e.port}`,this.writeIpcRuntimeFile()),this.server.unref(),await this.channelRuntime.start(),this.healthTimer=setInterval(()=>this.scanWorkerHealth(),6e4),this.healthTimer.unref(),this.recoverPersistedInboxDeliveries()}async ready(){await this.start()}async stop(){this.healthTimer&&clearInterval(this.healthTimer),this.healthTimer=null,await this.channelRuntime.stop(),await new Promise(e=>{if(!this.server)return e();this.server.close(()=>e())}),this.server=null,this.ipcUrl=null,this.removeIpcRuntimeFile()}writeIpcRuntimeFile(){if(this.ipcUrl)try{const e=x();p.mkdirSync(M.dirname(e),{recursive:!0}),p.writeFileSync(e,JSON.stringify({url:this.ipcUrl,token:this.ipcToken,pid:process.pid,updatedAt:new Date().toISOString()},null,2),{mode:384}),p.chmodSync(e,384)}catch{}}removeIpcRuntimeFile(){try{const e=x(),t=JSON.parse(p.readFileSync(e,"utf-8"));(t.url===this.ipcUrl||t.token===this.ipcToken)&&p.unlinkSync(e)}catch{}}getInjectedEnv(e,t,r,n){return this.ipcUrl?{SHENNIAN_MANAGER_SESSION_ID:e,SHENNIAN_MANAGER_AGENT_SESSION_ID:t??"",SHENNIAN_MANAGER_WORKDIR:f(r),SHENNIAN_MANAGER_MODEL:n,SHENNIAN_MANAGER_IPC_URL:this.ipcUrl,SHENNIAN_MANAGER_IPC_TOKEN:this.ipcToken}:{}}registerManager(e){this.registry.upsertManager({...e,workDir:f(e.workDir),machineId:process.env.SHENNIAN_MACHINE_ID??null,managedRoomBound:e.managedRoomBound??(this.managedPolicies.has(e.sessionId)||void 0)})}setManagedAgentPolicy(e,t){if(t){this.managedPolicies.set(e,t);const r=this.registry.getManager(e);r&&!r.managedRoomBound&&this.registry.upsertManager({...r,managedRoomBound:!0})}else this.managedPolicies.delete(e)}getManagedAgentPolicyForWorker(e,t){const r=this.registry.getManager(e),n=this.managedPolicies.get(e);return!r||!n||f(t)!==r.workDir?null:n}isExternalWorkerController(e,t){const r=this.registry.getManager(e);return r?.controllerKind==="ephemeral"&&f(t)===r.workDir}isManagedWorkerSession(e){return!!this.findWorker(e)}isManagedRoomBoundManager(e){return this.registry.getManager(e)?.managedRoomBound===!0}async retireManagerAndWorkers(e){const t=this.registry.retireManager(e);this.managedPolicies.delete(e);const r=this.opts.getRuntime(),n=[],a=async(s,u)=>{const i=[];try{r.runJournal?.markSessionAborted(s)}catch(h){i.push(h)}try{r.recoveryBarrier?.end(s)}catch(h){i.push(h)}try{r.chatQueue?.purgeSession(s)}catch(h){i.push(h)}const o=r.sessions.get(s);let c=!o;if(o){if(o.managedRoomContextToken)try{r.revokeManagedRoomContextToken?.(o.managedRoomContextToken),o.managedRoomContextToken=null}catch(h){i.push(h)}try{await o.adapter.stop(),c=!0}catch(h){i.push(h)}}if(o&&c&&i.length===0&&(r.sessions.delete(s),o.heartbeatTimer&&clearInterval(o.heartbeatTimer),o.adapter.removeAllListeners()),c&&i.length===0)try{u?.()}catch(h){i.push(h),n.push(h)}else n.push(...i)};await a(e);for(const s of t)await a(s.sessionId,()=>this.registry.completeRetiredWorker(e,s.sessionId));if(n.length)throw new AggregateError(n,"Failed to retire every Manager or Worker session");return t.map(s=>s.sessionId)}setManagerWorkerDefaults(e,t,r){const n=this.registry.getManager(e);n&&this.registry.upsertManager({...n,defaultWorkerAgentType:t??null,defaultWorkerModelId:r??null})}getManagerWorkerDefaults(e){const t=this.registry.getManager(e);return{agentType:t?.defaultWorkerAgentType??null,modelId:t?.defaultWorkerModelId??null}}noteManagerAgentSession(e,t,r,n){const a=this.registry.getManager(e);this.registry.upsertManager({sessionId:e,agentSessionId:t,workDir:f(r),machineId:process.env.SHENNIAN_MACHINE_ID??null,modelId:n,agentType:a?.agentType,providerModelId:a?.providerModelId})}noteAgentEvent(e,t){const r=this.findWorker(e);if(!r)return;const n={lastActivityAt:new Date().toISOString(),runId:t.runId};if(t.agentSessionId&&(n.agentSessionId=t.agentSessionId),t.state==="start"){const i=this.pendingWorkerOrigins.get(e);this.pendingWorkerOrigins.delete(e),i&&(n.runRoomOrigins={...r.runRoomOrigins??{},[t.runId]:i})}const a=`${e}:${t.runId}`;let s=t.message||r.summary||"(\u65E0\u53EF\u89C1\u5185\u5BB9)";if(t.state==="delta"&&t.text&&!t.thinking){const i=(this.workerTextAcc.get(a)??"")+t.text;this.workerTextAcc.set(a,i);const o=R(i);o&&(n.summary=o.length>160?`${o.slice(0,160)}...`:o)}if(t.state==="final"||t.state==="error"||t.state==="aborted"){n.status=t.state;const i=R(this.workerTextAcc.get(a)??"");if(i&&(n.summary=i.length>240?`${i.slice(0,240)}...`:i,s=t.state==="final"?i:t.message||i),this.workerTextAcc.delete(a),r.runRoomOrigins?.[t.runId]){const o={...r.runRoomOrigins};delete o[t.runId],n.runRoomOrigins=o}}else t.state==="start"&&(n.status="running");const u=this.registry.updateWorker(e,n);u&&t.errorCode==="auth_retrying"&&this.registry.enqueueInboxEvent({managerSessionId:u.managedBy,workerSessionId:u.sessionId,kind:"worker.health",priority:"normal",runId:t.runId,roomOrigin:r.runRoomOrigins?.[t.runId]??null,summary:t.message||"Worker authentication is retrying.",code:"auth_retrying"}),u&&(t.state==="final"||t.state==="error"||t.state==="aborted")&&this.enqueueWorkerEvent(u.managedBy,u,t.state,t.runId,s,r.runRoomOrigins?.[t.runId]??null,t.errorCode==="auth_required"||t.errorCode==="acp_auth_required"?"auth_required":void 0)}findWorker(e){return this.registry.load().workers[e]}async handleAppReq(e){K(this.opts.getRuntime(),e.id,!1,{error:"feature_retired"})}async handleIpc(e,t){if(e.headers.authorization!==`Bearer ${this.ipcToken}`){m(t,401,{ok:!1,error:"Unauthorized"});return}try{const r=new URL(e.url??"/","http://127.0.0.1"),n=await J(e);if(r.pathname.startsWith("/channel/")||r.pathname.startsWith("/external/")||r.pathname.startsWith("/wechat-rpa/")){m(t,410,{ok:!1,error:"feature_retired"});return}const a={registry:this.registry,getRuntime:this.opts.getRuntime,getAgentSnapshot:this.opts.getAgentSnapshot,dispatchReq:this.opts.dispatchReq,dispatchChatEnqueue:this.dispatchChatEnqueue.bind(this),appendManagerVisibleMarkdown:this.appendManagerVisibleMarkdown.bind(this)},s=await H(a,r.pathname,n)??await S(a,r.pathname,n,String(e.headers["x-shennian-manager-session-id"]||""));if(s){m(t,s.status,s.body);return}m(t,404,{ok:!1,error:`Unknown manager IPC path: ${r.pathname}`})}catch(r){if(r instanceof B){m(t,r.status,{ok:!1,error:r.code,message:r.message,nextAction:r.nextAction});return}m(t,400,{ok:!1,error:r instanceof Error?r.message:String(r)})}}async executeScopedWorkerAction(e,t,r,n){const a={"manager.sessions.list":"/sessions/list","manager.sessions.start":"/sessions/start","manager.sessions.send":"/sessions/send","manager.sessions.queue":"/sessions/queue","manager.sessions.queue.edit":"/sessions/queue/edit","manager.sessions.queue.delete":"/sessions/queue/delete","manager.sessions.stop":"/sessions/stop","manager.sessions.read":"/sessions/read","manager.memory.path":"/memory/path"},s=await S({registry:this.registry,getRuntime:this.opts.getRuntime,getAgentSnapshot:this.opts.getAgentSnapshot,dispatchReq:this.opts.dispatchReq,dispatchChatEnqueue:this.dispatchChatEnqueue.bind(this),appendManagerVisibleMarkdown:this.appendManagerVisibleMarkdown.bind(this)},a[t],r,e,{roomOrigin:n});if(!s||s.status<200||s.status>=300)throw new Error(String(s?.body.error||"Managed Manager action failed"));return JSON.parse(JSON.stringify(s.body))}async dispatchChatEnqueue(e,t,r,n,a,s,u,i){if((await this.opts.dispatchReq(v({type:"req",id:`manager-enqueue-${k()}`,method:"chat.enqueue",params:{...w(),sessionId:e,text:n,agentType:t,workDir:r,agentSessionId:a,modelId:s,managedWorkerParentSessionId:u,managerWorkerRoomOrigin:i}})))?.durableQueueHandoff===!1)throw new Error("manager_worker_queue_handoff_unconfirmed")}appendManagerVisibleMarkdown(e,t,r){this.registry.getManager(e)?.controllerKind==="managed_session"&&(O(e,{id:r,sessionId:e,role:"agent",ts:Date.now(),payload:t},"committed"),q(this.opts.getRuntime().client))}enqueueWorkerEvent(e,t,r,n,a,s,u){const i=this.registry.enqueueInboxEvent({managerSessionId:e,workerSessionId:t.sessionId,kind:r==="final"?"worker.final":r==="error"?"worker.error":"worker.aborted",priority:"normal",runId:n??null,roomOrigin:s??null,summary:a||t.summary||"(\u65E0\u53EF\u89C1\u6458\u8981)",...u?{code:u}:{}}),o=this.registry.getManager(e);i&&o?.controllerKind==="managed_session"&&this.drainManagerInbox(e)}scanWorkerHealth(){const e=Date.now(),t=this.registry.load(),r=this.opts.getRuntime();for(const n of Object.values(t.workers)){if(n.status!=="running")continue;const a=e-Date.parse(n.createdAt);if(a<10*6e4)continue;const s=e-Date.parse(n.lastActivityAt),u=r.sessions.get(n.sessionId)?.currentRunId,i=!!r.chatQueue?.getSnapshot(n.sessionId).pending.length;if(!u){if(!i&&s>=10*6e4){const E="\u672A\u627E\u5230\u6D3B\u52A8\u4E2D\u7684 Provider Run \u6216\u5F85\u5904\u7406\u6D88\u606F\uFF1B\u5DF2\u5C06\u9057\u7559\u7684\u8FD0\u884C\u72B6\u6001\u6536\u655B\u4E3A\u5931\u8D25\u3002\u539F Worker \u4F1A\u8BDD\u4ECD\u4FDD\u7559\uFF0C\u53EF\u7EE7\u7EED\u53D1\u9001\u65B0\u6D88\u606F\u3002",I=this.registry.updateWorker(n.sessionId,{status:"error",lastActivityAt:new Date(e).toISOString(),summary:E});I&&this.enqueueWorkerEvent(I.managedBy,I,"error",n.runId??void 0,E)}continue}const o=n.healthNotifiedAt?Date.parse(n.healthNotifiedAt):0;if(o&&e-o<10*6e4)continue;const c=t.managers[n.managedBy];if(!c)continue;const h=Math.max(0,Math.floor(s/6e4)),d=R(n.runId?this.workerTextAcc.get(`${n.sessionId}:${n.runId}`)??"":""),l=`\u5DF2\u8FD0\u884C ${Math.floor(a/6e4)} \u5206\u949F\uFF0C\u5C1A\u672A\u7ED3\u675F\u3002
|
|
1
|
+
import _ from"node:http";import{createHash as N,randomBytes as W,randomUUID as k}from"node:crypto";import f from"node:fs";import A from"node:os";import M from"node:path";import{ManagerRegistry as T}from"./registry.js";import{appendMessage as O}from"../session/store.js";import{requestSessionIndexPublish as D}from"../session/index-publisher.js";import{RetiredChannelRuntime as q}from"./retired-external-channel-compat.js";import{resolveShennianPath as P}from"../config/index.js";import{buildExternalChannelInstructions as C}from"../agents/external-channel-instructions.js";import{getPersonalSyncIdentity as w}from"../personal-sync/epoch.js";import{EphemeralWorkerError as B,handleEphemeralWorkerIpcRoute as H,handleWorkerIpcRoute as S}from"./worker-ipc.js";import{deriveWorkerTitle as $,formatWorkerMarkdown as U}from"./worker-markdown.js";import{markTrustedManagerEvent as v}from"../session/manager-event-authority.js";const j=Number(process.env.SHENNIAN_MANAGER_IPC_BODY_MAX_BYTES||2*1024*1024),y=12*6e4;let b=null;function de(g){b=g}function he(){return b}function m(g){return M.resolve(g||A.homedir())}function p(g,e,r){g.writeHead(e,{"content-type":"application/json; charset=utf-8"}),g.end(JSON.stringify(r))}function R(g){return g.replace(/\r\n/g,`
|
|
2
|
+
`).trim()}function G(g){return g==="worker.final"?"completed":g==="worker.error"?"failed":g==="worker.aborted"?"stopped":"progress"}function F(g,e){return`manager-inbox:${N("sha256").update(g).update("\0").update(e.map(t=>t.id).join("\0")).digest("hex").slice(0,32)}`}async function J(g){const e=[];let r=0;for await(const t of g){const n=Buffer.isBuffer(t)?t:Buffer.from(t);if(r+=n.length,r>j)throw new Error("Request body too large");e.push(n)}return e.length===0?{}:JSON.parse(Buffer.concat(e).toString("utf8"))}function K(g,e,r,t){g.client.sendRes({type:"res",id:e,ok:r,...r?{payload:t}:{error:String(t.error||"unknown error")}})}function x(){return V()?M.join(A.tmpdir(),"shennian-vitest-runtime",String(process.pid),"manager-ipc.json"):P("runtime","manager-ipc.json")}function V(){return(process.env.VITEST==="true"||!!process.env.VITEST_WORKER_ID)&&!process.env.SHENNIAN_HOME?.trim()}class le{opts;managedPolicies=new Map;registry=new T;channelRuntime;server=null;ipcUrl=null;ipcToken=W(24).toString("hex");healthTimer=null;startPromise=null;workerTextAcc=new Map;pendingWorkerOrigins=new Map;prepareWorkerTurnOrigin(e,r){r?this.pendingWorkerOrigins.set(e,r):this.pendingWorkerOrigins.delete(e)}constructor(e){this.opts=e,this.channelRuntime=e.channelRuntime??new q}async start(){return this.startPromise?this.startPromise:(this.startPromise=this.doStart(),this.startPromise)}async doStart(){if(this.server)return;this.registry.clearWorkerRunOrigins(),this.server=_.createServer((r,t)=>{this.handleIpc(r,t)}),this.server.requestTimeout=y,this.server.timeout=y,this.server.keepAliveTimeout=y,this.server.headersTimeout=y+5e3,await new Promise((r,t)=>{this.server.once("error",t),this.server.listen(0,"127.0.0.1",()=>r())});const e=this.server.address();typeof e=="object"&&e&&(this.ipcUrl=`http://127.0.0.1:${e.port}`,this.writeIpcRuntimeFile()),this.server.unref(),await this.channelRuntime.start(),this.healthTimer=setInterval(()=>this.scanWorkerHealth(),6e4),this.healthTimer.unref(),this.recoverPersistedInboxDeliveries()}async ready(){await this.start()}async stop(){this.healthTimer&&clearInterval(this.healthTimer),this.healthTimer=null,await this.channelRuntime.stop(),await new Promise(e=>{if(!this.server)return e();this.server.close(()=>e())}),this.server=null,this.ipcUrl=null,this.removeIpcRuntimeFile()}writeIpcRuntimeFile(){if(this.ipcUrl)try{const e=x();f.mkdirSync(M.dirname(e),{recursive:!0}),f.writeFileSync(e,JSON.stringify({url:this.ipcUrl,token:this.ipcToken,pid:process.pid,updatedAt:new Date().toISOString()},null,2),{mode:384}),f.chmodSync(e,384)}catch{}}removeIpcRuntimeFile(){try{const e=x(),r=JSON.parse(f.readFileSync(e,"utf-8"));(r.url===this.ipcUrl||r.token===this.ipcToken)&&f.unlinkSync(e)}catch{}}getInjectedEnv(e,r,t,n){return this.ipcUrl?{SHENNIAN_MANAGER_SESSION_ID:e,SHENNIAN_MANAGER_AGENT_SESSION_ID:r??"",SHENNIAN_MANAGER_WORKDIR:m(t),SHENNIAN_MANAGER_MODEL:n,SHENNIAN_MANAGER_IPC_URL:this.ipcUrl,SHENNIAN_MANAGER_IPC_TOKEN:this.ipcToken}:{}}registerManager(e){this.registry.upsertManager({...e,workDir:m(e.workDir),machineId:process.env.SHENNIAN_MACHINE_ID??null,managedRoomBound:e.managedRoomBound??(this.managedPolicies.has(e.sessionId)||void 0)})}setManagedAgentPolicy(e,r){if(r){this.managedPolicies.set(e,r);const t=this.registry.getManager(e);t&&!t.managedRoomBound&&this.registry.upsertManager({...t,managedRoomBound:!0})}else this.managedPolicies.delete(e)}getManagedAgentPolicyForWorker(e,r){const t=this.registry.getManager(e),n=this.managedPolicies.get(e);return!t||!n||m(r)!==t.workDir?null:n}isExternalWorkerController(e,r){const t=this.registry.getManager(e);return t?.controllerKind==="ephemeral"&&m(r)===t.workDir}isManagedWorkerSession(e){return!!this.findWorker(e)}getWorkerControllerSessionId(e,r){const t=this.findWorker(e);if(!t||m(r)!==t.workDir)return null;const n=this.registry.getManager(t.managedBy);return!n||n.workDir!==t.workDir?null:n.sessionId}isManagedRoomBoundManager(e){return this.registry.getManager(e)?.managedRoomBound===!0}async retireManagerAndWorkers(e){const r=this.registry.retireManager(e);this.managedPolicies.delete(e);const t=this.opts.getRuntime(),n=[],a=async(s,u)=>{const i=[];try{t.runJournal?.markSessionAborted(s)}catch(h){i.push(h)}try{t.recoveryBarrier?.end(s)}catch(h){i.push(h)}try{t.chatQueue?.purgeSession(s)}catch(h){i.push(h)}const o=t.sessions.get(s);let c=!o;if(o){if(o.managedRoomContextToken)try{t.revokeManagedRoomContextToken?.(o.managedRoomContextToken),o.managedRoomContextToken=null}catch(h){i.push(h)}try{await o.adapter.stop(),c=!0}catch(h){i.push(h)}}if(o&&c&&i.length===0&&(t.sessions.delete(s),o.heartbeatTimer&&clearInterval(o.heartbeatTimer),o.adapter.removeAllListeners()),c&&i.length===0)try{u?.()}catch(h){i.push(h),n.push(h)}else n.push(...i)};await a(e);for(const s of r)await a(s.sessionId,()=>this.registry.completeRetiredWorker(e,s.sessionId));if(n.length)throw new AggregateError(n,"Failed to retire every Manager or Worker session");return r.map(s=>s.sessionId)}setManagerWorkerDefaults(e,r,t){const n=this.registry.getManager(e);n&&this.registry.upsertManager({...n,defaultWorkerAgentType:r??null,defaultWorkerModelId:t??null})}getManagerWorkerDefaults(e){const r=this.registry.getManager(e);return{agentType:r?.defaultWorkerAgentType??null,modelId:r?.defaultWorkerModelId??null}}noteManagerAgentSession(e,r,t,n){const a=this.registry.getManager(e);this.registry.upsertManager({sessionId:e,agentSessionId:r,workDir:m(t),machineId:process.env.SHENNIAN_MACHINE_ID??null,modelId:n,agentType:a?.agentType,providerModelId:a?.providerModelId})}noteAgentEvent(e,r){const t=this.findWorker(e);if(!t)return;const n={lastActivityAt:new Date().toISOString(),runId:r.runId};if(r.agentSessionId&&(n.agentSessionId=r.agentSessionId),r.state==="start"){const i=this.pendingWorkerOrigins.get(e);this.pendingWorkerOrigins.delete(e),i&&(n.runRoomOrigins={...t.runRoomOrigins??{},[r.runId]:i})}const a=`${e}:${r.runId}`;let s=r.message||t.summary||"(\u65E0\u53EF\u89C1\u5185\u5BB9)";if(r.state==="delta"&&r.text&&!r.thinking){const i=(this.workerTextAcc.get(a)??"")+r.text;this.workerTextAcc.set(a,i);const o=R(i);o&&(n.summary=o.length>160?`${o.slice(0,160)}...`:o)}if(r.state==="final"||r.state==="error"||r.state==="aborted"){n.status=r.state;const i=R(this.workerTextAcc.get(a)??"");if(i&&(n.summary=i.length>240?`${i.slice(0,240)}...`:i,s=r.state==="final"?i:r.message||i),this.workerTextAcc.delete(a),t.runRoomOrigins?.[r.runId]){const o={...t.runRoomOrigins};delete o[r.runId],n.runRoomOrigins=o}}else r.state==="start"&&(n.status="running");const u=this.registry.updateWorker(e,n);u&&r.errorCode==="auth_retrying"&&this.registry.enqueueInboxEvent({managerSessionId:u.managedBy,workerSessionId:u.sessionId,kind:"worker.health",priority:"normal",runId:r.runId,roomOrigin:t.runRoomOrigins?.[r.runId]??null,summary:r.message||"Worker authentication is retrying.",code:"auth_retrying"}),u&&(r.state==="final"||r.state==="error"||r.state==="aborted")&&this.enqueueWorkerEvent(u.managedBy,u,r.state,r.runId,s,t.runRoomOrigins?.[r.runId]??null,r.errorCode==="auth_required"||r.errorCode==="acp_auth_required"?"auth_required":void 0)}findWorker(e){return this.registry.load().workers[e]}async handleAppReq(e){K(this.opts.getRuntime(),e.id,!1,{error:"feature_retired"})}async handleIpc(e,r){if(e.headers.authorization!==`Bearer ${this.ipcToken}`){p(r,401,{ok:!1,error:"Unauthorized"});return}try{const t=new URL(e.url??"/","http://127.0.0.1"),n=await J(e);if(t.pathname.startsWith("/channel/")||t.pathname.startsWith("/external/")||t.pathname.startsWith("/wechat-rpa/")){p(r,410,{ok:!1,error:"feature_retired"});return}const a={registry:this.registry,getRuntime:this.opts.getRuntime,getAgentSnapshot:this.opts.getAgentSnapshot,dispatchReq:this.opts.dispatchReq,dispatchChatEnqueue:this.dispatchChatEnqueue.bind(this),appendManagerVisibleMarkdown:this.appendManagerVisibleMarkdown.bind(this)},s=await H(a,t.pathname,n)??await S(a,t.pathname,n,String(e.headers["x-shennian-manager-session-id"]||""));if(s){p(r,s.status,s.body);return}p(r,404,{ok:!1,error:`Unknown manager IPC path: ${t.pathname}`})}catch(t){if(t instanceof B){p(r,t.status,{ok:!1,error:t.code,message:t.message,nextAction:t.nextAction});return}p(r,400,{ok:!1,error:t instanceof Error?t.message:String(t)})}}async executeScopedWorkerAction(e,r,t,n){const a={"manager.sessions.list":"/sessions/list","manager.sessions.start":"/sessions/start","manager.sessions.send":"/sessions/send","manager.sessions.queue":"/sessions/queue","manager.sessions.queue.edit":"/sessions/queue/edit","manager.sessions.queue.delete":"/sessions/queue/delete","manager.sessions.stop":"/sessions/stop","manager.sessions.read":"/sessions/read","manager.memory.path":"/memory/path"},s=await S({registry:this.registry,getRuntime:this.opts.getRuntime,getAgentSnapshot:this.opts.getAgentSnapshot,dispatchReq:this.opts.dispatchReq,dispatchChatEnqueue:this.dispatchChatEnqueue.bind(this),appendManagerVisibleMarkdown:this.appendManagerVisibleMarkdown.bind(this)},a[r],t,e,{roomOrigin:n});if(!s||s.status<200||s.status>=300)throw new Error(String(s?.body.error||"Managed Manager action failed"));return JSON.parse(JSON.stringify(s.body))}async dispatchChatEnqueue(e,r,t,n,a,s,u,i){if((await this.opts.dispatchReq(v({type:"req",id:`manager-enqueue-${k()}`,method:"chat.enqueue",params:{...w(),sessionId:e,text:n,agentType:r,workDir:t,agentSessionId:a,modelId:s,managedWorkerParentSessionId:u,managerWorkerRoomOrigin:i}})))?.durableQueueHandoff===!1)throw new Error("manager_worker_queue_handoff_unconfirmed")}appendManagerVisibleMarkdown(e,r,t){this.registry.getManager(e)?.controllerKind==="managed_session"&&(O(e,{id:t,sessionId:e,role:"agent",ts:Date.now(),payload:r},"committed"),D(this.opts.getRuntime().client))}enqueueWorkerEvent(e,r,t,n,a,s,u){const i=this.registry.enqueueInboxEvent({managerSessionId:e,workerSessionId:r.sessionId,kind:t==="final"?"worker.final":t==="error"?"worker.error":"worker.aborted",priority:"normal",runId:n??null,roomOrigin:s??null,summary:a||r.summary||"(\u65E0\u53EF\u89C1\u6458\u8981)",...u?{code:u}:{}}),o=this.registry.getManager(e);i&&o?.controllerKind==="managed_session"&&this.drainManagerInbox(e)}scanWorkerHealth(){const e=Date.now(),r=this.registry.load(),t=this.opts.getRuntime();for(const n of Object.values(r.workers)){if(n.status!=="running")continue;const a=e-Date.parse(n.createdAt);if(a<10*6e4)continue;const s=e-Date.parse(n.lastActivityAt),u=t.sessions.get(n.sessionId)?.currentRunId,i=!!t.chatQueue?.getSnapshot(n.sessionId).pending.length;if(!u){if(!i&&s>=10*6e4){const E="\u672A\u627E\u5230\u6D3B\u52A8\u4E2D\u7684 Provider Run \u6216\u5F85\u5904\u7406\u6D88\u606F\uFF1B\u5DF2\u5C06\u9057\u7559\u7684\u8FD0\u884C\u72B6\u6001\u6536\u655B\u4E3A\u5931\u8D25\u3002\u539F Worker \u4F1A\u8BDD\u4ECD\u4FDD\u7559\uFF0C\u53EF\u7EE7\u7EED\u53D1\u9001\u65B0\u6D88\u606F\u3002",I=this.registry.updateWorker(n.sessionId,{status:"error",lastActivityAt:new Date(e).toISOString(),summary:E});I&&this.enqueueWorkerEvent(I.managedBy,I,"error",n.runId??void 0,E)}continue}const o=n.healthNotifiedAt?Date.parse(n.healthNotifiedAt):0;if(o&&e-o<10*6e4)continue;const c=r.managers[n.managedBy];if(!c)continue;const h=Math.max(0,Math.floor(s/6e4)),d=R(n.runId?this.workerTextAcc.get(`${n.sessionId}:${n.runId}`)??"":""),l=`\u5DF2\u8FD0\u884C ${Math.floor(a/6e4)} \u5206\u949F\uFF0C\u5C1A\u672A\u7ED3\u675F\u3002
|
|
3
3
|
|
|
4
4
|
\u5F53\u524D\u53EF\u89C1\u8FDB\u5C55\uFF1A
|
|
5
5
|
- \u6700\u8FD1\u6587\u672C\uFF1A${d||n.summary||"(\u65E0\u53EF\u89C1\u5185\u5BB9)"}
|
|
6
6
|
- \u6700\u8FD1\u6D3B\u52A8\u65F6\u95F4\uFF1A${n.lastActivityAt}
|
|
7
|
-
- \u6700\u8FD1 ${h} \u5206\u949F\u6CA1\u6709\u65B0\u6D3B\u52A8\u3002`;this.registry.updateWorker(n.sessionId,{healthNotifiedAt:new Date(e).toISOString(),lastHealthSummary:l}),this.registry.enqueueInboxEvent({managerSessionId:c.sessionId,workerSessionId:n.sessionId,kind:"worker.health",priority:"normal",runId:n.runId??null,roomOrigin:n.runRoomOrigins?.[n.runId??""]??null,summary:l})&&c.controllerKind==="managed_session"&&this.drainManagerInbox(c.sessionId)}}async interruptAndResumeManager(e,t,
|
|
7
|
+
- \u6700\u8FD1 ${h} \u5206\u949F\u6CA1\u6709\u65B0\u6D3B\u52A8\u3002`;this.registry.updateWorker(n.sessionId,{healthNotifiedAt:new Date(e).toISOString(),lastHealthSummary:l}),this.registry.enqueueInboxEvent({managerSessionId:c.sessionId,workerSessionId:n.sessionId,kind:"worker.health",priority:"normal",runId:n.runId??null,roomOrigin:n.runRoomOrigins?.[n.runId??""]??null,summary:l})&&c.controllerKind==="managed_session"&&this.drainManagerInbox(c.sessionId)}}async interruptAndResumeManager(e,r,t,n){this.opts.getRuntime().sessions.get(e.sessionId)?.currentRunId&&await this.opts.dispatchReq({type:"req",id:`manager-interrupt-${k()}`,method:"chat.abort",params:{...w(),sessionId:e.sessionId}}),await this.dispatchManagerContinuation(e,r,!1,t,n)}bindManagerAdapterEvents(e,r){r.on("agentEvent",t=>{if(t.state==="start"&&t.agentSessionId){const n=this.registry.getManager(e);n&&this.noteManagerAgentSession(e,t.agentSessionId,n.workDir,n.modelId)}t.state==="start"&&(this.acknowledgeManagerInboxDelivery(e),this.updateManagerStatus(e,"running")),(t.state==="final"||t.state==="error"||t.state==="aborted")&&(this.updateManagerStatus(e,"idle"),queueMicrotask(()=>{this.drainManagerInbox(e)}))})}updateManagerStatus(e,r){const t=this.registry.getManager(e);t&&this.registry.upsertManager({...t,status:r})}async drainManagerInbox(e){const r=this.registry.getManager(e);if(!r||r.deliveringInboxEventIds?.length)return;const t=this.registry.listInboxEvents(e);if(!t.length)return;const n=t.find(d=>d.priority==="high"),a=this.opts.getRuntime().sessions.get(e);if(r.status!=="idle"&&!n||a?.currentRunId&&!n)return;const u=(n??t[0]).roomOrigin??null,i=JSON.stringify(u),o=n?[n]:t.filter(d=>JSON.stringify(d.roomOrigin??null)===i).slice(0,20),c=o.map(d=>{const l=this.registry.getWorkerForManager(e,d.workerSessionId);return U({kind:G(d.kind),sessionId:d.workerSessionId,title:l?.title||$(l?.userGoal||l?.summary||d.summary),body:d.summary})}).join(`
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
-
`),h=F(e,o);try{n&&a?.currentRunId?(this.registry.upsertManager({...
|
|
11
|
+
`),h=F(e,o);try{n&&a?.currentRunId?(this.registry.upsertManager({...r,status:"interrupting",deliveringInboxEventIds:o.map(l=>l.id)}),await this.interruptAndResumeManager(r,c,h,u)):(this.registry.upsertManager({...r,status:"running",deliveringInboxEventIds:o.map(l=>l.id)}),await this.dispatchManagerContinuation(r,c,!0,h,u)),this.registry.removeInboxEvents(e,o.map(l=>l.id));const d=this.registry.getManager(e);d&&this.registry.upsertManager({...d,deliveringInboxEventIds:[]})}catch(d){const l=this.registry.getManager(e);l&&this.registry.upsertManager({...l,status:"idle",deliveringInboxEventIds:[]}),console.error(`[manager-inbox] delivery failed sessionId=${e}: ${d instanceof Error?d.message:String(d)}`)}}acknowledgeManagerInboxDelivery(e){const r=this.registry.getManager(e),t=r?.deliveringInboxEventIds??[];!r||t.length===0||(this.registry.removeInboxEvents(e,t),this.registry.upsertManager({...r,deliveringInboxEventIds:[]}))}recoverPersistedInboxDeliveries(){const e=this.registry.load(),r=this.opts.getRuntime();for(const t of Object.values(e.managers)){if(r.sessions.get(t.sessionId)?.currentRunId)continue;(t.status!=="idle"||t.deliveringInboxEventIds?.length)&&this.registry.upsertManager({...t,status:"idle",deliveringInboxEventIds:[]});const a=this.registry.listInboxEvents(t.sessionId).filter(s=>s.kind==="worker.health"&&!r.sessions.get(s.workerSessionId)?.currentRunId).map(s=>s.id);this.registry.removeInboxEvents(t.sessionId,a),this.registry.listInboxEvents(t.sessionId).length>0&&queueMicrotask(()=>{this.drainManagerInbox(t.sessionId)})}}async dispatchManagerContinuation(e,r,t=!1,n,a){const s=e.agentType??"manager",u=s==="manager"?e.modelId:e.providerModelId??void 0,i={...w(),sessionId:e.sessionId,text:r,agentType:s,sessionMode:"manager",workDir:e.workDir,agentSessionId:e.agentSessionId,modelId:u,managerDefaultWorkerAgentType:e.defaultWorkerAgentType??null,managerDefaultWorkerModelId:e.defaultWorkerModelId??null,...e.managedRoomBound?{managedAgentDirect:!0}:{},clientMessageId:n,managerEventId:n,...a?{managerEventRoomOrigin:a}:{}};if((await this.opts.dispatchReq(v({type:"req",id:`manager-${t?"enqueue":"send"}-${k()}`,method:"chat.enqueue",params:i})))?.durableQueueHandoff===!1)throw new Error("manager_event_queue_handoff_unconfirmed")}getExternalChannelStatus(e){return this.channelRuntime.getManagerChannelStatus(e)}getManagerExternalChannelSystemPrompt(e){return this.channelRuntime.listManagerExternalChannels(e).map(r=>C(r,void 0,e,"manager")).join(`
|
|
12
12
|
|
|
13
13
|
`).trim()}}export{le as ManagerRuntimeService,he as getManagerRuntimeService,de as setManagerRuntimeService};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{isAvailableBuiltinAgentType as k,managedAgentExecutionMode as P}from"@shennian/wire";import{getBuiltinAgentVersion as S,resolveBuiltinCommand as B}from"../agents/command-spec.js";import{registerCustomRuntimeSecurity as K,resolveCustomRuntimeExecutableFingerprint as $}from"../agents/custom-managed-runtime.js";import{loadConfig as x}from"../config/index.js";import{resolveManagedAgentExecutableIdentity as W}from"../security/managed-agent-executable.js";import{compileManagedAgentPolicy as T}from"../security/managed-agent-policy.js";import{resolveManagedAgentRuntimeProtection as R}from"../security/managed-agent-runtime.js";import{selectRoomActivationTargets as q}from"./activation-policy.js";import{buildManagedAgentPermissionPrompt as E,composeManagedRoomPrompt as D}from"./managed-prompt-composer.js";import{isCurrentRegistration as f}from"./registration-authority.js";const N=4,O=6e4,F=6,V=5*6e4;class ce{deps;latestContexts=new Map;constructor(e){this.deps=e}async processMessages(e,o,t,a={roomName:"\u5F53\u524D\u7FA4\u804A"}){const d=[],m=[],c=new Map,u=new Set,g=[...o].sort((i,s)=>J(i.sequence,s.sequence));for(const i of t){const s=this.deps.registry.getByRegistryKey(i.registryKey);!s?.binding||!f(this.deps.registry,i)||s.binding.roomId!==e||s.binding.status!=="active"&&s.binding.status!=="paused"&&s.binding.activationQueue.length}for(const i of t){const s=this.deps.registry.getByRegistryKey(i.registryKey);if(!s?.binding||!f(this.deps.registry,i)||s.binding.roomId!==e||s.binding.activationQueue.length===0&&!s.binding.activeActivation||(u.add(s.registryKey),s.binding.status!=="active"||s.binding.activationMode==="paused")||s.binding.activationQueue.length===0)continue;const l=this.deps.observations.getBySource(s);if(!l?.workspacePath||l.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");this.latestContexts.set(s.registryKey,a);for(const r of s.binding.activationQueue)d.push({registration:s,input:{params:{sessionId:s.nianSessionId,text:r.deliveryText??"\u7FA4\u804A\u4E2D\u6709\u4E00\u6761\u5F85\u5904\u7406\u6D88\u606F\uFF0C\u8BF7\u7ED3\u5408\u5F53\u524D\u7FA4\u804A\u4E0A\u4E0B\u6587\u5904\u7406\u3002",agentType:y(s.runtimeAdapter),sessionMode:a.agentsByIdentityId?.[s.binding.agentIdentityId]?.sessionMode??"standard",managerConfig:a.agentsByIdentityId?.[s.binding.agentIdentityId]?.managerConfig??null,managerDefaultWorkerAgentType:a.agentsByIdentityId?.[s.binding.agentIdentityId]?.managerConfig?.defaultWorkerAgentType??null,managerDefaultWorkerModelId:a.agentsByIdentityId?.[s.binding.agentIdentityId]?.managerConfig?.defaultWorkerModelId??null,workDir:l.workspacePath,agentSessionId:s.sourceSessionKey,queueMessageId:`room-activation-${s.binding.bindingId}-${r.causeMessageId}`,clientMessageId:`room-activation-${s.binding.bindingId}-${r.causeMessageId}`},context:{registryKey:s.registryKey,causeMessageId:r.causeMessageId,rootCauseMessageId:r.rootCauseMessageId,sequence:r.sequence,hopCount:r.hopCount}}})}for(const i of g){const s=new Set(q(i,t).map(l=>l.registration.registryKey));for(const l of t){const r=this.deps.registry.getByRegistryKey(l.registryKey);if(!r?.binding||!f(this.deps.registry,l)||r.binding.roomId!==e||r.binding.status!=="active"&&r.binding.status!=="paused"||a.agentsByIdentityId?.[r.binding.agentIdentityId]?.configurationPending||BigInt(i.sequence)<=BigInt(r.binding.lastActivatedSequence))continue;const I=c.get(r.registryKey)??[...r.binding.mentionContext??[]];if(c.set(r.registryKey,I),s.has(r.registryKey)){const p=(i.activationCause?.hopCount??0)+(i.author.principalType==="agent"?1:0),h=i.activationCause?.rootCauseMessageId??i.id;if(p>N)this.audit(r,i.id,h,p,"suppressed","max_hops");else{const w=this.deps.registry.claimActivation({registryKey:r.registryKey,causeMessageId:i.id,windowMs:O,maxActivations:F,pauseMs:V,now:this.deps.now?.()});if(!w.allowed){w.newlyPaused&&this.audit(r,i.id,h,p,"suppressed","rate_limited"),m.push({registryKey:r.registryKey,sequence:i.sequence});continue}const b=this.deps.observations.getBySource(r);if(!b?.workspacePath||b.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");const M=v([i]),C=r.binding.activationMode==="mention_only"?Q([...I.map(_=>_.deliveryText),M]):M;this.latestContexts.set(r.registryKey,a),d.push({registration:r,input:{params:{sessionId:r.nianSessionId,text:C,agentType:y(r.runtimeAdapter),sessionMode:a.agentsByIdentityId?.[r.binding.agentIdentityId]?.sessionMode??"standard",managerConfig:a.agentsByIdentityId?.[r.binding.agentIdentityId]?.managerConfig??null,managerDefaultWorkerAgentType:a.agentsByIdentityId?.[r.binding.agentIdentityId]?.managerConfig?.defaultWorkerAgentType??null,managerDefaultWorkerModelId:a.agentsByIdentityId?.[r.binding.agentIdentityId]?.managerConfig?.defaultWorkerModelId??null,workDir:b.workspacePath,agentSessionId:r.sourceSessionKey,queueMessageId:`room-activation-${r.binding.bindingId}-${i.id}`,clientMessageId:`room-activation-${r.binding.bindingId}-${i.id}`,roomMessageCreatedAt:i.createdAt},context:{registryKey:r.registryKey,causeMessageId:i.id,rootCauseMessageId:h,sequence:i.sequence,hopCount:p}}}),r.binding.activationMode==="mention_only"&&c.set(r.registryKey,[])}}else r.binding.activationMode==="mention_only"&&i.kind!=="system"&&i.author.membershipId!==r.binding.agentMembershipId&&(I.some(p=>p.sequence===i.sequence)||I.push({sequence:i.sequence,deliveryText:v([i])}));m.push({registryKey:r.registryKey,sequence:i.sequence})}}await this.deps.enqueueConversationBatch(d.map(i=>i.input));for(const i of u)this.deps.registry.retireLegacyActivationState(i,this.deps.now?.()??new Date);for(const[i,s]of c)this.deps.registry.getByRegistryKey(i)?.lifecycleState!=="closed"&&this.deps.registry.replaceMentionContext(i,s,this.deps.now?.()??new Date);for(const i of d)this.audit(i.registration,i.input.context.causeMessageId,i.input.context.rootCauseMessageId,i.input.context.hopCount,"queued","conversation_queue");for(const i of m)this.deps.registry.getByRegistryKey(i.registryKey)?.lifecycleState!=="closed"&&this.deps.registry.advanceCursor({registryKey:i.registryKey,cursor:"activated",sequence:i.sequence})}async prepareConversationTurn(e){const o=e.at(-1);if(!o)throw new Error("managed_room_queue_context_missing");const t=this.deps.registry.getByRegistryKey(o.registryKey);if(!t)throw new Error("managed_room_binding_unavailable");const a=await this.prepareManagedTurn(t,o.causeMessageId);return this.audit(t,o.causeMessageId,o.rootCauseMessageId,o.hopCount,"activated","conversation_queue_dispatch"),a}async prepareDirectConversationTurn(e,o={}){const t=o.roomOrigin?this.deps.registry.getByRegistryKey(o.roomOrigin.registryKey):this.deps.registry.getByNianSessionId(e);return t?.nianSessionId!==e||!t||o.roomOrigin&&(t.registrationEpoch!==o.roomOrigin.registrationEpoch||t.binding?.bindingId!==o.roomOrigin.bindingId||t.binding.roomId!==o.roomOrigin.roomId)?null:this.prepareManagedTurn(t,void 0,o)}async prepareManagedTurn(e,o,t){if(!f(this.deps.registry,e)||!e.binding||e.binding.status!=="active")throw new Error("managed_room_binding_unavailable");const a=this.deps.loadContext?await this.deps.loadContext(e):this.latestContexts.get(e.registryKey);if(!a)throw new Error("managed_room_prompt_context_unavailable");const d=a.agentsByIdentityId?.[e.binding.agentIdentityId];if(d?.configurationPending)throw new Error("managed_agent_configuration_pending");if(!d?.permissionPolicy)throw new Error("managed_agent_policy_missing");const m=d.sessionMode==="manager"?"manager":"standard",c=m==="manager"?{...d.managerConfig,defaultWorkerAgentType:d.managerConfig?.defaultWorkerAgentType??y(e.runtimeAdapter),defaultWorkerModelId:d.managerConfig?.defaultWorkerModelId??null}:null,u=this.deps.observations.getBySource(e);if(!u?.workspacePath||u.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");const g=this.deps.resolveManagedAgentPolicy?await this.deps.resolveManagedAgentPolicy({registration:e,permissionPolicy:d.permissionPolicy,permissionConfirmation:d.permissionConfirmation,workDir:u.workspacePath}):await U({registration:e,permissionPolicy:d.permissionPolicy,permissionConfirmation:d.permissionConfirmation,workDir:u.workspacePath});if(!f(this.deps.registry,e))throw new Error("managed_room_binding_unavailable");const i=H(a,e,g),s=this.deps.issueManagedRoomContext(e.registryKey,o,t?.allowedRoomActions,t?.propagateWorkerRoomOrigin);if(!/^mrc_[A-Za-z0-9_-]{43}$/.test(s))throw new Error("managed_room_context_unavailable");return{agentType:y(e.runtimeAdapter),workDir:g.adapterPolicy.workDir,agentSessionId:e.sourceSessionKey,systemPrompt:t?G({managerEvent:t.managerEvent===!0,roomSendAllowed:t.allowedRoomActions?.includes("send")===!0,agentSystemPrompt:a.agentSystemPromptsByIdentityId?.[e.binding.agentIdentityId],managedAgentPolicy:g,runtimeAdapter:y(e.runtimeAdapter)}):i.systemPrompt,sessionMode:m,managerConfig:c,managerDefaultWorkerAgentType:c?.defaultWorkerAgentType??null,managerDefaultWorkerModelId:c?.defaultWorkerModelId??null,roomContextRegistryKey:e.registryKey,managedRoomContextToken:s,managedAgentPolicy:g,managedRoomPromptSnapshot:i.snapshot}}audit(e,o,t,a,d,m){e.binding&&this.deps.audit?.record({roomId:e.binding.roomId,bindingId:e.binding.bindingId,causeMessageId:o,rootCauseMessageId:t,hopCount:a,outcome:d,reason:m,at:this.deps.now?.()})}}function v(n){const e=n.map(o=>{const t=o.text?.trim()||"\u53D1\u9001\u4E86\u4E00\u6761\u6D88\u606F",a=o.attachments.length>0?`
|
|
1
|
+
import{isAvailableBuiltinAgentType as k,managedAgentExecutionMode as P}from"@shennian/wire";import{getBuiltinAgentVersion as S,resolveBuiltinCommand as B}from"../agents/command-spec.js";import{registerCustomRuntimeSecurity as K,resolveCustomRuntimeExecutableFingerprint as $}from"../agents/custom-managed-runtime.js";import{loadConfig as x}from"../config/index.js";import{resolveManagedAgentExecutableIdentity as W}from"../security/managed-agent-executable.js";import{compileManagedAgentPolicy as T}from"../security/managed-agent-policy.js";import{resolveManagedAgentRuntimeProtection as R}from"../security/managed-agent-runtime.js";import{selectRoomActivationTargets as q}from"./activation-policy.js";import{buildManagedAgentPermissionPrompt as E,composeManagedRoomPrompt as D}from"./managed-prompt-composer.js";import{isCurrentRegistration as f}from"./registration-authority.js";const N=4,O=6e4,F=6,V=5*6e4;class ce{deps;latestContexts=new Map;constructor(e){this.deps=e}async processMessages(e,o,t,a={roomName:"\u5F53\u524D\u7FA4\u804A"}){const d=[],m=[],c=new Map,u=new Set,g=[...o].sort((i,s)=>J(i.sequence,s.sequence));for(const i of t){const s=this.deps.registry.getByRegistryKey(i.registryKey);!s?.binding||!f(this.deps.registry,i)||s.binding.roomId!==e||s.binding.status!=="active"&&s.binding.status!=="paused"&&s.binding.activationQueue.length}for(const i of t){const s=this.deps.registry.getByRegistryKey(i.registryKey);if(!s?.binding||!f(this.deps.registry,i)||s.binding.roomId!==e||s.binding.activationQueue.length===0&&!s.binding.activeActivation||(u.add(s.registryKey),s.binding.status!=="active"||s.binding.activationMode==="paused")||s.binding.activationQueue.length===0)continue;const l=this.deps.observations.getBySource(s);if(!l?.workspacePath||l.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");this.latestContexts.set(s.registryKey,a);for(const r of s.binding.activationQueue)d.push({registration:s,input:{params:{sessionId:s.nianSessionId,text:r.deliveryText??"\u7FA4\u804A\u4E2D\u6709\u4E00\u6761\u5F85\u5904\u7406\u6D88\u606F\uFF0C\u8BF7\u7ED3\u5408\u5F53\u524D\u7FA4\u804A\u4E0A\u4E0B\u6587\u5904\u7406\u3002",agentType:y(s.runtimeAdapter),sessionMode:a.agentsByIdentityId?.[s.binding.agentIdentityId]?.sessionMode??"standard",managerConfig:a.agentsByIdentityId?.[s.binding.agentIdentityId]?.managerConfig??null,managerDefaultWorkerAgentType:a.agentsByIdentityId?.[s.binding.agentIdentityId]?.managerConfig?.defaultWorkerAgentType??null,managerDefaultWorkerModelId:a.agentsByIdentityId?.[s.binding.agentIdentityId]?.managerConfig?.defaultWorkerModelId??null,workDir:l.workspacePath,agentSessionId:s.sourceSessionKey,queueMessageId:`room-activation-${s.binding.bindingId}-${r.causeMessageId}`,clientMessageId:`room-activation-${s.binding.bindingId}-${r.causeMessageId}`},context:{registryKey:s.registryKey,causeMessageId:r.causeMessageId,rootCauseMessageId:r.rootCauseMessageId,sequence:r.sequence,hopCount:r.hopCount}}})}for(const i of g){const s=new Set(q(i,t).map(l=>l.registration.registryKey));for(const l of t){const r=this.deps.registry.getByRegistryKey(l.registryKey);if(!r?.binding||!f(this.deps.registry,l)||r.binding.roomId!==e||r.binding.status!=="active"&&r.binding.status!=="paused"||a.agentsByIdentityId?.[r.binding.agentIdentityId]?.configurationPending||BigInt(i.sequence)<=BigInt(r.binding.lastActivatedSequence))continue;const I=c.get(r.registryKey)??[...r.binding.mentionContext??[]];if(c.set(r.registryKey,I),s.has(r.registryKey)){const p=(i.activationCause?.hopCount??0)+(i.author.principalType==="agent"?1:0),h=i.activationCause?.rootCauseMessageId??i.id;if(p>N)this.audit(r,i.id,h,p,"suppressed","max_hops");else{const w=this.deps.registry.claimActivation({registryKey:r.registryKey,causeMessageId:i.id,windowMs:O,maxActivations:F,pauseMs:V,now:this.deps.now?.()});if(!w.allowed){w.newlyPaused&&this.audit(r,i.id,h,p,"suppressed","rate_limited"),m.push({registryKey:r.registryKey,sequence:i.sequence});continue}const b=this.deps.observations.getBySource(r);if(!b?.workspacePath||b.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");const M=v([i]),C=r.binding.activationMode==="mention_only"?Q([...I.map(_=>_.deliveryText),M]):M;this.latestContexts.set(r.registryKey,a),d.push({registration:r,input:{params:{sessionId:r.nianSessionId,text:C,agentType:y(r.runtimeAdapter),sessionMode:a.agentsByIdentityId?.[r.binding.agentIdentityId]?.sessionMode??"standard",managerConfig:a.agentsByIdentityId?.[r.binding.agentIdentityId]?.managerConfig??null,managerDefaultWorkerAgentType:a.agentsByIdentityId?.[r.binding.agentIdentityId]?.managerConfig?.defaultWorkerAgentType??null,managerDefaultWorkerModelId:a.agentsByIdentityId?.[r.binding.agentIdentityId]?.managerConfig?.defaultWorkerModelId??null,workDir:b.workspacePath,agentSessionId:r.sourceSessionKey,queueMessageId:`room-activation-${r.binding.bindingId}-${i.id}`,clientMessageId:`room-activation-${r.binding.bindingId}-${i.id}`,roomMessageCreatedAt:i.createdAt},context:{registryKey:r.registryKey,causeMessageId:i.id,rootCauseMessageId:h,sequence:i.sequence,hopCount:p}}}),r.binding.activationMode==="mention_only"&&c.set(r.registryKey,[])}}else r.binding.activationMode==="mention_only"&&i.kind!=="system"&&i.author.membershipId!==r.binding.agentMembershipId&&(I.some(p=>p.sequence===i.sequence)||I.push({sequence:i.sequence,deliveryText:v([i])}));m.push({registryKey:r.registryKey,sequence:i.sequence})}}await this.deps.enqueueConversationBatch(d.map(i=>i.input));for(const i of u)this.deps.registry.retireLegacyActivationState(i,this.deps.now?.()??new Date);for(const[i,s]of c)this.deps.registry.getByRegistryKey(i)?.lifecycleState!=="closed"&&this.deps.registry.replaceMentionContext(i,s,this.deps.now?.()??new Date);for(const i of d)this.audit(i.registration,i.input.context.causeMessageId,i.input.context.rootCauseMessageId,i.input.context.hopCount,"queued","conversation_queue");for(const i of m)this.deps.registry.getByRegistryKey(i.registryKey)?.lifecycleState!=="closed"&&this.deps.registry.advanceCursor({registryKey:i.registryKey,cursor:"activated",sequence:i.sequence})}async prepareConversationTurn(e){const o=e.at(-1);if(!o)throw new Error("managed_room_queue_context_missing");const t=this.deps.registry.getByRegistryKey(o.registryKey);if(!t)throw new Error("managed_room_binding_unavailable");const a=await this.prepareManagedTurn(t,o.causeMessageId);return this.audit(t,o.causeMessageId,o.rootCauseMessageId,o.hopCount,"activated","conversation_queue_dispatch"),a}async prepareDirectConversationTurn(e,o={}){const t=o.roomOrigin?this.deps.registry.getByRegistryKey(o.roomOrigin.registryKey):this.deps.registry.getByNianSessionId(e);return t?.nianSessionId!==e||!t||o.roomOrigin&&(t.registrationEpoch!==o.roomOrigin.registrationEpoch||t.binding?.bindingId!==o.roomOrigin.bindingId||t.binding.roomId!==o.roomOrigin.roomId)?null:this.prepareManagedTurn(t,void 0,o)}async prepareManagedTurn(e,o,t){if(!f(this.deps.registry,e)||!e.binding||e.binding.status!=="active")throw new Error("managed_room_binding_unavailable");const a=this.deps.loadContext?await this.deps.loadContext(e):this.latestContexts.get(e.registryKey);if(!a)throw new Error("managed_room_prompt_context_unavailable");const d=a.agentsByIdentityId?.[e.binding.agentIdentityId];if(d?.configurationPending)throw new Error("managed_agent_configuration_pending");if(!d?.permissionPolicy)throw new Error("managed_agent_policy_missing");const m=d.sessionMode==="manager"?"manager":"standard",c=m==="manager"?{...d.managerConfig,defaultWorkerAgentType:d.managerConfig?.defaultWorkerAgentType??y(e.runtimeAdapter),defaultWorkerModelId:d.managerConfig?.defaultWorkerModelId??null}:null,u=this.deps.observations.getBySource(e);if(!u?.workspacePath||u.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");const g=this.deps.resolveManagedAgentPolicy?await this.deps.resolveManagedAgentPolicy({registration:e,permissionPolicy:d.permissionPolicy,permissionConfirmation:d.permissionConfirmation,workDir:u.workspacePath}):await U({registration:e,permissionPolicy:d.permissionPolicy,permissionConfirmation:d.permissionConfirmation,workDir:u.workspacePath});if(!f(this.deps.registry,e))throw new Error("managed_room_binding_unavailable");const i=H(a,e,g),s=this.deps.issueManagedRoomContext(e.registryKey,o,t?.allowedRoomActions,t?.propagateWorkerRoomOrigin);if(!/^mrc_[A-Za-z0-9_-]{43}$/.test(s))throw new Error("managed_room_context_unavailable");return{agentType:y(e.runtimeAdapter),effectiveModelId:a.effectiveModelsBySessionId?.[e.nianSessionId],workDir:g.adapterPolicy.workDir,agentSessionId:e.sourceSessionKey,systemPrompt:t?G({managerEvent:t.managerEvent===!0,roomSendAllowed:t.allowedRoomActions?.includes("send")===!0,agentSystemPrompt:a.agentSystemPromptsByIdentityId?.[e.binding.agentIdentityId],managedAgentPolicy:g,runtimeAdapter:y(e.runtimeAdapter)}):i.systemPrompt,sessionMode:m,managerConfig:c,managerDefaultWorkerAgentType:c?.defaultWorkerAgentType??null,managerDefaultWorkerModelId:c?.defaultWorkerModelId??null,roomContextRegistryKey:e.registryKey,managedRoomContextToken:s,managedAgentPolicy:g,managedRoomPromptSnapshot:i.snapshot}}audit(e,o,t,a,d,m){e.binding&&this.deps.audit?.record({roomId:e.binding.roomId,bindingId:e.binding.bindingId,causeMessageId:o,rootCauseMessageId:t,hopCount:a,outcome:d,reason:m,at:this.deps.now?.()})}}function v(n){const e=n.map(o=>{const t=o.text?.trim()||"\u53D1\u9001\u4E86\u4E00\u6761\u6D88\u606F",a=o.attachments.length>0?`
|
|
2
2
|
${o.attachments.map(L).join(`
|
|
3
3
|
`)}`:"";return{speaker:o.author.displayName,body:`${t}${a}`}});return A(e)}function L(n){return`\u9644\u4EF6\uFF1A${`${j(n.name)} \xB7 ${z(n.byteSize)}`}\uFF5C${n.mimeType}\uFF5CattachmentId=${n.attachmentId}`}function j(n){return String(n||"\u9644\u4EF6").replace(/[[\]\\]/g,"\\$&").replace(/[\r\n]+/g," ")}function z(n){const e=Number(n);if(!Number.isFinite(e)||e<0)return`${n} B`;if(e<1024)return`${e} B`;const o=["KB","MB","GB","TB"];let t=e,a=-1;do t/=1024,a+=1;while(t>=1024&&a<o.length-1);const d=t>=100?0:t>=10?1:2;return`${t.toFixed(d).replace(/\.0+$|(?<=\.[0-9])0+$/,"")} ${o[a]}`}function Q(n){return A(n.map(X))}function X(n){const e=n.match(/^\[群消息\][ \t]+<([^>\n]+)>\n([\s\S]*)$/);if(e)return{speaker:e[1],body:e[2]};const o=n.match(/^\[群消息\]\n<([^>\n]+)>\n([\s\S]*)$/);if(o)return{speaker:o[1],body:o[2]};const t=n.match(/^群消息\n([^\n]+)\n([\s\S]*)$/);if(t)return{speaker:t[1],body:t[2]};const a=n.match(/^([^\n]+)\n([\s\S]*)$/);return a?{speaker:a[1],body:a[2]}:{speaker:"\u7FA4\u6210\u5458",body:n.trim()}}function A(n){if(n.length===1){const[e]=n;return`[\u7FA4\u6D88\u606F] <${e.speaker}>
|
|
4
4
|
${e.body}`}return`[\u7FA4\u6D88\u606F]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import x from"node:crypto";import
|
|
1
|
+
import x from"node:crypto";import i from"node:fs";import c from"node:path";const k=100*1024*1024;class p extends Error{code;constructor(e,a){super(a),this.code=e,this.name="RoomAttachmentUploadError"}}class O{deps;constructor(e){this.deps=e}async upload(e){if(e.files.length<1||e.files.length>10)throw s("room_upload_file_count");const a=i.realpathSync(e.workspacePath),o=[];for(const[n,r]of e.files.entries()){const l=c.resolve(a,r);if(i.lstatSync(l).isSymbolicLink())throw s("room_upload_invalid_file");const m=i.realpathSync(l),d=c.relative(a,m);if(d===".."||d.startsWith(`..${c.sep}`)||c.isAbsolute(d))throw s("room_upload_outside_workspace");if(L(d))throw s("room_upload_sensitive_file");const h=z(m),_=x.createHash("sha256").update(h).digest("hex"),f=c.basename(m),v=F(f),S=$(f,v),A=x.createHash("sha256").update(`${e.bindingId}\0${e.clientMessageId}\0${n}\0${_}`,"utf8").digest("hex"),y={machineToken:e.machineToken,roomId:e.roomId,bindingId:e.bindingId},u=await this.deps.actionClient.initializeAttachment({...y,clientUploadId:A,name:f,mediaKind:S,mimeType:v,byteSize:h.byteLength,sha256:_});let g;try{g=await(this.deps.fetch??fetch)(u.upload.url,{method:"PUT",headers:u.upload.headers,body:new Uint8Array(h),redirect:"error",signal:AbortSignal.timeout(12e4)})}catch{throw new p("room_upload_unavailable","Private attachment upload service is unavailable.")}if(!g.ok)throw new p(`room_upload_http_${g.status}`,"Private attachment upload failed.");const I=await this.deps.actionClient.completeAttachment({...y,attachmentId:u.attachment.id});o.push(I.attachment.attachmentId)}return o}}function z(t){const e=i.lstatSync(t);if(!b(e))throw s("room_upload_invalid_file");let a=null;try{a=i.openSync(t,i.constants.O_RDONLY|(i.constants.O_NOFOLLOW??0));const o=i.fstatSync(a);if(!b(o)||!w(e,o))throw s("room_upload_file_changed");const n=i.readFileSync(a),r=i.fstatSync(a),l=i.lstatSync(t);if(!w(o,r)||!w(o,l)||o.size!==r.size||o.mtimeMs!==r.mtimeMs||n.byteLength!==o.size)throw s("room_upload_file_changed");return n}catch(o){throw o instanceof p?o:s("room_upload_invalid_file")}finally{a!==null&&i.closeSync(a)}}function b(t){return t.isFile()&&t.nlink===1&&t.size>0&&t.size<=k}function w(t,e){return t.dev===e.dev&&t.ino===e.ino}function L(t){const a=t.replaceAll("\\","/").split("/").filter(Boolean).map(n=>n.toLowerCase()),o=a.at(-1)??"";return o===".env"||o.startsWith(".env.")||o===".netrc"||o===".npmrc"||o.endsWith(".key")||o.endsWith(".pem")||a.includes(".ssh")||a.includes(".aws")||a.includes(".azure")?!0:a.some((n,r)=>n===".config"&&a[r+1]==="gcloud")}function F(t){const e=c.extname(t).toLowerCase();return{".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".webp":"image/webp",".heic":"image/heic",".heif":"image/heif",".mp3":"audio/mpeg",".wav":"audio/wav",".m4a":"audio/mp4",".aac":"audio/aac",".ogg":"audio/ogg",".flac":"audio/flac",".pdf":"application/pdf",".txt":"text/plain",".md":"text/markdown",".json":"application/json",".csv":"text/csv",".doc":"application/msword",".docx":"application/vnd.openxmlformats-officedocument.wordprocessingml.document",".xls":"application/vnd.ms-excel",".xlsx":"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",".ppt":"application/vnd.ms-powerpoint",".pptx":"application/vnd.openxmlformats-officedocument.presentationml.presentation"}[e]??"application/octet-stream"}function $(t,e){return/^image\/hei[cf](?:-sequence)?$/.test(e)||/\.(?:heic|heif)$/i.test(t)?"file":e.startsWith("image/")?"image":e.startsWith("audio/")?"audio":"file"}function s(t){return new p(t,"Room attachment file is not allowed.")}export{p as RoomAttachmentUploadError,O as RoomAttachmentUploader,$ as attachmentMediaKind};
|