shennian 0.4.34 → 0.4.36
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 +6 -6
- package/dist/src/agents/opencode.js +1 -1
- package/dist/src/upgrade/updater-bootstrap.js +2 -2
- package/dist/src/upgrade/updater-launcher.d.ts +6 -2
- package/dist/src/upgrade/updater-launcher.js +3 -3
- 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
|
@@ -231,8 +231,8 @@
|
|
|
231
231
|
},
|
|
232
232
|
{
|
|
233
233
|
"file": "src/agents/opencode.js",
|
|
234
|
-
"beforeBytes":
|
|
235
|
-
"afterBytes":
|
|
234
|
+
"beforeBytes": 12131,
|
|
235
|
+
"afterBytes": 6608
|
|
236
236
|
},
|
|
237
237
|
{
|
|
238
238
|
"file": "src/agents/pi-coding-agent-managed-permissions.js",
|
|
@@ -891,13 +891,13 @@
|
|
|
891
891
|
},
|
|
892
892
|
{
|
|
893
893
|
"file": "src/upgrade/updater-bootstrap.js",
|
|
894
|
-
"beforeBytes":
|
|
895
|
-
"afterBytes":
|
|
894
|
+
"beforeBytes": 3595,
|
|
895
|
+
"afterBytes": 1840
|
|
896
896
|
},
|
|
897
897
|
{
|
|
898
898
|
"file": "src/upgrade/updater-launcher.js",
|
|
899
|
-
"beforeBytes":
|
|
900
|
-
"afterBytes":
|
|
899
|
+
"beforeBytes": 9356,
|
|
900
|
+
"afterBytes": 4695
|
|
901
901
|
},
|
|
902
902
|
{
|
|
903
903
|
"file": "src/upgrade/updater-protocol.js",
|
|
@@ -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};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import r from"node:fs";import e from"node:path";import{fileURLToPath as S}from"node:url";import{randomUUID as
|
|
2
|
-
`,{mode:384,flag:"wx"});const i=r.openSync(n,process.platform==="win32"?"r+":"r");try{r.fsyncSync(i)}finally{r.closeSync(i)}r.renameSync(n,
|
|
1
|
+
import r from"node:fs";import e from"node:path";import{fileURLToPath as S}from"node:url";import{randomUUID as d}from"node:crypto";import{resolveShennianPath as f}from"../config/index.js";import{UPDATER_PROTOCOL_VERSION as l}from"./updater-protocol.js";function m(t,o){r.mkdirSync(e.dirname(t),{recursive:!0,mode:448});const n=`${t}.tmp-${process.pid}-${d()}`;try{r.writeFileSync(n,`${JSON.stringify(o,null,2)}
|
|
2
|
+
`,{mode:384,flag:"wx"});const i=r.openSync(n,process.platform==="win32"?"r+":"r");try{r.fsyncSync(i)}finally{r.closeSync(i)}r.renameSync(n,t)}finally{try{r.unlinkSync(n)}catch{}}}function U(t={}){const o=t.root??f("runtime","updater"),n=e.join(o,"versions",String(l));r.mkdirSync(n,{recursive:!0,mode:448}),process.platform!=="win32"&&r.chmodSync(n,448),m(e.join(n,"package.json"),{type:"module"});const i=t.assetPath??S(new URL("./updater-worker.js",import.meta.url)),s=e.join(n,"updater-worker.js"),p=r.readFileSync(i),u=(()=>{try{return r.readFileSync(s)}catch{return null}})();if(!u||!u.equals(p)){const c=`${s}.tmp-${process.pid}-${d()}`;try{r.writeFileSync(c,p,{mode:448,flag:"wx"}),process.platform!=="win32"&&r.chmodSync(c,448);const y=r.openSync(c,process.platform==="win32"?"r+":"r");try{r.fsyncSync(y)}finally{r.closeSync(y)}r.renameSync(c,s)}finally{try{r.unlinkSync(c)}catch{}}}const a={protocolVersion:l,workerPath:s,installedAt:(t.now??new Date).toISOString()};return m(e.join(o,"current.json"),a),m(e.join(o,"readiness.json"),{protocolVersion:a.protocolVersion,installedAt:a.installedAt}),a}function $(t=f("runtime","updater")){try{const o=JSON.parse(r.readFileSync(e.join(t,"current.json"),"utf8"));if(o.protocolVersion!==l||!o.workerPath)return null;const n=r.lstatSync(o.workerPath);return n.isSymbolicLink()||!n.isFile()?null:o}catch{return null}}export{U as ensureUpdaterInstalled,$ as readUpdaterReadiness};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { spawn, type ChildProcess } from 'node:child_process';
|
|
1
|
+
import { spawn, spawnSync, type ChildProcess } from 'node:child_process';
|
|
2
2
|
import { ensureUpdaterInstalled } from './updater-bootstrap.js';
|
|
3
3
|
import { type UpdaterJob } from './updater-protocol.js';
|
|
4
4
|
export declare function createUpdaterJob(input: {
|
|
@@ -15,14 +15,18 @@ export declare function createUpdaterJob(input: {
|
|
|
15
15
|
isolatedDaemonStart?: boolean;
|
|
16
16
|
}): UpdaterJob;
|
|
17
17
|
export declare function writeUpdaterJob(job: UpdaterJob, root?: string): string;
|
|
18
|
+
export declare function needsSystemdUpdater(platform: string, cgroup: string): boolean;
|
|
18
19
|
export declare function launchUpdater(input: Parameters<typeof createUpdaterJob>[0], deps?: {
|
|
19
20
|
root?: string;
|
|
20
21
|
spawn?: typeof spawn;
|
|
21
22
|
readiness?: ReturnType<typeof ensureUpdaterInstalled>;
|
|
22
23
|
now?: () => number;
|
|
23
24
|
isAlive?: (pid: number) => boolean;
|
|
25
|
+
serviceCgroup?: string;
|
|
26
|
+
platform?: NodeJS.Platform;
|
|
27
|
+
systemdRun?: typeof spawnSync;
|
|
24
28
|
}): {
|
|
25
29
|
job: UpdaterJob;
|
|
26
|
-
process: ChildProcess;
|
|
30
|
+
process: ChildProcess | null;
|
|
27
31
|
};
|
|
28
32
|
export declare function markUpdaterHealthy(jobId: string, effectiveVersion: string, root?: string): boolean;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import
|
|
2
|
-
`,{mode:384,flag:"wx"});const c=
|
|
3
|
-
`,{mode:384,flag:"wx"});const
|
|
1
|
+
import e from"node:fs";import u from"node:path";import{randomUUID as S}from"node:crypto";import{spawn as j,spawnSync as P}from"node:child_process";import{resolveShennianPath as p}from"../config/index.js";import{findPackageJson as I,resolveNpmGlobalPrefixFromPackageJson as E}from"./engine.js";import{ensureUpdaterInstalled as _}from"./updater-bootstrap.js";import{UPDATER_PROTOCOL_VERSION as w}from"./updater-protocol.js";function b(r,o){e.mkdirSync(u.dirname(r),{recursive:!0,mode:448});const n=`${r}.tmp-${process.pid}-${S()}`;try{e.writeFileSync(n,`${JSON.stringify(o,null,2)}
|
|
2
|
+
`,{mode:384,flag:"wx"});const c=e.openSync(n,process.platform==="win32"?"r+":"r");try{e.fsyncSync(c)}finally{e.closeSync(c)}if(e.renameSync(n,r),process.platform!=="win32"){const t=e.openSync(u.dirname(r),"r");try{e.fsyncSync(t)}finally{e.closeSync(t)}}}finally{try{e.unlinkSync(n)}catch{}}}function $(r){const o=r.root??p("runtime","updater"),n=I(),c=r.npmPrefix??(n?E(n):null);if(!c)throw new Error("Cannot determine the active npm prefix");const t=r.jobId??S();return{protocolVersion:w,jobId:t,fromVersion:r.fromVersion,targetVersion:r.targetVersion,npmPrefix:c,npmExecutable:r.npmExecutable??(process.platform==="win32"?"npm.cmd":"npm"),nodeExecutable:r.nodeExecutable??process.execPath,packageName:"shennian",operation:r.operation??"upgrade",oldDaemonPid:r.oldDaemonPid,platform:process.platform,createdAt:(r.now??new Date).toISOString(),daemonExitTimeoutMs:15e3,commandTimeoutMs:12e4,resultPath:u.join(o,"results",`${t}.json`),attemptPath:p("upgrade-attempt.json"),...r.isolatedDaemonStart?{isolatedDaemonStart:!0}:{}}}function O(r,o=p("runtime","updater")){const n=u.join(o,"jobs");e.mkdirSync(n,{recursive:!0,mode:448});const c=u.join(n,`${r.jobId}.json`),t=`${c}.tmp-${process.pid}-${S()}`;try{e.writeFileSync(t,`${JSON.stringify(r,null,2)}
|
|
3
|
+
`,{mode:384,flag:"wx"});const s=e.openSync(t,process.platform==="win32"?"r+":"r");try{e.fsyncSync(s)}finally{e.closeSync(s)}if(e.renameSync(t,c),process.platform!=="win32"){const a=e.openSync(n,"r");try{e.fsyncSync(a)}finally{e.closeSync(a)}}return c}finally{try{e.unlinkSync(t)}catch{}}}function v(r,o){return r==="linux"&&/(?:^|\/)shennian\.service(?:\n|$)/m.test(o.trim())}function C(r,o={}){const n=o.root??r.root??p("runtime","updater"),c=o.readiness??_({root:n}),t=$({...r,root:n}),s=O(t,n),a=u.join(n,"updater.lock");e.mkdirSync(n,{recursive:!0,mode:448});let l=null,m=!1;const h=()=>{if(m){try{JSON.parse(e.readFileSync(a,"utf8")).jobId===t.jobId&&e.unlinkSync(a)}catch{}m=!1}};try{try{l=e.openSync(a,"wx",384),m=!0}catch(i){if(i.code!=="EEXIST")throw i;let g=!1;try{const y=JSON.parse(e.readFileSync(a,"utf8")),k=o.isAlive??(x=>{try{return process.kill(x,0),!0}catch{return!1}});g=!!(y.pid&&k(y.pid)&&(o.now??Date.now)()-Number(y.createdAt)<30*6e4)}catch{}if(g)throw new Error("upgrade_locked");e.rmSync(a,{force:!0}),l=e.openSync(a,"wx",384),m=!0}e.writeFileSync(l,JSON.stringify({jobId:t.jobId,pid:process.pid,createdAt:(o.now??Date.now)()})),e.closeSync(l),l=null;let d=o.serviceCgroup??"";if(process.platform==="linux"&&!o.spawn&&o.serviceCgroup===void 0)try{d=e.readFileSync("/proc/self/cgroup","utf8")}catch{}if(v(o.platform??process.platform,d)){const i=(o.systemdRun??P)("systemd-run",["--user","--quiet","--collect",`--unit=shennian-updater-${t.jobId}`,"--property=Type=exec","--property=KillMode=process",`--setenv=PATH=${process.env.PATH??"/usr/local/bin:/usr/bin:/bin"}`,"--",t.nodeExecutable,c.workerPath,s],{encoding:"utf8",stdio:"pipe",timeout:1e4});if(i.error||i.status!==0)throw new Error("updater_service_launch_failed");return{job:t,process:null}}const f=(o.spawn??j)(t.nodeExecutable,[c.workerPath,s],{detached:!0,stdio:"ignore",windowsHide:!0});return f.once?.("error",i=>{b(t.resultPath,{protocolVersion:w,jobId:t.jobId,state:"failed",fromVersion:t.fromVersion,targetVersion:t.targetVersion,errorCode:"internal_error",errorSummary:i instanceof Error?i.message.slice(0,500):"updater_spawn_failed",updatedAt:new Date().toISOString()}),h()}),f.unref(),{job:t,process:f}}catch(d){l!==null&&e.closeSync(l),h();try{e.unlinkSync(s)}catch{}throw d}}function H(r,o,n=p("runtime","updater")){if(!/^[A-Za-z0-9_-]{1,128}$/.test(r))return!1;const c=u.join(n,"results",`${r}.json`);try{const t=e.lstatSync(c);if(t.isSymbolicLink()||!t.isFile())return!1;const s=JSON.parse(e.readFileSync(c,"utf8"));return s.protocolVersion!==w||s.jobId!==r||s.state!=="starting"||s.targetVersion!==o?!1:(b(c,{...s,state:s.operation==="rollback"?"rolled_back":"healthy",effectiveVersion:o,updatedAt:new Date().toISOString()}),!0)}catch{return!1}}export{$ as createUpdaterJob,C as launchUpdater,H as markUpdaterHealthy,v as needsSystemdUpdater,O as writeUpdaterJob};
|
|
@@ -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
|
+
}
|