shennian 0.5.3 → 0.5.4
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/bin/shennian-adapter-host.js +4 -0
- package/dist/publish-build-manifest.json +102 -37
- package/dist/resources/agent-adapters/claude.md +33 -0
- package/dist/resources/agent-adapters/codex.md +33 -0
- package/dist/resources/agent-adapters/cursor.md +32 -0
- package/dist/resources/agent-adapters/custom.md +40 -0
- package/dist/resources/agent-adapters/opencode.md +31 -0
- package/dist/resources/agent-adapters/pi-coding-agent.md +31 -0
- package/dist/resources/agent-adapters/workbuddy.md +32 -0
- package/dist/src/adapter-kit/check.js +4 -0
- package/dist/src/adapter-kit/host.js +2 -0
- package/dist/src/adapter-kit/official-adapters.js +1 -0
- package/dist/src/adapter-kit/registry.js +2 -0
- package/dist/src/adapter-kit/submission-client.js +1 -0
- package/dist/src/adapter-kit/types.js +1 -0
- package/dist/src/agents/custom.js +5 -4
- package/dist/src/agents/detect.js +1 -1
- package/dist/src/agents/model-registry/discovery.js +3 -3
- package/dist/src/agents/node-adapter.js +7 -0
- package/dist/src/agents/public-agent-client.js +1 -0
- package/dist/src/commands/adapter.js +49 -0
- package/dist/src/commands/agent-readiness.js +2 -2
- package/dist/src/commands/agent-workspace.js +3 -0
- package/dist/src/commands/agent.js +2 -2
- package/dist/src/commands/room.js +4 -3
- package/dist/src/commands/runtime.js +5 -5
- package/dist/src/commands/support.js +4 -0
- package/dist/src/daemon-ipc/agent-readiness.js +1 -1
- package/dist/src/index.js +3 -3
- package/dist/src/room/activation-policy.js +1 -1
- package/dist/src/room/activation-service.js +2 -1
- package/dist/src/room/invite-bootstrap.js +1 -1
- package/dist/src/room/live-delivery.js +1 -1
- package/dist/src/room/managed-prompt-composer.js +2 -2
- package/dist/src/room/public-room-client.js +1 -1
- package/dist/src/room/room-action-client.js +1 -1
- package/dist/src/session/manager.js +1 -1
- package/dist/src/session/queue-helpers.js +8 -3
- package/dist/src/support/support-client.js +1 -0
- package/node_modules/@shennian/wire/dist/adapter-submission.js +1 -0
- package/node_modules/@shennian/wire/dist/index.js +1 -1
- package/node_modules/@shennian/wire/dist/support.js +1 -0
- package/package.json +4 -4
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import{createInterface as
|
|
2
|
-
`)}}async stop(){this.mode==="stdio"?await this.stopStdioProcess():await this.killSpawnProcess()}async runSpawn(t
|
|
3
|
-
`),await
|
|
4
|
-
`)
|
|
1
|
+
import{createInterface as g}from"node:readline";import{createHash as f,randomUUID as a}from"node:crypto";import{AgentAdapter as w,registerAgent as P}from"./adapter.js";import{spawnCommandString as l}from"./command-spec.js";import{buildAgentProcessEnv as v,buildManagedAgentProcessEnv as S,buildManagedNativeFullProcessEnv as y}from"../agent-env.js";import{buildInlineChannelPreamble as u,buildPlatformInstructions as D}from"./platform-instructions.js";import{isManagedAgentNativeFull as C}from"../security/managed-agent-policy.js";import{collectPolicyEnforcementGaps as I}from"../security/managed-agent-runtime.js";import{assessCustomRuntimeSecurity as E,createCustomManagedPolicyFile as R,CUSTOM_RUNTIME_POLICY_ENV as x,disposeCustomManagedPolicyFile as b,registerCustomRuntimeSecurity as k,resolveCustomRuntimeExecutableFingerprint as p}from"./custom-managed-runtime.js";import{hasCustomResumeConformance as q,recordCustomResumeConformance as _}from"./custom-recovery-conformance.js";const m=3e3;class A extends w{type;get recoveryCapabilities(){return{inspectTerminal:!1,reattachRun:!1,resumeRun:!1,resumeSession:this.entry.caps.resume===!0&&this.resumeConformant}}command;mode;proactive;entry;sessionId=null;agentSessionId=null;workDir=null;runId="";seq=0;externalChannel=null;shennianSessionId=null;extraEnv={};managedAgentPolicy=null;managedConfigDir=null;configurationRestartRequired=!1;executableFingerprint;resumeConformant;expectedResumedSessionId=null;stoppingStdioProcess=!1;pendingProtocolRequests=new Map;stdioProcess=null;spawnProcess=null;dispatchGuard=null;dispatchObservedEvent=!1;constructor(e,t){super(),this.type=`custom:${e}`,this.entry=t,this.command=t.command,this.mode=t.caps.mode,this.proactive=t.caps.proactive??!1,this.executableFingerprint=p(t),this.resumeConformant=q(this.type,this.executableFingerprint,t.caps.version)}get usesNodeStdioV2(){return this.entry.caps.protocol==="shennian-node-stdio"&&this.entry.caps.protocolVersion===2}async start(e,t,s){if(this.sessionId=e,this.workDir=this.managedAgentPolicy?.adapterPolicy.workDir??t,this.seq=0,s&&(this.agentSessionId=s,this.expectedResumedSessionId=s),this.mode==="stdio"){if(await this.startStdioProcess(),this.usesNodeStdioV2){const i=await this.sendProtocolRequest("start",{sessionId:e,workDir:this.workDir});i?.nativeSessionId&&(this.agentSessionId=i.nativeSessionId)}s&&await this.resume(s)}}configure(e){const t=this.configurationFingerprint();this.shennianSessionId=e.sessionId??null,this.externalChannel=e.externalChannel??null,this.extraEnv=e.env??{},"managedAgentPolicy"in e&&(this.managedAgentPolicy=e.managedAgentPolicy??null),this.managedAgentPolicy&&(this.workDir=this.managedAgentPolicy.adapterPolicy.workDir),t!==this.configurationFingerprint()&&(this.stdioProcess||this.spawnProcess)&&(this.configurationRestartRequired=!0)}async send(e,t,s,i,n,r){if(r?.source==="room_activation"&&!this.managedAgentPolicy)throw new Error("managed_agent_policy_missing");if(this.configurationRestartRequired&&(this.mode==="stdio"?await this.stopStdioProcess():await this.killSpawnProcess(),this.configurationRestartRequired=!1),this.runId=a(),this.seq=0,this.mode==="stdio")if(this.usesNodeStdioV2)await this.sendStdioV2(e,D(this.workDir??process.cwd(),this.externalChannel,this.shennianSessionId??void 0,n),t,s,i);else{const o=`${u(this.workDir??process.cwd(),this.externalChannel,this.shennianSessionId??void 0,n)}${e}`;await this.sendStdio(o,t)}else{const o=`${u(this.workDir??process.cwd(),this.externalChannel,this.shennianSessionId??void 0,n)}${e}`;await this.runSpawn(o,t)}}async resume(e){if(this.mode==="stdio"){if(this.usesNodeStdioV2){const s=await this.sendProtocolRequest("resume",{sessionId:this.sessionId,nativeSessionId:e});this.agentSessionId=s?.nativeSessionId??e;return}const t=JSON.stringify({method:"resume",id:a(),params:{sessionId:this.sessionId,agentSessionId:e}});this.stdioProcess?.stdin?.write(t+`
|
|
2
|
+
`)}}async stop(){this.mode==="stdio"?await this.stopStdioProcess():await this.killSpawnProcess()}async runSpawn(e,t){await this.killSpawnProcess();const s=this.startDispatchGuard();this.dispatchObservedEvent=!1;const i=["/run","--workdir",this.workDir??process.cwd()];this.sessionId&&i.push("--session",this.sessionId),this.agentSessionId&&i.push("--resume",this.agentSessionId),t&&i.push("--model",t);const n=await this.prepareManagedLaunch();let r;try{r=l(this.command,i,{cwd:this.workDir??void 0,stdio:["pipe","pipe","pipe"],env:n.env})}catch(o){throw await this.disposeManagedConfig(n.configDir),o}this.spawnProcess=r,this.managedConfigDir=n.configDir,this.attachOutputHandlers(r,()=>{this.spawnProcess===r&&(this.spawnProcess=null),this.disposeManagedConfig(n.configDir)}),r.stdin?.once("error",o=>this.rejectDispatch(o)),r.stdin?.write(e),r.stdin?.end(),await s.promise}async killSpawnProcess(){const e=this.spawnProcess;if(!e){await this.disposeManagedConfig(this.managedConfigDir);return}this.spawnProcess=null,e.kill("SIGTERM"),await new Promise(t=>{e.on("close",t),setTimeout(()=>{e.kill("SIGKILL"),t()},3e3)}),await this.disposeManagedConfig(this.managedConfigDir)}async startStdioProcess(){const e=await this.prepareManagedLaunch();let t;try{t=l(this.command,["/start","--workdir",this.workDir??process.cwd()],{cwd:this.workDir??void 0,stdio:["pipe","pipe","pipe"],env:e.env})}catch(s){throw await this.disposeManagedConfig(e.configDir),s}this.stdioProcess=t,this.managedConfigDir=e.configDir,this.attachOutputHandlers(t,()=>{this.stdioProcess===t&&(this.stdioProcess=null,this.disposeManagedConfig(e.configDir),this.stoppingStdioProcess||this.emit("error",new Error("Custom agent process exited unexpectedly")))})}async sendStdio(e,t){this.stdioProcess||await this.startStdioProcess();const s=this.startDispatchGuard();this.dispatchObservedEvent=!1;const i=JSON.stringify({method:"send",id:a(),params:{sessionId:this.sessionId,text:e,modelId:t}});this.stdioProcess?.stdin?.once("error",n=>this.rejectDispatch(n)),this.stdioProcess?.stdin?.write(i+`
|
|
3
|
+
`),await s.promise}async sendStdioV2(e,t,s,i,n){this.stdioProcess||(await this.startStdioProcess(),await this.sendProtocolRequest("start",{sessionId:this.sessionId,workDir:this.workDir})),this.dispatchObservedEvent=!1,await this.sendProtocolRequest("send",{sessionId:this.sessionId,text:e,systemPrompt:t,modelId:s,reasoningEffort:i,attachments:n?.map(r=>({path:r.path,name:r.name,mime:r.mimeType}))??[]})}async stopStdioProcess(){const e=this.stdioProcess;if(!e){await this.disposeManagedConfig(this.managedConfigDir);return}this.stoppingStdioProcess=!0;const t=new Promise(s=>{const i=setTimeout(()=>{e.kill("SIGTERM"),setTimeout(()=>{e.kill("SIGKILL"),s()},3e3)},5e3);e.on("close",()=>{clearTimeout(i),s()})});if(this.usesNodeStdioV2)await this.sendProtocolRequest("stop",{}).catch(()=>{});else{const s=JSON.stringify({method:"stop",id:a()});e.stdin?.write(s+`
|
|
4
|
+
`)}await t,this.stdioProcess=null,await this.disposeManagedConfig(this.managedConfigDir),this.stoppingStdioProcess=!1}async prepareManagedLaunch(){const e=this.managedAgentPolicy;if(!e)return{env:v(this.extraEnv),configDir:null};if(C(e))return{env:y(this.extraEnv),configDir:null};const t=p(this.entry);if(!e.enforcement.executableFingerprint||t!==e.enforcement.executableFingerprint)throw new Error("custom_managed_runtime_executable_changed");if(e.enforcement.level!=="unverified")throw new Error("custom_managed_runtime_protection_must_be_unverified");const s=E(this.entry);if(s.state==="declared"&&e.enforcement.runtimeVersion!==this.entry.caps.version)throw new Error("custom_managed_runtime_version_changed");if(s.state==="declared"){const r=s.declaration.platforms[e.enforcement.platform],o=I(e.requestedPolicy,r??null);if(o.unavailableCapabilities.length>0)throw new Error(`custom_managed_runtime_capability_unavailable:${o.unavailableCapabilities.join(",")}`)}const i=S({agentType:this.type,providerEnv:this.extraEnv,sessionEnv:this.extraEnv,runtimeEnv:{NO_COLOR:"1"}});if(s.state==="unverified")return{env:i,configDir:null};const n=await R(e);return i[x]=n.policyPath,{env:i,configDir:n.directory}}async disposeManagedConfig(e){e&&(this.managedConfigDir===e&&(this.managedConfigDir=null),await b(e).catch(()=>{}))}configurationFingerprint(){const e=Object.keys(this.extraEnv).sort().map(t=>[t,this.extraEnv[t]??null]);return f("sha256").update(JSON.stringify({sessionId:this.shennianSessionId,externalChannel:this.externalChannel,env:e,managedAgentPolicy:this.managedAgentPolicy})).digest("hex")}attachOutputHandlers(e,t){g({input:e.stdout}).on("line",n=>{if(n.trim())try{const r=JSON.parse(n);"type"in r&&r.type==="response"?this.handleProtocolResponse(r):"type"in r&&r.type==="event"?this.handleProtocolEvent({...r.event,agentSessionId:r.event.nativeSessionId}):this.handleProtocolEvent(r)}catch{}});let i="";e.stderr?.on("data",n=>{i+=n.toString()}),e.on("close",n=>{if(t(),this.rejectPendingProtocolRequests(new Error(`Custom agent process exited with code ${n??"unknown"}`)),n!==0&&n!==null){const r=i.trim()||`Agent exited with code ${n}`;this.rejectDispatch(new Error(r)),this.emitEvent({state:"error",message:r})}else this.dispatchObservedEvent?this.acceptDispatch():this.rejectDispatch(new Error("Custom agent exited without protocol events"))}),e.on("error",n=>{t(),this.rejectDispatch(n),this.emit("error",n)})}handleProtocolResponse(e){if(!e.id)return;const t=this.pendingProtocolRequests.get(e.id);if(t){if(clearTimeout(t.timer),this.pendingProtocolRequests.delete(e.id),e.ok){t.resolve(e.result);return}t.reject(new Error(e.error?.message??e.error?.code??"Adapter request failed"))}}sendProtocolRequest(e,t){const s=this.stdioProcess?.stdin;if(!s?.writable)return Promise.reject(new Error("Custom adapter stdio is not writable"));const i=a();return new Promise((n,r)=>{const o=setTimeout(()=>{this.pendingProtocolRequests.delete(i),r(new Error(`Timed out waiting for custom adapter ${e} response`))},e==="send"?m:5e3);this.pendingProtocolRequests.set(i,{resolve:n,reject:r,timer:o}),s.write(`${JSON.stringify({protocolVersion:2,id:i,method:e,params:t})}
|
|
5
|
+
`,h=>{if(!h)return;const d=this.pendingProtocolRequests.get(i);d&&(clearTimeout(d.timer),this.pendingProtocolRequests.delete(i),d.reject(h))})})}rejectPendingProtocolRequests(e){for(const t of this.pendingProtocolRequests.values())clearTimeout(t.timer),t.reject(e);this.pendingProtocolRequests.clear()}handleProtocolEvent(e){const{state:t}=e;if(t)switch(this.dispatchObservedEvent=!0,t){case"delta":this.acceptDispatch(),this.emitEvent({state:"delta",text:e.text??"",thinking:e.thinking});break;case"final":this.acceptDispatch(),this.expectedResumedSessionId&&e.agentSessionId===this.expectedResumedSessionId&&this.executableFingerprint&&(_(this.type,this.executableFingerprint,this.entry.caps.version),this.resumeConformant=!0,this.expectedResumedSessionId=null),e.agentSessionId&&(this.agentSessionId=e.agentSessionId),this.emitEvent({state:"final",usage:e.usage,agentSessionId:this.agentSessionId??void 0});break;case"error":this.rejectDispatch(new Error(e.message??"unknown error")),this.emitEvent({state:"error",message:e.message??"unknown error"});break;case"tool-call":this.acceptDispatch(),this.emitEvent({state:"tool-call",name:e.name??"",args:e.args});break;case"tool-result":this.acceptDispatch(),this.emitEvent({state:"tool-result",name:e.name??"",result:e.result??""});break;case"notify":this.acceptDispatch(),this.emitEvent({state:"notify",text:e.text??"",title:e.title,source:e.source});break}}startDispatchGuard(){this.acceptDispatch();let e;const t=new Promise((s,i)=>{e={settled:!1,timer:setTimeout(()=>this.acceptDispatch(e),m),resolve:s,reject:i,promise:Promise.resolve()}});return e.promise=t,this.dispatchGuard=e,e}acceptDispatch(e=this.dispatchGuard){!e||e.settled||(e.settled=!0,clearTimeout(e.timer),this.dispatchGuard===e&&(this.dispatchGuard=null),e.resolve())}rejectDispatch(e,t=this.dispatchGuard){!t||t.settled||(t.settled=!0,clearTimeout(t.timer),this.dispatchGuard===t&&(this.dispatchGuard=null),t.reject(e))}emitEvent(e){const t={...e,runId:this.runId,seq:this.seq++};this.emit("agentEvent",t)}}function J(c,e){const t=`custom:${c}`;k(t,e),P(t,()=>new A(c,e))}export{A as CustomAgentAdapter,J as registerCustomAgent};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{probeBuiltinAuthStatus as
|
|
1
|
+
import{probeBuiltinAuthStatus as m}from"./auth-status.js";import{AVAILABLE_BUILTIN_AGENT_TYPES as u}from"@shennian/wire";import{loadConfig as l}from"../config/index.js";import{getBuiltinAgentVersion as p,resolveBuiltinCommand as f}from"./command-spec.js";import{registerCustomRuntimeSecurity as d,resolveCustomRuntimeExecutableFingerprint as g}from"./custom-managed-runtime.js";import{resolveManagedAgentExecutableIdentity as y}from"../security/managed-agent-executable.js";import{discoverAcpAgents as A}from"./acp/discovery.js";import{listNodeAdapters as T}from"../adapter-kit/registry.js";import{syncNodeAdapterAgents as v}from"./node-adapter.js";const h=u.filter(e=>e!=="pi");function x(e,o){const i=e?.match(/(?:^|v)(\d+)\.(\d+)\.(\d+)/);if(!i)return!1;const s=i.slice(1,4).map(Number);for(let a=0;a<o.length;a+=1)if(s[a]!==o[a])return s[a]>o[a];return!0}function S(e){return x(e,[0,85,0])}function b(e){const o=e.models?.length?e.models:e.model?[e.model]:[],i=e.defaultModel??e.model;return o.map(s=>({id:s,name:s,isDefault:s===i}))}async function M(e={}){const o=[];for(const n of h){if(e.agentType&&e.agentType!==n)continue;const t=f(n);if(!t)continue;const r=p(n,t);if((n==="codex"||n==="claude")&&!r||n==="pi-coding-agent"&&!S(r))continue;const c=y(t);o.push({type:n,command:t.command,args:t.args,version:r,runtimePlatform:c.platform,runtimeIsWsl:c.isWsl,executableFingerprint:c.executableFingerprint,authState:await m(n,t,r)})}const i=l(),s=new Set(Object.keys(i.customAgents??{}));for(const[n,t]of Object.entries(i.customAgents??{})){const r=`custom:${n}`;e.agentType&&e.agentType!==r||(d(r,t),o.push({type:r,command:t.command,args:[],version:t.caps.version,runtimePlatform:process.platform,runtimeIsWsl:process.platform==="linux"&&!!(process.env.WSL_DISTRO_NAME||process.env.WSL_INTEROP),executableFingerprint:g(t),models:b(t.caps)}))}const a=T();v(a,s);for(const[n,t]of Object.entries(a)){if(s.has(n))continue;const r=`custom:${n}`;e.agentType&&e.agentType!==r||o.push({type:r,command:process.execPath,args:[],version:t.version,runtimePlatform:process.platform,runtimeIsWsl:process.platform==="linux"&&!!(process.env.WSL_DISTRO_NAME||process.env.WSL_INTEROP),executableFingerprint:t.entrySha256,models:t.models.map(c=>({id:c,name:c,isDefault:c===t.defaultModel})),display:{name:t.name,version:t.version},authState:"ready",nodeAdapter:t})}return(!e.agentType||e.agentType.startsWith("acp:"))&&o.push(...await A(e)),o}export{M as detectAgents,S as isPiCodingAgentRuntimeCompatible};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{createInterface as _}from"node:readline";import{resolveBuiltinCommand as l,spawnResolvedCommand as w}from"../command-spec.js";import{fallbackClaudeAliasModels as E,discoverClaudeAliasModelsFromEnv as k,fallbackCodexModels as h,fallbackWorkBuddyModels as x,fallbackGeminiModels as
|
|
2
|
-
`,c=>{c&&(clearTimeout(p),
|
|
3
|
-
${s.stderr}`)?n:n.filter(i=>i.id!=="fable")}async function
|
|
1
|
+
import{createInterface as _}from"node:readline";import{resolveBuiltinCommand as l,spawnResolvedCommand as w}from"../command-spec.js";import{fallbackClaudeAliasModels as E,discoverClaudeAliasModelsFromEnv as k,fallbackCodexModels as h,fallbackWorkBuddyModels as x,fallbackGeminiModels as A,parseCodexAppServerModels as R,parseCursorModels as O,parseOpenCodeModels as I}from"./parsers.js";import{runResolvedCommand as f}from"./runner.js";import{DISCOVERY_WORKDIR as M,ModelDiscoveryFallbackError as b}from"./types.js";import{buildAgentProcessEnv as C,readLatestUserEnv as P}from"../../agent-env.js";import{CODEX_APP_SERVER_CLIENT_INFO as S}from"../codex-utils.js";import{PiRpcClient as T}from"../pi-rpc-client.js";import{discoverOpenCodeModelsViaApi as $}from"./opencode-api.js";import{discoverNodeAdapterModels as D}from"../node-adapter.js";function L(e){const r=e instanceof Error?e.message:"";return/timed out/i.test(r)?"app_server_timeout":/\berror\b|invalid|unsupported|unknown (?:method|field)/i.test(r)?"app_server_rpc_error":"app_server_unavailable"}function m(e,r,t,n,s,i){return e.stdin?new Promise((o,a)=>{const p=setTimeout(()=>{r.delete(t),a(new Error(`codex app-server request timed out: ${n}`))},i);r.set(t,{resolve:o,reject:a,timer:p}),e.stdin.write(`${JSON.stringify({id:t,method:n,params:s})}
|
|
2
|
+
`,c=>{c&&(clearTimeout(p),r.delete(t),a(c))})}):Promise.reject(new Error("codex app-server stdin unavailable"))}async function q(e){const r=w(e,["app-server","--listen","stdio://"],{cwd:M,stdio:["pipe","pipe","pipe"],env:C({NO_COLOR:"1"})}),t=new Map;let n=1;_({input:r.stdout}).on("line",i=>{if(!i.trim())return;let o;try{o=JSON.parse(i)}catch{return}if(o.id==null)return;const a=t.get(o.id);a&&(clearTimeout(a.timer),t.delete(o.id),o.error?a.reject(new Error(o.error.message||`codex app-server error ${o.error.code??""}`.trim())):a.resolve(o.result))});try{await m(r,t,n++,"initialize",{clientInfo:S,capabilities:{experimentalApi:!0}},1e4);const[i,o]=await Promise.all([m(r,t,n++,"model/list",{},15e3),m(r,t,n++,"config/read",{},15e3).catch(()=>null)]),a=typeof o=="object"&&o!==null&&"config"in o?o.config:void 0;return R({...typeof i=="object"&&i!==null?i:{},config:a})}finally{for(const[i,o]of t)clearTimeout(o.timer),o.reject(new Error("codex app-server stopped")),t.delete(i);r.kill("SIGTERM"),setTimeout(()=>r.kill("SIGKILL"),1e3).unref()}}async function j(){const e=l("cursor");if(!e)return[];const r=await f(e,["--list-models"]);return O(r.stdout)}async function N(){const e=l("opencode");if(!e)return[];const r=await $().catch(()=>[]);if(r.length>0)return r;const t=await f(e,["models"]);return I(t.stdout)}async function G(){const e=l("pi-coding-agent");if(!e)return[];const r=w(e,["--mode","rpc","--no-session","--offline","--no-extensions","--no-skills","--no-prompt-templates","--no-context-files","--no-tools"],{cwd:M,stdio:["pipe","pipe","pipe"],env:C({NO_COLOR:"1"})}),t=new T(r,{requestTimeoutMs:1e4});try{const[n,s]=await Promise.all([t.request("get_available_models"),t.request("get_state")]),i=n.data&&typeof n.data=="object"?n.data:{},o=s.data&&typeof s.data=="object"?s.data:{},a=typeof o.model?.id=="string"&&typeof o.model.provider=="string"?`${o.model.provider}/${o.model.id}`:null;return Array.isArray(i.models)?i.models.flatMap(c=>{if(!c||typeof c!="object")return[];const d=c;if(typeof d.id!="string"||typeof d.provider!="string")return[];const v=`${d.provider}/${d.id}`,y=d.reasoning===!0?["off","minimal","low","medium","high","xhigh","max"].map(g=>({id:g,name:g})):void 0;return[{id:v,name:typeof d.name=="string"&&d.name.trim()?d.name:d.id,provider:d.provider,isDefault:v===a,...y?{supportedReasoningEfforts:y,defaultReasoningEffort:"medium"}:{}}]}):[]}catch{return[]}finally{t.close(),r.kill("SIGTERM"),setTimeout(()=>r.kill("SIGKILL"),1e3).unref()}}async function F(){const e=l("claude");if(!e)return[];const r=P(),t=k(r);if(t.length>0)return t;const n=E(r),s=await f(e,["--help"],3e3).catch(()=>null);return!s||/\bfable\b/i.test(`${s.stdout}
|
|
3
|
+
${s.stderr}`)?n:n.filter(i=>i.id!=="fable")}async function B(){const e=l("codex");if(!e)return[];let r;try{r=await q(e)}catch(t){throw new b("codex",L(t),h())}if(r.length===0)throw new b("codex","empty_model_list",h());return r}async function V(){return l("gemini")?A():[]}const u=[{id:"qwen3.6-plus",name:"Qwen 3.6 Plus",provider:"dashscope",isDefault:!0}],W=[{id:"codex",name:"Codex",provider:"shennian",isDefault:!0},{id:"claude",name:"Claude Code",provider:"shennian"}];async function K(e){if(!e.serverUrl||!e.authToken)return u;try{const r=await fetch(`${e.serverUrl.replace(/\/$/,"")}/api/models`,{headers:{Authorization:`Bearer ${e.authToken}`}});if(!r.ok)return u;const t=await r.json(),n=(t.models??[]).map(s=>({id:s.id,name:s.name?.trim()||s.id,provider:s.provider,isDefault:s.id===t.default}));return n.length>0?n:u}catch{return u}}async function U(e,r){switch(e){case"claude":return F();case"codex":return B();case"workbuddy":return l("workbuddy")?x():[];case"gemini":return V();case"cursor":return j();case"openclaw":return[];case"opencode":return N();case"pi":return K(r);case"pi-coding-agent":return G();case"manager":return W}}async function oe(e,r){return e.type.startsWith("custom:")?e.nodeAdapter?D(e.nodeAdapter):e.models??[]:e.type.startsWith("acp:")?[]:U(e.type,r)}export{oe as discoverModelsForAgent};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import{spawn as I}from"node:child_process";import p from"node:crypto";import A from"node:path";import{fileURLToPath as _}from"node:url";import{buildAgentProcessEnv as y,buildManagedNativeFullProcessEnv as R}from"../agent-env.js";import{ADAPTER_PROTOCOL_VERSION as E}from"../adapter-kit/types.js";import{AgentAdapter as P,registerAgent as x,unregisterAgent as k}from"./adapter.js";const S=6e4,q="You are a helpful AI assistant. Follow the user request accurately and safely.",g=new Set;class M extends P{adapterName;registration;type;hostPath;nodeArgs;timeoutMs;proc=null;stdoutBuffer="";pending=new Map;sessionId="";workDir="";nativeSessionId=null;systemPrompt=null;activeRequestId=null;runId="";seq=0;extraEnv={};managed=!1;stopping=!1;constructor(s,t,e={}){super(),this.adapterName=s,this.registration=t,this.type=`custom:${s}`,this.hostPath=e.hostPath??_(new URL("../../bin/shennian-adapter-host.js",import.meta.url)),this.nodeArgs=e.nodeArgs??[],this.timeoutMs=e.timeoutMs??S}configure(s){this.extraEnv=s.env??{},this.managed=!!s.managedAgentPolicy}async start(s,t,e){this.sessionId=s,this.workDir=t,this.nativeSessionId=e?.trim()||null,this.systemPrompt=null}async send(s,t,e,i,n,o){if(!this.sessionId||!this.workDir)throw new Error("node_adapter_session_not_started");if(this.activeRequestId)throw new Error("node_adapter_turn_active");const u=t?.trim()||this.registration.defaultModel;if(!this.registration.models.includes(u))throw new Error("node_adapter_model_unavailable");const a=n?.trim()||q;this.proc&&this.systemPrompt!==a&&(await this.stopRuntime(),this.nativeSessionId=null),await this.ensureRuntime(a,u);const r=p.randomUUID();this.activeRequestId=r,this.runId=p.randomUUID(),this.seq=0,this.emitAgentEvent({state:"start"});const d=await this.request("send",{text:s,modelId:u,...e?.trim()?{reasoningEffort:e.trim()}:{},attachments:(i??[]).map(l=>({path:l.path,...l.name?{name:l.name}:{},...l.mimeType?{mime:l.mimeType}:{}}))},r);if(!d.ok)throw this.activeRequestId=null,new Error(`node_adapter_${d.error?.code??"send_failed"}`)}async resume(s){await this.stopRuntime(),this.nativeSessionId=s.trim()||null,this.systemPrompt=null}async stop(){await this.stopRuntime()}async ensureRuntime(s,t){if(this.proc||this.spawnRuntime(),this.systemPrompt)return;const e=this.nativeSessionId?"resume":"start",i=await this.request(e,{sessionId:this.sessionId,workDir:this.workDir,modelId:t,systemPrompt:s,...this.nativeSessionId?{nativeSessionId:this.nativeSessionId}:{}});if(!i.ok)throw await this.stopRuntime(),new Error(`node_adapter_${i.error?.code??`${e}_failed`}`);const n=typeof i.result?.nativeSessionId=="string"?i.result.nativeSessionId.trim():"";if(!n||this.nativeSessionId&&n!==this.nativeSessionId)throw await this.stopRuntime(),new Error("node_adapter_resume_failed");this.nativeSessionId=n,this.systemPrompt=s,this.emit("nativeSession",this.type,n)}spawnRuntime(){const s=this.managed?R(this.extraEnv):y(this.extraEnv),t=I(process.execPath,[...this.nodeArgs,this.hostPath,this.registration.entryPath,"--workdir",this.workDir],{cwd:this.workDir,env:s,stdio:["pipe","pipe","pipe"],windowsHide:!0});this.proc=t,this.stopping=!1,t.stdout.setEncoding("utf8"),t.stdout.on("data",e=>this.onStdout(e)),t.on("error",()=>this.onRuntimeClosed(t)),t.on("close",()=>this.onRuntimeClosed(t)),t.stderr.resume()}onStdout(s){this.stdoutBuffer+=s;let t=this.stdoutBuffer.indexOf(`
|
|
2
|
+
`);for(;t>=0;){const e=this.stdoutBuffer.slice(0,t).trim();this.stdoutBuffer=this.stdoutBuffer.slice(t+1),e&&this.onRuntimeLine(e),t=this.stdoutBuffer.indexOf(`
|
|
3
|
+
`)}}onRuntimeLine(s){let t;try{t=JSON.parse(s)}catch{return}if(t.type==="response"&&t.id){const i=this.pending.get(t.id);if(!i)return;clearTimeout(i.timer),this.pending.delete(t.id),i.resolve(t);return}if(t.type!=="event"||t.requestId!==this.activeRequestId)return;const e=t.event;if(e.state==="delta"){this.emitAgentEvent({state:"delta",text:e.text??"",thinking:e.thinking});return}if(e.state==="tool-call"){this.emitAgentEvent({state:"tool-call",name:e.name,args:e.args});return}if(e.state==="tool-result"){this.emitAgentEvent({state:"tool-result",name:e.name,result:e.result});return}if(e.nativeSessionId?.trim()){const i=e.nativeSessionId.trim();i!==this.nativeSessionId&&(this.nativeSessionId=i,this.emit("nativeSession",this.type,i))}this.activeRequestId=null,e.state==="final"?this.emitAgentEvent({state:"final",agentSessionId:this.nativeSessionId??void 0}):e.state==="error"&&e.code==="stopped"?this.emitAgentEvent({state:"aborted",agentSessionId:this.nativeSessionId??void 0}):e.state==="error"&&this.emitAgentEvent({state:"error",message:`Local Adapter turn failed (${e.code??"send_failed"}).`,agentSessionId:this.nativeSessionId??void 0})}request(s,t,e=p.randomUUID()){const i=this.proc;return i?.stdin.writable?new Promise((n,o)=>{const u=setTimeout(()=>{this.pending.delete(e),o(new Error(`node_adapter_${s}_timeout`))},this.timeoutMs);this.pending.set(e,{resolve:n,reject:o,timer:u}),i.stdin.write(`${JSON.stringify({protocolVersion:E,id:e,method:s,params:t})}
|
|
4
|
+
`)}):Promise.reject(new Error("node_adapter_runtime_unavailable"))}async stopRuntime(){const s=this.proc;s&&(this.stopping=!0,this.systemPrompt&&s.stdin.writable&&await this.request("stop",{reason:"client_stop"}).catch(()=>null),this.proc===s&&!s.killed&&s.kill("SIGTERM"),this.proc===s&&(this.proc=null),this.systemPrompt=null,this.activeRequestId=null)}onRuntimeClosed(s){if(this.proc===s){this.proc=null,this.systemPrompt=null;for(const t of this.pending.values())clearTimeout(t.timer),t.reject(new Error("node_adapter_runtime_exited"));this.pending.clear(),this.activeRequestId&&!this.stopping&&(this.activeRequestId=null,this.emitAgentEvent({state:"error",message:"Local Adapter Runtime exited before the turn completed."}))}}emitAgentEvent(s){this.emit("agentEvent",{...s,runId:this.runId,seq:this.seq++})}}function L(h,s=new Set){for(const t of g)s.has(t)||k(`custom:${t}`);g.clear();for(const[t,e]of Object.entries(h))s.has(t)||(x(`custom:${t}`,()=>new M(t,e)),g.add(t))}async function B(h,s={}){const t=s.hostPath??_(new URL("../../bin/shennian-adapter-host.js",import.meta.url)),e=h.workDir||A.dirname(h.entryPath),i=I(process.execPath,[...s.nodeArgs??[],t,h.entryPath,"--workdir",e],{cwd:e,env:y(),stdio:["pipe","pipe","pipe"],windowsHide:!0});i.stderr.resume(),i.stdout.setEncoding("utf8");const n=p.randomUUID(),o=await new Promise((a,r)=>{let d="";const l=setTimeout(()=>{r(new Error("node_adapter_models_timeout"))},s.timeoutMs??S),c=f=>{clearTimeout(l),f()};i.on("error",()=>c(()=>r(new Error("node_adapter_runtime_unavailable")))),i.on("close",()=>c(()=>r(new Error("node_adapter_runtime_exited")))),i.stdout.on("data",f=>{d+=f;let m=d.indexOf(`
|
|
5
|
+
`);for(;m>=0;){const v=d.slice(0,m).trim();if(d=d.slice(m+1),m=d.indexOf(`
|
|
6
|
+
`),!!v)try{const w=JSON.parse(v);if(w.type==="response"&&w.id===n){c(()=>a(w));return}}catch{}}}),i.stdin.write(`${JSON.stringify({protocolVersion:E,id:n,method:"listModels",params:{workDir:e}})}
|
|
7
|
+
`)}).finally(()=>{i.killed||i.kill("SIGTERM")});if(!o.ok||!Array.isArray(o.result?.models))throw new Error(`node_adapter_${o.error?.code??"models_invalid"}`);const u=typeof o.result.defaultModel=="string"?o.result.defaultModel:"";return o.result.models.map(a=>{const r=typeof a=="string"?{id:a,name:a}:a;return{id:r.id,name:r.name??r.id,isDefault:r.id===u}})}export{M as NodeAdapterAgent,B as discoverNodeAdapterModels,L as syncNodeAdapterAgents};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{loadConfig as p}from"../config/index.js";import{SERVERS as g}from"../region.js";class a extends Error{code;nextAction;constructor(e,n,o=null){super(n),this.code=e,this.nextAction=o}}class R{fetchImpl;constructor(e=fetch){this.fetchImpl=e}list(){return this.request("GET","/api/agent-cli/agents")}get(e){return this.request("GET",`/api/agent-cli/agents/${encodeURIComponent(e)}`)}create(e){return this.request("POST","/api/agent-cli/agents",e)}update(e,n){return this.request("PATCH",`/api/agent-cli/agents/${encodeURIComponent(e)}`,n)}delete(e){return this.request("DELETE",`/api/agent-cli/agents/${encodeURIComponent(e)}`)}listRooms(e){return this.request("GET",`/api/agent-cli/agents/${encodeURIComponent(e)}/rooms`)}createRoomSession(e,n){return this.request("POST",`/api/agent-cli/agents/${encodeURIComponent(e)}/room-sessions`,n)}removeFromRoom(e,n){return this.request("POST",`/api/agent-cli/agents/${encodeURIComponent(e)}/rooms/${encodeURIComponent(n)}/remove`,{})}async request(e,n,o){const c=p();if(!c.machineToken)throw new a("not_paired","Shennian CLI is not paired.",{type:"run_command",command:"shennian pair --browser"});const u=(c.serverUrl??g.cn.url).replace(/\/+$/,"");let s;try{s=await this.fetchImpl(`${u}${n}`,{method:e,headers:{authorization:`Bearer ${c.machineToken}`,...o==null?{}:{"content-type":"application/json"}},...o==null?{}:{body:JSON.stringify(o)},signal:AbortSignal.timeout(2e4)})}catch{throw new a("server_unavailable","Shennian service is unavailable.")}const i=await m(s);if(!s.ok){const r=l(i)?i:{};throw new a(typeof r.error=="string"?r.error:`http_${s.status}`,typeof r.message=="string"?r.message:"Agent request failed.",h(r.nextAction)?r.nextAction:null)}if(!l(i))throw new a("invalid_server_response","Shennian returned an invalid Agent response.");return i}}async function m(t){try{return await t.json()}catch{return null}}function h(t){return l(t)&&Object.values(t).every(e=>typeof e=="string")}function l(t){return!!t&&typeof t=="object"&&!Array.isArray(t)}export{R as PublicAgentClient,a as PublicAgentClientError};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import p from"node:fs";import m from"node:path";import{fileURLToPath as w}from"node:url";import{ADAPTER_SUBMISSION_FILENAME as y,ADAPTER_SUBMISSION_MAX_BYTES as v,scanAdapterSubmissionSource as S}from"@shennian/wire";import{AdapterCheckError as x,checkNodeAdapter as I}from"../adapter-kit/check.js";import{findOfficialAdapter as P,OFFICIAL_ADAPTERS as N,readOfficialAdapterDoc as M}from"../adapter-kit/official-adapters.js";import{AdapterSubmissionClientError as C,HttpAdapterSubmissionClient as D}from"../adapter-kit/submission-client.js";import{getNodeAdapter as R,getPassedCheck as g,listNodeAdapters as $,recordPassedCheck as E,registerNodeAdapter as j,removeNodeAdapter as T}from"../adapter-kit/registry.js";import{loadConfig as V}from"../config/index.js";const f=3;function Q(i,s={}){const d=i.command("adapter").description("Create, check and manage local Shennian Node Adapters (protocol V1)"),A=s.hostPath??O(),k=s.hostNodeArgs??[],b=s.submissionClient??new D;d.command("init <directory>").description("Create a dependency-free Node Adapter V1 template").requiredOption("--name <name>","Display name for the adapter").action((n,o)=>{const t=m.resolve(n),e=m.join(t,"adapter.mjs");if(p.existsSync(e)){a("adapter_file_exists",`Refusing to overwrite ${e}`);return}p.mkdirSync(t,{recursive:!0}),p.writeFileSync(e,q(o.name),{encoding:"utf8",mode:384,flag:"wx"}),l("adapter.init",{entry:e,protocolVersion:1,requiredMethods:["listModels","start","send","stop","resume"],maxCheckAttempts:f},{type:"implement_and_check",entry:e,workDir:t,command:"shennian adapter check <entry> --workdir <allowed-project-directory>",rules:["Inspect only non-sensitive installation, version and machine-interface facts.","Never read or print provider credentials or environment values.","Pass systemPrompt through a real system/instructions channel; never prepend it to user text.",`Stop after ${f} failed check iterations and explain the remaining stage/error to the user.`]})}),d.command("check <entry>").description("Run the full protocol V1 completeness check through the bundled Adapter Runtime").option("--workdir <directory>","Working directory",process.cwd()).option("--model <id>","Preferred model ID; falls back to the dynamic default model").option("--timeout <ms>","Timeout per step in milliseconds","60000").action(async(n,o)=>{const t=Number.parseInt(o.timeout,10);if(!Number.isSafeInteger(t)||t<1e3||t>3e5){a("invalid_timeout","Timeout must be between 1000 and 300000 milliseconds.");return}const e=m.resolve(n);if(!p.statSync(e,{throwIfNoEntry:!1})?.isFile()){a("adapter_entry_not_found",`Adapter entry does not exist: ${e}`);return}try{const r=await I({hostPath:A,entryPath:e,workDir:m.resolve(o.workdir),modelId:o.model,timeoutMs:t,nodeArgs:k});E(e,{name:r.name,version:r.version,workDir:m.resolve(o.workdir),models:r.models,defaultModel:r.defaultModel}),l("adapter.check",{stage:"passed",name:r.name,version:r.version,models:r.models,defaultModel:r.defaultModel,modelId:r.modelId,systemPromptVerified:r.systemPromptVerified,resumeVerified:r.resumeVerified,maxCheckAttempts:f},{type:"register_adapter",entry:e,command:"shennian adapter add <stable-name> --entry <entry>",after:"shennian agent discover --refresh"})}catch(r){if(r instanceof x){F(r.stage,r.code);return}a("adapter_check_failed","Adapter V1 check could not complete. Raw Runtime output was withheld because it may contain private machine data.",_("start","adapter_check_failed"))}}),d.command("add <name>").description("Register a local Node Adapter that already passed `adapter check` with an unchanged file").requiredOption("--entry <file>","Path to adapter.mjs").action((n,o)=>{if(!/^[a-zA-Z0-9_-]+$/.test(n)){a("invalid_adapter_name","Adapter name may contain only letters, numbers, hyphens, and underscores.");return}if(V().customAgents?.[n]){a("adapter_name_conflict",`A legacy custom runtime already uses the name "${n}". Choose a different stable name.`,{type:"choose_adapter_name",rule:"letters, numbers, hyphens and underscores only"});return}const t=m.resolve(o.entry);if(!p.statSync(t,{throwIfNoEntry:!1})?.isFile()){a("adapter_entry_not_found",`Adapter entry does not exist: ${t}`);return}const e=g(t);if(!e){a("adapter_not_checked","Run `shennian adapter check` on this exact file first; `add` only accepts an adapter whose passing check still matches the current file contents.");return}const r=j(n,t,e);l("adapter.add",{adapterRef:`custom:${n}`,name:r.name,version:r.version,models:r.models,defaultModel:r.defaultModel},{type:"refresh_discovery",command:"shennian agent discover --refresh",expectedAgentRef:`custom:${n}`})}),d.command("list").description("List official Adapters and locally registered Node Adapters").action(()=>{const n=N.map(t=>({kind:"official",id:t.id,name:t.name})),o=Object.entries($()).map(([t,e])=>({kind:"local",id:`custom:${t}`,name:e.name,version:e.version,models:e.models,defaultModel:e.defaultModel,registeredAt:e.registeredAt}));l("adapter.list",{adapters:[...n,...o]})}),d.command("show <adapter-id>").description("Show an official Adapter's Markdown detail, or a local adapter's registration summary").action(n=>{const o=n.startsWith("custom:")?n.slice(7):n,t=R(o);if(t){l("adapter.show",{kind:"local",id:`custom:${o}`,name:t.name,version:t.version,models:t.models,defaultModel:t.defaultModel,entryPath:t.entryPath,registeredAt:t.registeredAt,checkedAt:t.checkedAt});return}const e=P(n);if(!e){a("adapter_not_found",`No official or local adapter named "${n}" exists.`);return}const r=M(n);if(!r){a("adapter_doc_missing",`Detail document for "${n}" is not available in this build.`);return}l("adapter.show",{kind:"official",id:e.id,name:e.name,doc:r})}),d.command("remove <name>").description("Unregister a local Node adapter without deleting its source").action(n=>{if(!T(n)){a("adapter_not_found",`No local Node adapter named "${n}" exists.`);return}l("adapter.remove",{adapterRef:`custom:${n}`,sourceDeleted:!1})}),d.command("submit <entry>").description("Submit one checked adapter.mjs to Shennian for private official review").requiredOption("--agent <name>","General upstream Agent that this implementation adapts").option("--general-purpose","Confirm this is a reusable public Agent adapter, not private or project-specific").option("--confirm-share","Confirm the user explicitly approved sharing this source with Shennian").action(async(n,o)=>{if(!o.generalPurpose){a("adapter_submission_not_general","Private, organization-internal or project-specific adapters must not be submitted.",{type:"stop",reason:"Keep the Adapter local."});return}if(!o.confirmShare){a("adapter_submission_consent_required","Ask the user for explicit approval before sharing adapter.mjs with Shennian.",{type:"ask_user",question:"Submit this reusable adapter.mjs to Shennian for official review?"});return}const t=m.resolve(n);if(m.basename(t)!==y||!p.statSync(t,{throwIfNoEntry:!1})?.isFile()){a("adapter_submission_file_invalid","Submission accepts exactly one text file named adapter.mjs.");return}const e=g(t);if(!e){a("adapter_not_checked","Run `shennian adapter check` on this exact file first; submission requires the unchanged passing fingerprint.");return}let r;try{const c=p.readFileSync(t);if(r=c.toString("utf8"),!Buffer.from(r,"utf8").equals(c)){a("adapter_submission_file_invalid","adapter.mjs must be valid UTF-8 text.");return}}catch{a("adapter_submission_file_invalid","adapter.mjs could not be read.");return}const u=S(r);if(u.byteSize===0||u.byteSize>v){a("adapter_submission_size_invalid",`adapter.mjs must contain 1-${v} UTF-8 bytes.`);return}if(!u.ok){a("adapter_submission_sensitive_content","Remove credentials, environment values, absolute paths and private business content before submission.",{type:"generate_clean_single_file",issues:u.issues});return}const h=o.agent.trim();if(!h||h.length>191){a("adapter_submission_agent_invalid","Agent name must contain 1-191 characters.");return}try{const c=await b.submit({filename:y,content:r,sha256:e.entrySha256,targetAgent:h,adapterName:e.name,adapterVersion:e.version,checkedAt:e.checkedAt,models:e.models,defaultModel:e.defaultModel,clientVersion:s.clientVersion??"unknown",platform:process.platform});l("adapter.submit",{submissionId:c.submissionId,status:c.status,sha256:e.entrySha256,duplicate:c.duplicate===!0,officialAdapterRegistered:!1})}catch(c){if(c instanceof C){a(c.code,c.message);return}a("adapter_submission_failed","Adapter submission failed without exposing local source or credentials.")}})}function O(){return w(new URL("../../bin/shennian-adapter-host.js",import.meta.url))}function q(i){return`/**
|
|
2
|
+
* Shennian Adapter \u534F\u8BAE V1.
|
|
3
|
+
* \u72EC\u7ACB\u5B89\u88C5\u7684 Shennian Client \u81EA\u5E26 Adapter Runtime\uFF0C\u5728\u81EA\u5DF1\u7684\u5B50\u8FDB\u7A0B\u91CC\u52A0\u8F7D
|
|
4
|
+
* \u8FD9\u4E2A\u6587\u4EF6\uFF1B\u4E0D\u9700\u8981 npm install\uFF0C\u4E5F\u4E0D\u9700\u8981\u7406\u89E3 JSON Lines\u3002
|
|
5
|
+
*
|
|
6
|
+
* \u4E94\u4E2A\u51FD\u6570\u5168\u90E8\u5FC5\u9700\uFF1AlistModels / start / send / stop / resume\u3002
|
|
7
|
+
*/
|
|
8
|
+
export default {
|
|
9
|
+
name: ${JSON.stringify(i.trim()||"My Adapter")},
|
|
10
|
+
version: '0.1.0',
|
|
11
|
+
|
|
12
|
+
async listModels({ workDir }) {
|
|
13
|
+
// \u4ECE\u5F53\u524D\u4E0A\u6E38 SDK / CLI / app-server \u52A8\u6001\u67E5\u8BE2\u6A21\u578B\uFF1B\u6BCF\u6B21\u771F\u5B9E\u8C03\u7528\uFF0C
|
|
14
|
+
// \u4E0D\u8981\u628A\u67D0\u6B21\u63A2\u6D4B\u7ED3\u679C\u5199\u6B7B\u8FDB\u6E90\u7801\u3002
|
|
15
|
+
void workDir
|
|
16
|
+
return { models: [], defaultModel: '' }
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
async start({ sessionId, workDir, modelId, systemPrompt }) {
|
|
20
|
+
// \u5728\u8FD9\u91CC\u521B\u5EFA\u4E0A\u6E38 SDK \u5BA2\u6237\u7AEF\u3001app-server \u8FDE\u63A5\u6216 CLI \u4F1A\u8BDD\u3002
|
|
21
|
+
// systemPrompt \u5FC5\u987B\u901A\u8FC7\u4E0A\u6E38\u771F\u5B9E\u7684 system / instructions \u53C2\u6570\u6CE8\u5165\uFF0C
|
|
22
|
+
// \u7981\u6B62\u62FC\u63A5\u8FDB\u7B2C\u4E00\u6761\u7528\u6237\u6D88\u606F\u3002
|
|
23
|
+
void sessionId; void workDir; void modelId; void systemPrompt
|
|
24
|
+
throw new Error('Implement start() before checking this adapter.')
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
async send({ session, text, modelId, reasoningEffort, attachments, signal, output }) {
|
|
28
|
+
// text \u53EA\u662F\u5F53\u524D\u7528\u6237\u6D88\u606F\uFF0C\u4E0D\u5305\u542B\u7CFB\u7EDF\u63D0\u793A\u8BCD\u3002\u7528 output.delta(...) \u6D41\u5F0F
|
|
29
|
+
// \u8F93\u51FA\u6B63\u6587\uFF0C\u6700\u540E\u5FC5\u987B\u8C03\u7528 output.final() \u6216 output.error(...)\u3002
|
|
30
|
+
void session; void text; void modelId; void reasoningEffort
|
|
31
|
+
void attachments; void signal
|
|
32
|
+
output.error('Implement send() before checking this adapter.', 'send_failed')
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
async stop({ session, reason }) {
|
|
36
|
+
// \u4E2D\u65AD\u5F53\u524D\u4E0A\u6E38\u8BF7\u6C42\u5E76\u91CA\u653E\u672C\u5730\u8D44\u6E90\u3002
|
|
37
|
+
void session; void reason
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
async resume({ sessionId, nativeSessionId, workDir, modelId, systemPrompt }) {
|
|
41
|
+
// \u5FC5\u987B\u6062\u590D\u8FD9\u4E2A\u7CBE\u786E\u7684 nativeSessionId\uFF0C\u7981\u6B62\u6062\u590D\u201C\u6700\u8FD1\u4E00\u6B21\u201D\u3002
|
|
42
|
+
void sessionId; void workDir; void modelId; void systemPrompt
|
|
43
|
+
throw new Error(\`Implement resume() to restore native session \${nativeSessionId}.\`)
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
`}function l(i,s,d){process.stdout.write(`${JSON.stringify({ok:!0,command:i,result:s,...d?{nextAction:d}:{}})}
|
|
47
|
+
`)}function a(i,s,d=null){process.stdout.write(`${JSON.stringify({ok:!1,error:i,message:s,nextAction:d})}
|
|
48
|
+
`),process.exitCode=1}function F(i,s){process.stdout.write(`${JSON.stringify({ok:!1,command:"adapter.check",stage:i,error:s,message:L(i,s),nextAction:_(i,s)})}
|
|
49
|
+
`),process.exitCode=1}function L(i,s){return s==="adapter_contract_invalid"?"The Adapter V1 module contract is incomplete or could not be loaded.":s==="models_invalid"?"Dynamic model discovery did not return a valid non-empty model list and default model.":s==="system_prompt_unsupported"?"The upstream did not prove that systemPrompt uses a real system/instructions channel.":s==="stop_failed"?"The Adapter did not stop the checked session cleanly.":s==="resume_failed"?"The Adapter did not resume the exact checked native session.":`Adapter V1 check failed at the ${i} stage (${s}).`}function _(i,s){return{type:"edit_and_retry",stage:i,error:s,guidance:{contract:"Keep one default-export object with non-empty name/version and all five required functions. Do not restore protocolVersion 2.",models:"Query the installed upstream machine interface dynamically; return unique non-empty model ids and a defaultModel from that list.",start:"Check only the upstream executable, version, login state and documented machine interface, then implement start with workDir, modelId and systemPrompt.",systemPrompt:"Map systemPrompt to the upstream system/instructions parameter. Never concatenate it with the user message.",send:"Send only the user text and attachments, forward output events, and produce exactly one terminal result.",stop:"Abort the active upstream turn and release the process or connection before returning.",resume:"Restore the exact nativeSessionId and the same systemPrompt; never select a recent or latest session."}[i]??"Inspect the Adapter V1 implementation and retry the same check once.",command:"Retry the same shennian adapter check command after editing adapter.mjs.",maxAttempts:f,safety:"Do not read, print or copy credentials, environment values, private project content or full provider logs."}}export{Q as registerAdapterCommand};
|
|
@@ -1,2 +1,2 @@
|
|
|
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=
|
|
2
|
-
`),
|
|
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=new Set(["claude","codex","cursor","workbuddy","opencode","pi-coding-agent"]);function u(t,o){const n=d[t]??"Return to the AI preparation guide on this machine. Check the selected directory, account, model and network; do not reinstall by default.",e=o&&typeof o=="object"&&!Array.isArray(o)&&typeof o.agentType=="string"?o.agentType:null;return!e||!c.has(e)||!["agent_validation_start_failed","agent_validation_response_failed","agent_validation_response_mismatch"].includes(t)?n:`${n} If the provider's own machine interface works but the official Adapter still fails, read shennian adapter show ${e}, create a local V1 file with shennian adapter init, and follow adapter check's structured stage/error/nextAction. Stop after three failed edit/check iterations.`}const h=9e4,_="A successful check only proves one standard-mode hello reply; it does not prove the AI can send messages in a Room.",p={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 g(t,o,n=s){try{const e=await n({method:t,params:o,timeoutMs:h});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:p}}:t==="agent.check"&&e&&typeof e=="object"&&!Array.isArray(e)?{ok:!0,command:t,result:{...e,note:_}}:{ok:!0,command:t,result:e}}catch(e){const i=e instanceof r?e.code:"agent_check_failed",a=l[i]??(t==="agent.check"&&i==="ipc_timeout"?"reply":void 0);return{ok:!1,command:t,error:i,...a?{stage:a}:{},nextAction:u(i,o)}}}function y(t){const o=async(n,e)=>{const i=await g(n,e);process.stdout.write(`${JSON.stringify(i)}
|
|
2
|
+
`),i.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(n=>o("agent.discover",{...n.agent?{agentType:n.agent}:{},compact:n.compact===!0,refresh:n.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((n,e)=>o("agent.check",{agentType:n,workDir:e.workdir,modelId:e.model??null}))}export{h as AGENT_READINESS_IPC_TIMEOUT_MS,g as executeAgentReadinessCommand,y as registerAgentReadinessCommands};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import O from"node:crypto";import E from"node:path";import{callDaemonIpc as M}from"../daemon-ipc/client.js";import{DaemonIpcError as k}from"../daemon-ipc/protocol.js";import{PublicAgentClient as q,PublicAgentClientError as b}from"../agents/public-agent-client.js";const C={write:e=>process.stdout.write(e),fail:()=>{process.exitCode=1}};function W(e,n={}){const i=n.client??new q,t=n.io??C,a=n.callDaemon??M;e.command("list").description("List Shennian Agents owned by the paired user on this computer").action(()=>c("agent.list",t,()=>i.list())),e.command("get <agent-id>").description("Read one exact Shennian Agent").action(o=>c("agent.get",t,()=>i.get(l(o,"agent_id_invalid")))),e.command("create").description("Check one local AI configuration, then create a standard full-access Shennian Agent").requiredOption("--name <name>","Agent display name").requiredOption("--description <description>","Public Room introduction").requiredOption("--agent <type>","Exact type returned by agent discover").requiredOption("--workdir <path>","Absolute working directory on this computer").option("--model <id>","Exact model id returned by agent discover").option("--system-prompt <prompt>","Private system prompt","").action(o=>c("agent.create",t,async()=>{const s=L(o);return await R(s,a),i.create(s)})),e.command("update <agent-id>").description("Update one exact Shennian Agent; runtime changes are checked before saving").option("--name <name>","Agent display name").option("--description <description>","Public Room introduction").option("--agent <type>","Exact type returned by agent discover").option("--workdir <path>","Absolute working directory on this computer").option("--model <id>","Exact model id returned by agent discover").option("--clear-model","Use the runtime default model").option("--system-prompt <prompt>","Private system prompt").action((o,s)=>c("agent.update",t,async()=>{const _=l(o,"agent_id_invalid"),m=F(s);Object.keys(m).length||d("agent_changes_required","Provide at least one Agent field to update.");const p=N(await i.get(_)),u={...p,...m};return I(p)!==I(u)&&await R(u,a),i.update(_,m)})),e.command("delete <agent-id>").description("Delete one exact Shennian Agent and its dedicated Room conversations").action(o=>c("agent.delete",t,()=>i.delete(l(o,"agent_id_invalid"))));const g=e.command("room").description("List, add, or remove this Agent in explicit Rooms");g.command("list <agent-id>").description("List Rooms currently linked to one exact Agent").action(o=>c("agent.room.list",t,()=>i.listRooms(l(o,"agent_id_invalid")))),g.command("add <agent-id> <room-url>").description("Create a dedicated conversation and add this Agent to one explicit Room").option("--mode <mode>","auto_follow, mention_only, or paused","auto_follow").action((o,s,_)=>c("agent.room.add",t,async()=>{const m=l(o,"agent_id_invalid"),p=x(s),u=$(_.mode),A=`cli-agent-room:${O.randomUUID()}`,f=await i.createRoomSession(m,{roomId:p,assignmentIntentId:A,activationMode:u});if(f.existing===!0)return f;const h=v(f.session,"invalid_server_response"),P=y(h.id,"invalid_server_response"),U=y(h.agentType,"invalid_server_response"),D=await a({method:"room.agent.bind-managed",params:{roomId:p,agentIdentityId:m,nianSessionId:P,assignmentIntentId:A,agentType:U,activationMode:u},timeoutMs:12e4});return{session:h,binding:D}})),g.command("remove <agent-id> <room-url>").description("Remove this Agent from one explicit Room and delete its dedicated conversation").action((o,s)=>c("agent.room.remove",t,()=>i.removeFromRoom(l(o,"agent_id_invalid"),x(s))))}function L(e){return{displayName:r(e.name,191,"agent_name_invalid",!0),description:r(e.description,1e3,"agent_description_invalid",!0),runtimeAdapter:r(e.agent,64,"agent_runtime_invalid",!0),workDir:S(e.workdir),modelId:e.model?r(e.model,191,"agent_model_invalid",!0):null,systemPrompt:r(e.systemPrompt,2e4,"agent_system_prompt_invalid")}}function F(e){return e.clearModel&&e.model!==void 0&&d("agent_model_ambiguous","Use either --model or --clear-model, not both."),{...e.name===void 0?{}:{displayName:r(e.name,191,"agent_name_invalid",!0)},...e.description===void 0?{}:{description:r(e.description,1e3,"agent_description_invalid",!0)},...e.agent===void 0?{}:{runtimeAdapter:r(e.agent,64,"agent_runtime_invalid",!0)},...e.workdir===void 0?{}:{workDir:S(e.workdir)},...e.model===void 0&&!e.clearModel?{}:{modelId:e.clearModel?null:r(e.model,191,"agent_model_invalid",!0)},...e.systemPrompt===void 0?{}:{systemPrompt:r(e.systemPrompt,2e4,"agent_system_prompt_invalid")}}}async function R(e,n){const i=y(e.runtimeAdapter,"agent_runtime_invalid"),t=y(e.workDir,"agent_workdir_invalid"),a=typeof e.modelId=="string"?e.modelId:null;if(v(await n({method:"agent.check",params:{agentType:i,workDir:t,modelId:a},timeoutMs:9e4}),"agent_check_invalid_response").ready!==!0)throw new k("agent_check_invalid_response","The daemon did not confirm a successful hello reply.")}function I(e){return JSON.stringify([e.runtimeAdapter??null,e.workDir??null,e.modelId??null,typeof e.systemPrompt=="string"?e.systemPrompt.trim():""])}function N(e){return v(e.agent,"invalid_server_response")}async function c(e,n,i){try{const t=await i();n.write(`${JSON.stringify({ok:!0,command:e,result:t})}
|
|
2
|
+
`)}catch(t){if(t instanceof b){w(n,t.code,t.message,t.nextAction);return}if(t instanceof k){w(n,t.code,t.message,null);return}w(n,"agent_command_failed","Shennian Agent command failed.",null)}}function l(e,n){return/^[A-Za-z0-9_.:@/-]{1,191}$/.test(e)||d(n,"Use the exact Agent id returned by Shennian."),e}function x(e){let n;try{n=new URL(e)}catch{d("invalid_room_ref","Use an official Room URL returned by Shennian.")}const i=n.pathname.match(/^\/spaces\/([A-Za-z0-9_-]{1,191})$/);return(n.protocol!=="https:"||n.hostname!=="app.shennian.net"||n.port||n.username||n.password||n.hash||n.search||!i)&&d("invalid_room_ref","Use an official Room URL returned by Shennian."),i[1]}function $(e){if(e==="auto_follow"||e==="mention_only"||e==="paused")return e;d("invalid_activation_mode","--mode must be auto_follow, mention_only, or paused.")}function S(e){const n=r(e,1024,"agent_workdir_invalid",!0);return E.isAbsolute(n)||d("agent_workdir_invalid","Use an absolute working directory."),n}function r(e,n,i,t=!1){const a=typeof e=="string"?e.trim():"";return(t&&!a||a.length>n||a.includes("\0"))&&d(i,"Agent field is invalid."),a}function y(e,n){return(typeof e!="string"||!e)&&d(n,"Shennian returned an invalid Agent response."),e}function v(e,n){return(!e||typeof e!="object"||Array.isArray(e))&&d(n,"Shennian returned an invalid Agent response."),e}function d(e,n){throw new b(e,n)}function w(e,n,i,t){e.write(`${JSON.stringify({ok:!1,error:n,message:i,nextAction:t})}
|
|
3
|
+
`),e.fail()}export{W as registerAgentWorkspaceCommands};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{registerAgentReadinessCommands as
|
|
2
|
-
`),
|
|
1
|
+
import{registerAgentReadinessCommands as h}from"./agent-readiness.js";import{registerAgentWorkspaceCommands as A}from"./agent-workspace.js";import{createAgent as f}from"../agents/adapter.js";import{AcpAgentAdapter as w}from"../agents/acp/adapter.js";import{discoverAcpAgents as C}from"../agents/acp/discovery.js";import{callDaemonIpc as v}from"../daemon-ipc/client.js";import{readAcpManifests as y,removeAcpManifest as $,validateAcpId as s,writeAcpManifest as P}from"../agents/acp/manifests.js";function O(n,a={}){const e=n.command("agent").description("Discover local AI tools and manage Shennian Agents");h(e),A(e,a);const t=e.command("integration").description("Manage locally registered ACP integrations");R(t),l(e.command("add <id>",{hidden:!0})),p(e.command("remove <id>",{hidden:!0})),u(e.command("auth <id>",{hidden:!0}))}function R(n){l(n.command("add <id>")),n.command("list").description("List locally registered ACP Agents").action(()=>{o(!0,"agent.integration.list",{agents:y().map(a=>({agentRef:`acp:${a.id}`,name:a.name,command:a.launch.command,args:a.launch.args??[]}))})}),p(n.command("remove <id>")),u(n.command("auth <id>"))}function l(n){n.description("Register an installed ACP v1 Agent (does not install it)").requiredOption("--name <name>","Display name").requiredOption("--command <command>","Absolute executable path or command name").option("--arg <args...>","Arguments passed to the ACP Agent").action((a,e)=>{try{const t=s(a);P({schemaVersion:1,id:t,name:e.name,protocol:"acp",command:e.command,args:e.arg??[],enabled:!0}),o(!0,"agent.add",{agentRef:`acp:${t}`,installedByShennian:!1})}catch(t){o(!1,"agent.add",void 0,t instanceof Error?t.message:String(t))}})}function p(n){n.description("Remove a local ACP Agent registration (does not uninstall it)").action(a=>{try{const e=s(a),t=$(e);o(t,"agent.remove",t?{agentRef:`acp:${e}`}:void 0,t?void 0:`No local ACP Agent named "${e}" exists.`)}catch(e){o(!1,"agent.remove",void 0,e instanceof Error?e.message:String(e))}})}function u(n){n.description("Authenticate an installed ACP Agent on this computer").option("--method <method>","Authentication method id").action(async(a,e)=>{let t=null;try{const r=s(a.replace(/^acp:/,""));if(!(await C()).some(i=>i.type===`acp:${r}`))throw new Error(`Installed ACP Agent "${r}" was not found.`);const c=f(`acp:${r}`);if(!(c instanceof w))throw new Error("ACP Agent adapter is unavailable");t=c,await t.start(`acp-auth-${process.pid}`,process.cwd());const d=t.getAuthMethods(),m=e.method?d.find(i=>i.id===e.method):d.length===1?d[0]:void 0;if(!m)throw new Error(d.length?`Choose an authentication method with --method: ${d.map(i=>i.id).join(", ")}`:"This ACP Agent did not advertise an authentication method.");await t.authenticate(m.id,{interactiveTerminal:!0});let g=!1;try{g=(await v({method:"agent.auth.completed",params:{agentType:`acp:${r}`}})).resumed}catch{}o(!0,"agent.auth",{agentRef:`acp:${r}`,authenticated:!0,resumed:g})}catch(r){o(!1,"agent.auth",void 0,r instanceof Error?r.message:String(r))}finally{await t?.stop().catch(()=>{})}})}function o(n,a,e,t){process.stdout.write(`${JSON.stringify(n?{ok:n,command:a,result:e}:{ok:n,command:a,error:"acp_agent_manifest_error",message:t,nextAction:null})}
|
|
2
|
+
`),n||(process.exitCode=1)}export{O as registerAgentCommand};
|
|
@@ -1,3 +1,4 @@
|
|
|
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
|
-
`)}
|
|
3
|
-
`),e.
|
|
1
|
+
import x from"node:crypto";import q from"node:fs";import I from"node:path";import{PublicAttachmentCache as T}from"../room/public-attachment-cache.js";import{PublicRoomClient as $,PublicRoomClientError as O}from"../room/public-room-client.js";import{callDaemonIpc as E}from"../daemon-ipc/client.js";import{DaemonIpcError as N}from"../daemon-ipc/protocol.js";import{MANAGED_ROOM_IPC_ROOT_ENV as C,resolveManagedRoomIpcPaths as V}from"../daemon-ipc/paths.js";import{MANAGED_ROOM_CONTEXT_ENV as F}from"../room/managed-context.js";import{createRoomInviteQr as G}from"../room/invite-qr.js";import{externalRoomNextAction as j,managedRoomNextAction as H,SHENNIAN_INSTALL_URL as M}from"../room/room-error-guidance.js";import{attachmentMediaKind as J}from"../room/attachment-upload.js";const Q={write:o=>{process.stdout.write(o)},fail:()=>{process.exitCode=1}};function pe(o,n={}){const t=n.client??new $,c=n.cache??new T({client:t}),a=n.io??Q,v=n.createInviteQr??G,p=async(i,e)=>{const s=await v(i).catch(()=>({imagePath:null,mimeType:"image/png",payload:i}));return{inviteUrl:i,installUrl:M,text:Z(i,e),inviteQr:s}},m=o.command("room").description("Use one explicitly referenced Shennian Room");m.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 i=>l("room.schedule",a,async()=>{const e=b();return e||r("managed_room_context_required","Scheduling is only available in managed Room context.",j()),g(e,"schedule",{...i,version:i.version?Number(i.version):void 0})})),m.command("create").description("Create a Room as the paired user").requiredOption("--name <name>","Room name").action(async i=>l("room.create",a,async()=>{u("create");const e=i.name.trim();(!e||e.length>80)&&r("invalid_room_name","Room name must contain 1 to 80 characters.");const s=await t.create(e,x.randomUUID()),d=f(s.room,"invalid_room_response");return typeof d.inviteUrl!="string"&&r("invalid_room_response","Shennian returned an invalid Room invitation."),{...s,sharing:await p(d.inviteUrl,typeof d.name=="string"?d.name:e)}})),m.command("list").description("List Rooms for the paired user; duplicate names remain separate candidates").action(()=>l("room.list",a,()=>(u("list"),t.list()))),m.command("get <room-ref>").description("Get one explicitly referenced Room and its current version").action(i=>l("room.get",a,()=>(u("get"),t.get(h(i))))),m.command("update <room-ref>").description("Update one Room using the version returned by room get or room list").requiredOption("--version <version>","Exact Room version returned by Shennian").option("--name <name>","New Room name").option("--visibility <visibility>","private or public_read").option("--join-policy <policy>","open, approval, or closed").action((i,e)=>l("room.update",a,()=>{u("update");const s=e.name?.trim();return e.name!==void 0&&(!s||s.length>80)&&r("invalid_room_name","Room name must contain 1 to 80 characters."),e.visibility!==void 0&&e.visibility!=="private"&&e.visibility!=="public_read"&&r("invalid_room_visibility","--visibility must be private or public_read."),e.joinPolicy!==void 0&&e.joinPolicy!=="open"&&e.joinPolicy!=="approval"&&e.joinPolicy!=="closed"&&r("invalid_room_join_policy","--join-policy must be open, approval, or closed."),s===void 0&&e.visibility===void 0&&e.joinPolicy===void 0&&r("room_update_required","Provide --name, --visibility, or --join-policy."),t.update(h(i),{version:z(e.version),...s===void 0?{}:{name:s},...e.visibility===void 0?{}:{visibility:e.visibility},...e.joinPolicy===void 0?{}:{joinPolicy:e.joinPolicy}})})),m.command("delete <room-ref>").description("Delete this user's Room chat record without physically deleting the Room").requiredOption("--version <version>","Exact Room version returned by Shennian").action((i,e)=>l("room.delete",a,()=>(u("delete"),t.deleteRecord(h(i),z(e.version))))),m.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(i,e)=>l("room.join",a,async()=>{u("join");const s=P(i,"invite"),d=e.message?.trim();return d&&d.length>500&&r("join_message_too_long","Join request message must be at most 500 characters."),t.join(s.roomId,s.inviteToken,d)})),m.command("open <room-url>").description("Open one Room where the paired user is already a member").action(async i=>l("room.open",a,async()=>{u("open");const e=P(i,"stable");return t.open(e.roomId)})),m.command("status [room-ref]").description("Show the managed Room, or one explicitly referenced Room").action(async i=>l("room.status",a,()=>{const e=b();return e?(i&&r("managed_room_ref_forbidden","Do not pass a Room reference in managed Room context."),g(e,"status",{})):t.status(A(i))})),m.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(i,e)=>{await l("room.read",a,async()=>{e.before&&e.after&&r("ambiguous_sequence_cursor","--before and --after are mutually exclusive."),e.before&&!L(e.before)&&r("invalid_sequence","--before sequence is invalid."),e.after&&!L(e.after)&&r("invalid_sequence","--after sequence is invalid.");const s=Number(e.limit);(!Number.isInteger(s)||s<1||s>50)&&r("invalid_limit","--limit must be between 1 and 50.");const d=b();if(d)return i&&r("managed_room_ref_forbidden","Do not pass a Room reference in managed Room context."),g(d,"read",{...e.before?{before:e.before}:{},...e.after?{after:e.after}:{},limit:s});const R=A(i),w=await t.read(R,{before:e.before,after:e.after,limit:s});return W(w,R,c)})}),m.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",K,[]).action(async(i,e)=>{await l("room.send",a,async()=>{const s=e.text?.trim()??"";!s&&e.file.length===0&&r("room_message_empty","Provide --text or at least one --file."),e.file.length>10&&r("room_attachments_too_many","A message can contain at most 10 files.");const d=b();if(d)return i&&r("managed_room_ref_forbidden","Do not pass a Room reference in managed Room context."),g(d,"send",{clientMessageId:`managed:${x.randomUUID()}`,text:s,files:e.file});const R=A(i),w=[];for(const k of e.file)w.push(await B(t,R,k));return t.send(R,{clientMessageId:`cli:${x.randomUUID()}`,...s?{text:s}:{},...w.length>0?{attachmentIds:w}:{}})})}),m.command("pause").description("Pause the current managed Agent in its bound Room").requiredOption("--reason <reason>","Short reason shown to all Room members").action(async i=>l("room.pause",a,()=>{const e=b();return e||r("managed_room_context_required","Pause is only available in managed Room context.",j()),g(e,"pause",{reason:i.reason})})),m.command("attachment").description("Download Room attachments through Shennian").command("get [room-ref] [attachment-id]").description("Download and verify one Room attachment").action(async(i,e)=>{await l("room.attachment.get",a,async()=>{const s=b(),d=s?i:e;return(!d||!/^ratt_[A-Za-z0-9_-]{1,191}$/.test(d))&&r("invalid_attachment_ref","attachment-id is invalid."),s?g(s,"attachment.get",{attachmentId:d}):c.materialize(A(i),d)})}),m.command("leave <room-ref>").description("Leave one explicitly referenced Room").action(async i=>l("room.leave",a,()=>(u("leave"),t.leave(h(i)))));const y=m.command("invite").description("Read or reset the current Room invitation");y.command("get <room-ref>").description("Get the current invitation and version for one Room").action(i=>l("room.invite.get",a,async()=>{u("invite get");const e=f(await t.getInvite(h(i)),"invalid_room_response"),s=f(e.invite,"invalid_room_response");return typeof s.url!="string"&&r("invalid_room_response","Shennian returned an invalid Room invitation."),{...e,sharing:await p(s.url)}})),y.command("reset <room-ref>").description("Reset an invitation using the version returned by room invite get").requiredOption("--version <version>","Positive invitation version").action((i,e)=>l("room.invite.reset",a,async()=>{u("invite reset");const s=f(await t.resetInvite(h(i),D(e.version,"room_invite_version")),"invalid_room_response"),d=f(s.invite,"invalid_room_response");return typeof d.url!="string"&&r("invalid_room_response","Shennian returned an invalid Room invitation."),{...s,sharing:await p(d.url)}}));const _=m.command("member").description("List or remove Room members");_.command("list <room-ref>").description("List public member references and versions for one Room").action(i=>l("room.member.list",a,()=>(u("member list"),t.listMembers(h(i))))),_.command("remove <room-ref>").description("Remove one exact member reference using its listed version").requiredOption("--member-ref <member-ref>","Public member reference returned by room member list").requiredOption("--version <version>","Positive member version returned by room member list").action((i,e)=>l("room.member.remove",a,()=>(u("member remove"),/^member_[A-Za-z0-9_-]{32,64}$/.test(e.memberRef)||r("invalid_room_member_ref","Use a memberRef returned by room member list."),t.removeMember(h(i),e.memberRef,D(e.version,"room_member_version"))))),m.command("search",{hidden:!0}).allowUnknownOption(!0).action(()=>S(a,"command_removed","Room search is not available to Agents.",null)),m.command("agent",{hidden:!0}).allowUnknownOption(!0).action(()=>S(a,"command_removed","Managed Agents are created in the Shennian mobile App.",null))}function Z(o,n){return[n?"\u9080\u8BF7\u4F60\u52A0\u5165\u795E\u5FF5\u7FA4\u804A\u300C"+n+"\u300D\u3002":"\u70B9\u51FB\u52A0\u5165\u795E\u5FF5\u7FA4\u804A\uFF0C\u6216\u628A\u672C\u6D88\u606F\u53D1\u7ED9\u4F60\u7684 AI\uFF1A",...n?["\u76F4\u63A5\u70B9\u51FB\uFF0C\u6216\u628A\u672C\u6D88\u606F\u53D1\u7ED9\u4F60\u7684 AI\uFF0C\u8BA9\u5B83\u5B89\u88C5\u795E\u5FF5\u3001\u5B8C\u6210\u914D\u5BF9\u5E76\u52A0\u5165\u7FA4\u804A\u3002"]:[],"\u9080\u8BF7\uFF1A"+o,"\u5B89\u88C5\uFF1A"+M].join(`
|
|
2
|
+
`)}async function l(o,n,t){try{const c=await t();n.write(`${JSON.stringify({ok:!0,command:o,result:c})}
|
|
3
|
+
`)}catch(c){if(c instanceof O){S(n,c.code,c.message,c.nextAction);return}if(c instanceof N){S(n,c.code,c.message,H(c.code));return}S(n,"room_command_failed","Shennian Room command failed.",null)}}function b(){return process.env[F]?.trim()||null}async function g(o,n,t){const c=process.env[C]?.trim();if(!c||!I.isAbsolute(c))throw new N("managed_room_context_unavailable","Managed Room IPC context is unavailable.");return E({method:"managed-room.execute",params:{context:o,action:n,...t},paths:V(c),timeoutMs:n==="send"||n==="attachment.get"?18e4:15e3})}function u(o){b()&&r("managed_room_action_forbidden",`${o} is not available in managed Room context.`)}function A(o){return o||r("room_ref_required","Use an official Room URL returned by Shennian.",j()),h(o)}function h(o){return P(o,"stable").roomId}function P(o,n){let t;const c=n==="invite"?"invalid_invite_url":"invalid_room_ref",a=n==="invite"?"Use an official Shennian Room invitation URL.":"Use an official Room URL returned by Shennian.";try{t=new URL(o)}catch{r(c,a)}const v=t.pathname.match(/^\/spaces\/([A-Za-z0-9_-]{1,191})$/),p=t.searchParams.get("invite");return(t.protocol!=="https:"||t.hostname!=="app.shennian.net"||t.port||t.username||t.password||t.hash||!v||t.searchParams.has("access")||[...t.searchParams.keys()].some(m=>m!=="invite")||n==="stable"&&p!=null||n==="invite"&&(p==null||!/^[A-Za-z0-9_-]{32,256}$/.test(p)))&&r(c,a),{roomId:v[1],...p?{inviteToken:p}:{}}}function L(o){return/^(0|[1-9]\d{0,19})$/.test(o)}function z(o){const n=new Date(o);return(Number.isNaN(n.getTime())||n.toISOString()!==o)&&r("invalid_room_version","Use the exact version returned by room get or room list."),o}function D(o,n){/^[1-9]\d*$/.test(o)||r(n,"Version must be a positive integer.");const t=Number(o);return Number.isSafeInteger(t)||r(n,"Version must be a positive integer."),t}function K(o,n){return[...n,o]}async function B(o,n,t){const c=I.resolve(t);let a;try{a=await q.promises.lstat(c)}catch{r("file_not_found",`File does not exist: ${t}`)}(!a.isFile()||a.isSymbolicLink())&&r("invalid_file",`Not a regular file: ${t}`),(a.size<1||a.size>100*1024*1024)&&r("invalid_file_size","Each file must be between 1 byte and 100 MB.");const v=await q.promises.readFile(c),p=x.createHash("sha256").update(v).digest("hex"),m=X(c),U=await o.initializeAttachment(n,{clientUploadId:`cli:${x.randomUUID()}`,name:I.basename(c),mediaKind:J(I.basename(c),m),mimeType:m,byteSize:a.size,sha256:p}),y=f(U.attachment,"invalid_attachment_response"),_=f(U.upload,"invalid_attachment_response");(typeof y.id!="string"||typeof _.url!="string"||_.method!=="PUT")&&r("invalid_attachment_response","Shennian returned an invalid attachment response.");const i=f(_.headers,"invalid_attachment_response");return Object.values(i).some(e=>typeof e!="string")&&r("invalid_attachment_response","Shennian returned invalid upload headers."),Y(_.url),await o.upload(_.url,i,v),await o.completeAttachment(n,y.id),y.id}function X(o){const n=I.extname(o).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"}[n]??"application/octet-stream"}function W(o,n,t){return Array.isArray(o.messages)||r("invalid_server_response","Shennian returned invalid messages."),{messages:o.messages.map(c=>{const a=f(c,"invalid_server_response"),v=Array.isArray(a.attachments)?a.attachments:[];return{...a,attachments:v.map(p=>{const m=f(p,"invalid_server_response");return(typeof m.attachmentId!="string"||typeof m.name!="string"||typeof m.mimeType!="string"||typeof m.byteSize!="string"||typeof m.sha256!="string")&&r("invalid_server_response","Shennian returned invalid attachment metadata."),{...m,...t.lookup(n,m)}})}})}}function f(o,n){return(!o||typeof o!="object"||Array.isArray(o))&&r(n,"Invalid structured response."),o}function Y(o){let n;try{n=new URL(o)}catch{r("invalid_attachment_response","Invalid upload URL.")}const t=n.protocol==="http:"&&(n.hostname==="localhost"||n.hostname==="127.0.0.1");(n.protocol!=="https:"&&!t||n.username||n.password||n.hash)&&r("invalid_attachment_response","Invalid upload URL.")}function r(o,n,t=null){throw new O(o,n,t)}function S(o,n,t,c){o.write(`${JSON.stringify({ok:!1,error:n,message:t,nextAction:c})}
|
|
4
|
+
`),o.fail()}export{pe as registerRoomCommand};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import{loadConfig as l,saveConfig as g}from"../config/index.js";import{spawnCommandString as
|
|
1
|
+
import{loadConfig as l,saveConfig as g}from"../config/index.js";import{spawnCommandString as y}from"../agents/command-spec.js";import{assessCustomRuntimeSecurity as w}from"../agents/custom-managed-runtime.js";function p(n){return n.models?.length?n.models:n.model?[n.model]:[]}async function C(n,s={}){return new Promise((r,t)=>{const e=y(n,["/caps"],{stdio:["ignore","pipe","pipe"]});let c="",u="",m=!1;const a=i=>{m||(m=!0,clearTimeout(v),i())};e.stdout?.on("data",i=>{c+=i.toString()}),e.stderr?.on("data",i=>{u+=i.toString()}),e.on("error",i=>a(()=>t(new Error(`Failed to run command: ${i.message}`)))),e.on("close",()=>a(()=>{const i=c.trim().split(`
|
|
2
2
|
`).find(o=>o.trim().startsWith("{"));if(!i){t(new Error(`Command did not output valid caps JSON.
|
|
3
|
-
stdout: ${
|
|
4
|
-
stderr: ${
|
|
5
|
-
`)}function d(
|
|
6
|
-
`),process.exitCode=1}export{
|
|
3
|
+
stdout: ${c.trim()}
|
|
4
|
+
stderr: ${u.trim()}`));return}try{const o=JSON.parse(i);if(!o.name||!o.mode)throw new Error('Caps JSON must contain "name" and "mode" fields');if(!s.allowNoModels&&p(o).length===0)throw new Error('Caps JSON must contain either "model" or a non-empty "models" array');if(o.mode!=="spawn"&&o.mode!=="stdio")throw new Error(`Caps "mode" must be "spawn" or "stdio", got: ${o.mode}`);r(o)}catch(o){t(o instanceof Error?o:new Error(`Failed to parse caps JSON: ${i}`))}}));const v=setTimeout(()=>{e.kill("SIGTERM"),a(()=>t(new Error("Timed out waiting for caps response (5s)")))},5e3)})}function A(n){const s=n.command("runtime").description("Manage custom Agent runtimes");s.command("add <name>").description("Register a custom Agent runtime").requiredOption("--command <cmd>","Command to run the runtime").action(async(r,t)=>{if(!/^[a-zA-Z0-9_-]+$/.test(r)){d("invalid_runtime_name","Runtime name may contain only letters, numbers, hyphens, and underscores.");return}let e;try{e=await C(t.command)}catch(a){d("runtime_probe_failed",a instanceof Error?a.message:String(a));return}const c={command:t.command,caps:e},u=w(c);if(e.security&&u.state==="unverified"){d("runtime_security_declaration_invalid",`Custom runtime security declaration could not be verified: ${u.reason}`);return}const m=l();m.customAgents=m.customAgents??{},m.customAgents[r]=c,g(m),f("runtime.add",{runtimeRef:`custom:${r}`,name:e.name,models:p(e),mode:e.mode,version:e.version??null,managedProtection:"unverified",securityDeclaration:u.state})}),s.command("list").description("List registered custom Agent runtimes").action(()=>{const r=Object.entries(l().customAgents??{}).map(([t,e])=>({runtimeRef:`custom:${t}`,name:e.caps.name,models:p(e.caps),mode:e.caps.mode,command:e.command,version:e.caps.version??null,managedProtection:"unverified",securityDeclaration:w(e).state}));f("runtime.list",{runtimes:r})}),s.command("remove <name>").description("Unregister a custom Agent runtime").action(r=>{const t=l();if(!t.customAgents?.[r]){d("runtime_not_found",`No custom runtime named "${r}" exists.`);return}delete t.customAgents[r],g(t),f("runtime.remove",{runtimeRef:`custom:${r}`})})}function f(n,s){process.stdout.write(`${JSON.stringify({ok:!0,command:n,result:s})}
|
|
5
|
+
`)}function d(n,s){process.stdout.write(`${JSON.stringify({ok:!1,error:n,message:s,nextAction:null})}
|
|
6
|
+
`),process.exitCode=1}export{p as getCapsModels,C as probeCaps,A as registerRuntimeCommand};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{SupportClient as m,SupportClientError as c}from"../support/support-client.js";const f=2e3,d=[/\bBearer\s+\S+/i,/\b(sk|pk)-[A-Za-z0-9_-]{8,}\b/,/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/,/\b[A-Za-z0-9_+/]{40,}={0,2}\b/,/\/Users\/\S+/,/\/home\/\S+/,/\/root\/\S+/,/[A-Za-z]:\\\S+/,/\.env(\.[a-z]+)?\b/i],l={write:e=>process.stdout.write(e),fail:()=>{process.exitCode=1}};function S(e,t={}){const n=t.client??new m,o=t.io??l,s=e.command("support").description("Contact Shennian support through the official Room after pairing");s.command("status").description("Check official support Room membership without joining").action(()=>u("support.status",o,()=>n.status())),s.command("ask").description("Send a redacted question and join the official support Room if needed").requiredOption("--text <text>","Redacted question; never include secrets, source, or full logs").action(r=>u("support.ask",o,()=>n.ask(_(r.text)))),s.command("read").description("Read replies after support contact has joined the official 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(r=>u("support.read",o,()=>{r.before&&r.after&&i("ambiguous_sequence_cursor","--before and --after are mutually exclusive."),r.before&&!p(r.before)&&i("invalid_sequence","--before sequence is invalid."),r.after&&!p(r.after)&&i("invalid_sequence","--after sequence is invalid.");const a=Number(r.limit);return(!Number.isInteger(a)||a<1||a>50)&&i("invalid_limit","--limit must be between 1 and 50."),n.read({before:r.before,after:r.after,limit:a})}))}async function u(e,t,n){try{const o=await n();t.write(`${JSON.stringify({ok:!0,command:e,result:o})}
|
|
2
|
+
`)}catch(o){if(o instanceof c){const s=o.nextAction??b(o.code);t.write(`${JSON.stringify({ok:!1,error:o.code,message:o.message,nextAction:s})}
|
|
3
|
+
`),t.fail();return}t.write(`${JSON.stringify({ok:!1,error:"support_command_failed",message:"Shennian support command failed.",nextAction:null})}
|
|
4
|
+
`),t.fail()}}function _(e){const t=e.trim();return t||i("support_text_required","Support text is required."),t.length>f&&i("support_text_too_long","Support text must be at most 2000 characters."),d.some(n=>n.test(t))&&i("support_text_contains_sensitive_data","Remove tokens, absolute paths, environment values, source code, and full logs before contacting support.",{type:"ask_user",message:"Rewrite the issue as a short redacted summary, then run support ask again."}),t}function b(e){return e==="support_room_not_joined"?{type:"run_command",command:"shennian support ask --text <redacted-question>"}:e==="support_room_pending_approval"||e==="official_room_unavailable"?{type:"retry_later",message:"The official support Room is temporarily unavailable."}:null}function p(e){return/^(0|[1-9]\d{0,19})$/.test(e)}function i(e,t,n=null){throw new c(e,t,n)}export{S as registerSupportCommand};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isAvailableAgentType as a}from"@shennian/wire";import{DaemonIpcError as n}from"./protocol.js";function g(s){let d=!1;const
|
|
1
|
+
import{isAvailableAgentType as a}from"@shennian/wire";import{DaemonIpcError as n}from"./protocol.js";function g(s){let d=!1;const u=()=>{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=>{u();const o=e&&typeof e=="object"&&!Array.isArray(e)&&typeof e.agentType=="string"?e.agentType:null,l=!!(e&&typeof e=="object"&&!Array.isArray(e)&&e.compact===!0),p=!!(e&&typeof e=="object"&&!Array.isArray(e)&&e.refresh===!0),c=(p?await s.discover(o??void 0):s.getSnapshot()).filter(t=>!o||t.type===o);return{modelTested:!1,compact:l,refreshed:p,agents:c.map(t=>{const i=t.models.find(r=>r.isDefault),y=(i?[i,...t.models.filter(r=>r.id!==i.id)]:t.models).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,...l?{}:{models:y,modelsTruncated:!1}}})}},"agent.check":async e=>{if(u(),!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};
|