shennian 0.4.34 → 0.4.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/publish-build-manifest.json +2 -2
- package/dist/src/agents/opencode.js +1 -1
- package/node_modules/@shennian/wire/dist/index.d.ts +1 -0
- package/node_modules/@shennian/wire/dist/index.js +1 -0
- package/node_modules/@shennian/wire/dist/project-path.d.ts +4 -0
- package/node_modules/@shennian/wire/dist/project-path.js +25 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createInterface as
|
|
1
|
+
import{createInterface as f}from"node:readline";import{randomUUID as c}from"node:crypto";import{AgentAdapter as p,registerAgent as h}from"./adapter.js";import{resolveBuiltinCommand as m,spawnAgentCommand as E}from"./command-spec.js";import{buildAgentProcessEnv as C,buildManagedAgentProcessEnv as I,buildManagedNativeFullProcessEnv as O}from"../agent-env.js";import{buildInlineChannelPreamble as y}from"./platform-instructions.js";import{resolveManagedAgentAdapterEnforcement as D,isManagedAgentNativeFull as S}from"../security/managed-agent-policy.js";import{registerManagedAgentRuntimeSecurity as w}from"../security/managed-agent-runtime.js";import{compileOpenCodeManagedPermissions as P,createOpenCodeManagedConfigDirectory as _,disposeOpenCodeManagedConfigDirectory as v,OPENCODE_MANAGED_RUNTIME_SECURITY as A}from"./opencode-managed-permissions.js";w("opencode",A);function N(n){if(!n)return;const e=n.input,t=n.output;if(!(typeof e!="number"&&typeof t!="number"))return{inputTokens:typeof e=="number"?e:0,outputTokens:typeof t=="number"?t:0}}function g(n){if(n==null)return"";if(typeof n=="string")return n;try{return JSON.stringify(n)}catch{return String(n)}}function u(n){if(!n)return"opencode request failed";if(typeof n=="string")return n;if(typeof n!="object")return String(n);const e=n;return typeof e.message=="string"?e.message:typeof e.name=="string"?e.name:"data"in e?u(e.data):g(n)||"opencode request failed"}class x extends p{type="opencode";get recoveryCapabilities(){return{inspectTerminal:!1,reattachRun:!1,resumeRun:!1,resumeSession:!0}}process=null;agentSessionId=null;workDir=null;seq=0;runId="";terminalState="closed";externalChannel=null;shennianSessionId=null;extraEnv={};managedAgentPolicy=null;managedConfigDir=null;configure(e){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)}async start(e,t,a){this.workDir=this.managedAgentPolicy?.adapterPolicy.workDir??t,a&&(this.agentSessionId=a),this.resetRunState()}async send(e,t,a,s,r,l){if(l?.source==="room_activation"&&!this.managedAgentPolicy)throw new Error("managed_agent_policy_missing");await this.killProcess(),this.runId=c(),this.resetRunState();const o=["run","--format","json","--dir",this.workDir??process.cwd()];this.agentSessionId&&o.push("--session",this.agentSessionId),t&&o.push("--model",t);const i=`${y(this.workDir??process.cwd(),this.externalChannel,this.shennianSessionId??void 0,r)}${e}`;await this.spawnAndParse(o,i)}async resume(e){await this.killProcess(),this.agentSessionId=e,this.runId=c(),this.resetRunState(),this.emitEvent({state:"start",agentSessionId:e}),this.emitFinalIfOpen({state:"final",agentSessionId:e})}async stop(){await this.killProcess()}resetRunState(){this.seq=0,this.terminalState="open"}async spawnAndParse(e,t){const a=m("opencode");if(!a){this.emit("error",new Error('Command "opencode" not found. Install it with "npm i -g opencode-ai@latest".'));return}const s=await this.prepareManagedLaunch();let r;try{r=E(a,[e[0],...s.args,...e.slice(1)],{cwd:this.workDir??void 0,stdio:["pipe","pipe","pipe"],env:s.env})}catch(i){throw await this.disposeManagedConfig(s.configDir),i}this.process=r,this.managedConfigDir=s.configDir,f({input:r.stdout}).on("line",i=>{if(i.trim())try{const d=JSON.parse(i);this.handleStreamEvent(d)}catch{this.emitEvent({state:"delta",text:i})}});let o="";r.stderr?.on("data",i=>{o+=i.toString()}),r.on("close",i=>{if(this.process!==r||(this.process=null,this.disposeManagedConfig(s.configDir),this.terminalState!=="open"))return;const d=o.trim();if(i!==0&&i!==null){this.emitErrorIfOpen({state:"error",message:d||`opencode exited with code ${i}`});return}this.emitFinalIfOpen({state:"final",agentSessionId:this.agentSessionId??void 0})}),r.on("error",i=>{this.process===r&&(this.process=null,this.disposeManagedConfig(s.configDir),i.code==="ENOENT"?this.emit("error",new Error('Command "opencode" not found. Install it with "npm i -g opencode-ai@latest".')):this.emit("error",i))}),r.stdin.on("error",i=>{this.process===r&&this.emitErrorIfOpen({state:"error",message:i.message})}),r.stdin.end(t)}async prepareManagedLaunch(){const e=this.managedAgentPolicy;if(!e)return{args:["--auto"],env:C({NO_COLOR:"1",...this.extraEnv}),configDir:null};if(S(e))return{args:["--auto"],env:O({NO_COLOR:"1",...this.extraEnv}),configDir:null};const t=P(D(e),e.adapterPolicy.workDir),a=await _(t),s=I({agentType:"opencode",providerEnv:this.extraEnv,sessionEnv:this.extraEnv,runtimeEnv:{NO_COLOR:"1"}});return s.OPENCODE_CONFIG=a.configPath,s.OPENCODE_CONFIG_DIR=a.directory,s.OPENCODE_CONFIG_CONTENT=JSON.stringify(t.config),s.OPENCODE_DISABLE_PROJECT_CONFIG="true",s.OPENCODE_DISABLE_CLAUDE_CODE="1",s.OPENCODE_DISABLE_CLAUDE_CODE_PROMPT="1",s.OPENCODE_DISABLE_CLAUDE_CODE_SKILLS="1",{args:t.args,env:s,configDir:a.directory}}async disposeManagedConfig(e){e&&(this.managedConfigDir===e&&(this.managedConfigDir=null),await v(e).catch(()=>{}))}handleStreamEvent(e){e.sessionID&&e.sessionID!==this.agentSessionId&&(this.agentSessionId=e.sessionID,this.emitEvent({state:"start",agentSessionId:e.sessionID}));const t=e.part;if(e.type==="text"&&t?.text){this.emitEvent({state:"delta",text:t.text});return}if(e.type==="reasoning"&&t?.text){this.emitEvent({state:"delta",text:t.text,thinking:!0});return}if(e.type==="tool_use"&&t?.type==="tool"){t.state?.status==="completed"||t.state?.status==="error"?this.emitEvent({state:"tool-result",name:t.tool??"tool",result:t.state.status==="error"?t.state.error??"tool failed":g(t.state?.output)}):this.emitEvent({state:"tool-call",name:t.tool??"tool",args:t.state?.input??{}});return}if(e.type==="error"){this.emitErrorIfOpen({state:"error",message:u(e.error)});return}e.type==="step_finish"&&this.emitFinalIfOpen({state:"final",agentSessionId:this.agentSessionId??e.sessionID,usage:N(t?.tokens)})}emitEvent(e){const t={...e,runId:this.runId,seq:this.seq++};this.emit("agentEvent",t)}emitFinalIfOpen(e){this.terminalState==="open"&&(this.terminalState="closed",this.emitEvent(e))}emitErrorIfOpen(e){this.terminalState==="open"&&(this.terminalState="closed",this.emitEvent(e))}async killProcess(){const e=this.process;if(!e){await this.disposeManagedConfig(this.managedConfigDir);return}this.process=null,e.kill("SIGTERM"),await new Promise(t=>{e.on("close",t),setTimeout(()=>{e.kill("SIGKILL"),t()},3e3)}),await this.disposeManagedConfig(this.managedConfigDir)}}h("opencode",()=>new x);export{x as OpenCodeAdapter};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Matching only: never changes persisted project IDs or directory spelling. */
|
|
2
|
+
export declare function normalizeProjectMatchPath(workDir: string): string;
|
|
3
|
+
/** Include both existing Windows spellings in one paginated database query. */
|
|
4
|
+
export declare function projectSessionPathCandidates(workDir: string): string[];
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// @arch docs/architecture/session-organization.md
|
|
2
|
+
// @test src/__tests__/project-path.test.ts
|
|
3
|
+
/** Matching only: never changes persisted project IDs or directory spelling. */
|
|
4
|
+
export function normalizeProjectMatchPath(workDir) {
|
|
5
|
+
const trimmed = workDir.trim();
|
|
6
|
+
const windows = /^[a-z]:[\\/]/i.test(trimmed) || /^\\\\/.test(trimmed) || /^\/\//.test(trimmed);
|
|
7
|
+
const path = windows ? trimmed.replace(/\\/g, '/') : trimmed;
|
|
8
|
+
return path === '/' || path === '~' ? path : path.replace(/[\\/]+$/, '');
|
|
9
|
+
}
|
|
10
|
+
/** Include both existing Windows spellings in one paginated database query. */
|
|
11
|
+
export function projectSessionPathCandidates(workDir) {
|
|
12
|
+
const path = normalizeProjectMatchPath(workDir);
|
|
13
|
+
if (path === '/' || path === '~')
|
|
14
|
+
return [path];
|
|
15
|
+
const paths = /^[a-z]:(?:\/|$)/i.test(path) || path.startsWith('//')
|
|
16
|
+
? [path, path.replace(/\//g, '\\')]
|
|
17
|
+
: [path];
|
|
18
|
+
const candidates = new Set();
|
|
19
|
+
for (const value of paths) {
|
|
20
|
+
candidates.add(value);
|
|
21
|
+
candidates.add(`${value}/`);
|
|
22
|
+
candidates.add(`${value}\\`);
|
|
23
|
+
}
|
|
24
|
+
return [...candidates];
|
|
25
|
+
}
|