shennian 0.5.6 → 0.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": 1,
3
3
  "jsMinified": true,
4
4
  "minifier": "esbuild",
5
- "fileCount": 250,
5
+ "fileCount": 252,
6
6
  "files": [
7
7
  {
8
8
  "file": "bin/shennian-adapter-host.js",
@@ -291,8 +291,8 @@
291
291
  },
292
292
  {
293
293
  "file": "src/agents/opencode.js",
294
- "beforeBytes": 13890,
295
- "afterBytes": 7062
294
+ "beforeBytes": 14292,
295
+ "afterBytes": 7094
296
296
  },
297
297
  {
298
298
  "file": "src/agents/pi-coding-agent-managed-permissions.js",
@@ -384,6 +384,11 @@
384
384
  "beforeBytes": 6275,
385
385
  "afterBytes": 3292
386
386
  },
387
+ {
388
+ "file": "src/commands/command-reference.js",
389
+ "beforeBytes": 963,
390
+ "afterBytes": 465
391
+ },
387
392
  {
388
393
  "file": "src/commands/daemon-instance-lock.js",
389
394
  "beforeBytes": 4507,
@@ -461,8 +466,8 @@
461
466
  },
462
467
  {
463
468
  "file": "src/commands/room.js",
464
- "beforeBytes": 26027,
465
- "afterBytes": 15403
469
+ "beforeBytes": 27387,
470
+ "afterBytes": 16121
466
471
  },
467
472
  {
468
473
  "file": "src/commands/runtime.js",
@@ -481,8 +486,8 @@
481
486
  },
482
487
  {
483
488
  "file": "src/commands/support.js",
484
- "beforeBytes": 4571,
485
- "afterBytes": 2964
489
+ "beforeBytes": 5435,
490
+ "afterBytes": 3542
486
491
  },
487
492
  {
488
493
  "file": "src/commands/tools.js",
@@ -581,8 +586,8 @@
581
586
  },
582
587
  {
583
588
  "file": "src/index.js",
584
- "beforeBytes": 43108,
585
- "afterBytes": 21430
589
+ "beforeBytes": 43231,
590
+ "afterBytes": 21514
586
591
  },
587
592
  {
588
593
  "file": "src/log-redaction.js",
@@ -874,6 +879,11 @@
874
879
  "beforeBytes": 30643,
875
880
  "afterBytes": 17214
876
881
  },
882
+ {
883
+ "file": "src/room/wait-for-messages.js",
884
+ "beforeBytes": 946,
885
+ "afterBytes": 507
886
+ },
877
887
  {
878
888
  "file": "src/runtime-distribution.js",
879
889
  "beforeBytes": 1096,
@@ -16,7 +16,7 @@
16
16
 
17
17
  ## 系统提示词
18
18
 
19
- **当前实现不满足 Adapter 协议 V1 的系统提示词要求。** 和 Cursor 一样,系统提示词由 `buildInlineChannelPreamble(...)` 拼接在用户正文前一起发送(见 `opencode.ts` 的 `send()`),而不是通过独立的 system/instructions 参数注入。这是协议 V1 明确禁止的做法,OpenCode 目前无法通过完整检查的系统提示词冲突测试。
19
+ 系统提示词通过 `buildInlineChannelPreamble(...)` 拼接在用户正文前一起发送(见 `opencode.ts` 的 `send()`)。这是当前 OpenCode 兼容接入的既定传递方式;如上游提供稳定的独立 system/instructions 参数,将另行评估迁移。
20
20
 
21
21
  ## 登录与环境
22
22
 
@@ -24,8 +24,8 @@
24
24
 
25
25
  ## 可用性检查
26
26
 
27
- 模型发现、`start/send/stop` 和精确 `resume` 目前可用;系统提示词检查会失败,因此 OpenCode 目前不满足 Adapter V1 的完整检查,不进入正常可选列表。
27
+ 模型发现、`start/send/stop` 和精确 `resume` 可用。候选对 OpenCode 1.18.x 与 2.0.11 共用子进程工作目录,并在 POSIX 同步 `PWD`,不传 2.0.11 不接受的 `run --dir`。代码级检查已通过;两个版本的真实 `agent check`、工作目录与回复仍需独立实机回归后才能宣称兼容完成。
28
28
 
29
29
  ## 已知限制与排障
30
30
 
31
- - 系统提示词目前是内联拼接,不是真实的 system/instructions 通道;需要确认 OpenCode 是否有等价的独立指令参数后才能补齐这项检查。
31
+ - 系统提示词当前以内联方式传递;这是兼容接入策略,不影响可用性检查。
@@ -1 +1 @@
1
- import{createInterface as g}from"node:readline";import{randomUUID as u}from"node:crypto";import{AgentAdapter as f,registerAgent as h}from"./adapter.js";import{resolveBuiltinCommand as m,spawnAgentCommand as E}from"./command-spec.js";import{spawnEnoentError as C}from"./spawn-errors.js";import{DETACH_PROCESS_GROUP as y,stopProcessTree as O}from"./process-tree.js";import{buildAgentProcessEnv as D,buildManagedAgentProcessEnv as I,buildManagedNativeFullProcessEnv as S}from"../agent-env.js";import{buildInlineChannelPreamble as P}from"./platform-instructions.js";import{resolveManagedAgentAdapterEnforcement as _,isManagedAgentNativeFull as w}from"../security/managed-agent-policy.js";import{registerManagedAgentRuntimeSecurity as A}from"../security/managed-agent-runtime.js";import{compileOpenCodeManagedPermissions as v,createOpenCodeManagedConfigDirectory as N,disposeOpenCodeManagedConfigDirectory as x,OPENCODE_MANAGED_RUNTIME_SECURITY as k}from"./opencode-managed-permissions.js";A("opencode",k);const T=64*1024;function R(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 l(n){if(n==null)return"";if(typeof n=="string")return n;try{return JSON.stringify(n)}catch{return String(n)}}function p(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?p(e.data):l(n)||"opencode request failed"}class M extends f{type="opencode";get recoveryCapabilities(){return{inspectTerminal:!1,reattachRun:!1,resumeRun:!1,resumeSession:!0}}process=null;agentSessionId=null;workDir=null;seq=0;runId="";terminalState="closed";runUsage;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,o){this.workDir=this.managedAgentPolicy?.adapterPolicy.workDir??t,o&&(this.agentSessionId=o),this.resetRunState()}async send(e,t,o,i,s,c){if(c?.source==="room_activation"&&!this.managedAgentPolicy)throw new Error("managed_agent_policy_missing");await this.killProcess(),this.runId=u(),this.resetRunState();const a=["run","--format","json","--dir",this.workDir??process.cwd()];this.agentSessionId&&a.push("--session",this.agentSessionId),t&&a.push("--model",t);const r=`${P(this.workDir??process.cwd(),this.externalChannel,this.shennianSessionId??void 0,s)}${e}`;await this.spawnAndParse(a,r)}async resume(e){await this.killProcess(),this.agentSessionId=e,this.runId=u(),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",this.runUsage=void 0}async spawnAndParse(e,t){const o=m("opencode");if(!o){this.emit("error",new Error('Command "opencode" not found. Install it with "npm i -g opencode-ai@latest".'));return}const i=await this.prepareManagedLaunch();let s;try{s=E(o,[e[0],...i.args,...e.slice(1)],{cwd:this.workDir??void 0,stdio:["pipe","pipe","pipe"],env:i.env,detached:y})}catch(r){throw await this.disposeManagedConfig(i.configDir),r}this.process=s,this.managedConfigDir=i.configDir,g({input:s.stdout}).on("line",r=>{this.handleProcessLine(s,r)});let a=Buffer.alloc(0);s.stderr?.on("data",r=>{const d=T-a.byteLength;d>0&&(a=Buffer.concat([a,r.subarray(0,d)]))}),s.on("close",(r,d)=>{this.process===s&&(this.disposeManagedConfig(i.configDir),this.terminalState==="open"&&this.completeRunAfterProcessExit(r,d,a.toString("utf8").trim()))}),s.on("error",r=>{this.process===s&&(this.process=null,this.disposeManagedConfig(i.configDir),r.code==="ENOENT"?this.emit("error",C(this.workDir,'Command "opencode" not found. Install it with "npm i -g opencode-ai@latest".')):this.emit("error",r))}),s.stdin.on("error",r=>{this.process===s&&this.emitErrorIfOpen({state:"error",message:r.message})}),s.stdin.end(t)}async prepareManagedLaunch(){const e=this.managedAgentPolicy;if(!e)return{args:["--auto"],env:D({NO_COLOR:"1",...this.extraEnv}),configDir:null};if(w(e))return{args:["--auto"],env:S({NO_COLOR:"1",...this.extraEnv}),configDir:null};const t=v(_(e),e.adapterPolicy.workDir),o=await N(t),i=I({agentType:"opencode",providerEnv:this.extraEnv,sessionEnv:this.extraEnv,runtimeEnv:{NO_COLOR:"1"}});return i.OPENCODE_CONFIG=o.configPath,i.OPENCODE_CONFIG_DIR=o.directory,i.OPENCODE_CONFIG_CONTENT=JSON.stringify(t.config),i.OPENCODE_DISABLE_PROJECT_CONFIG="true",i.OPENCODE_DISABLE_CLAUDE_CODE="1",i.OPENCODE_DISABLE_CLAUDE_CODE_PROMPT="1",i.OPENCODE_DISABLE_CLAUDE_CODE_SKILLS="1",{args:t.args,env:i,configDir:o.directory}}async disposeManagedConfig(e){e&&(this.managedConfigDir===e&&(this.managedConfigDir=null),await x(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":l(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:p(e.error)});return}e.type==="step_finish"&&this.recordStepUsage(R(t?.tokens))}handleProcessLine(e,t){if(!(this.process!==e||!t.trim()))try{this.handleStreamEvent(JSON.parse(t))}catch{this.emitEvent({state:"delta",text:t})}}recordStepUsage(e){e&&(this.runUsage={inputTokens:(this.runUsage?.inputTokens??0)+e.inputTokens,outputTokens:(this.runUsage?.outputTokens??0)+e.outputTokens})}completeRunAfterProcessExit(e=0,t=null,o=""){if(e!==0||t!==null){this.emitErrorIfOpen({state:"error",message:o||(t?`opencode exited with signal ${t}`:`opencode exited with code ${String(e)}`)});return}this.emitFinalIfOpen({state:"final",agentSessionId:this.agentSessionId??void 0,usage:this.runUsage})}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,await O(e),await this.disposeManagedConfig(this.managedConfigDir)}}h("opencode",()=>new M);export{M as OpenCodeAdapter};
1
+ import{createInterface as f}from"node:readline";import{randomUUID as u}from"node:crypto";import{AgentAdapter as g,registerAgent as h}from"./adapter.js";import{resolveBuiltinCommand as m,spawnAgentCommand as E}from"./command-spec.js";import{spawnEnoentError as C}from"./spawn-errors.js";import{DETACH_PROCESS_GROUP as y,stopProcessTree as O}from"./process-tree.js";import{buildAgentProcessEnv as D,buildManagedAgentProcessEnv as I,buildManagedNativeFullProcessEnv as S}from"../agent-env.js";import{buildInlineChannelPreamble as P}from"./platform-instructions.js";import{resolveManagedAgentAdapterEnforcement as w,isManagedAgentNativeFull as _}from"../security/managed-agent-policy.js";import{registerManagedAgentRuntimeSecurity as A}from"../security/managed-agent-runtime.js";import{compileOpenCodeManagedPermissions as v,createOpenCodeManagedConfigDirectory as N,disposeOpenCodeManagedConfigDirectory as x,OPENCODE_MANAGED_RUNTIME_SECURITY as k}from"./opencode-managed-permissions.js";A("opencode",k);const T=64*1024;function R(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 l(n){if(n==null)return"";if(typeof n=="string")return n;try{return JSON.stringify(n)}catch{return String(n)}}function p(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?p(e.data):l(n)||"opencode request failed"}class M extends g{type="opencode";get recoveryCapabilities(){return{inspectTerminal:!1,reattachRun:!1,resumeRun:!1,resumeSession:!0}}process=null;agentSessionId=null;workDir=null;seq=0;runId="";terminalState="closed";runUsage;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,o){this.workDir=this.managedAgentPolicy?.adapterPolicy.workDir??t,o&&(this.agentSessionId=o),this.resetRunState()}async send(e,t,o,i,s,c){if(c?.source==="room_activation"&&!this.managedAgentPolicy)throw new Error("managed_agent_policy_missing");await this.killProcess(),this.runId=u(),this.resetRunState();const a=["run","--format","json"];this.agentSessionId&&a.push("--session",this.agentSessionId),t&&a.push("--model",t);const r=`${P(this.workDir??process.cwd(),this.externalChannel,this.shennianSessionId??void 0,s)}${e}`;await this.spawnAndParse(a,r)}async resume(e){await this.killProcess(),this.agentSessionId=e,this.runId=u(),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",this.runUsage=void 0}async spawnAndParse(e,t){const o=m("opencode");if(!o){this.emit("error",new Error('Command "opencode" not found. Install it with "npm i -g opencode-ai@latest".'));return}const i=await this.prepareManagedLaunch();process.platform!=="win32"&&(i.env.PWD=this.workDir??process.cwd());let s;try{s=E(o,[e[0],...i.args,...e.slice(1)],{cwd:this.workDir??void 0,stdio:["pipe","pipe","pipe"],env:i.env,detached:y})}catch(r){throw await this.disposeManagedConfig(i.configDir),r}this.process=s,this.managedConfigDir=i.configDir,f({input:s.stdout}).on("line",r=>{this.handleProcessLine(s,r)});let a=Buffer.alloc(0);s.stderr?.on("data",r=>{const d=T-a.byteLength;d>0&&(a=Buffer.concat([a,r.subarray(0,d)]))}),s.on("close",(r,d)=>{this.process===s&&(this.disposeManagedConfig(i.configDir),this.terminalState==="open"&&this.completeRunAfterProcessExit(r,d,a.toString("utf8").trim()))}),s.on("error",r=>{this.process===s&&(this.process=null,this.disposeManagedConfig(i.configDir),r.code==="ENOENT"?this.emit("error",C(this.workDir,'Command "opencode" not found. Install it with "npm i -g opencode-ai@latest".')):this.emit("error",r))}),s.stdin.on("error",r=>{this.process===s&&this.emitErrorIfOpen({state:"error",message:r.message})}),s.stdin.end(t)}async prepareManagedLaunch(){const e=this.managedAgentPolicy;if(!e)return{args:["--auto"],env:D({NO_COLOR:"1",...this.extraEnv}),configDir:null};if(_(e))return{args:["--auto"],env:S({NO_COLOR:"1",...this.extraEnv}),configDir:null};const t=v(w(e),e.adapterPolicy.workDir),o=await N(t),i=I({agentType:"opencode",providerEnv:this.extraEnv,sessionEnv:this.extraEnv,runtimeEnv:{NO_COLOR:"1"}});return i.OPENCODE_CONFIG=o.configPath,i.OPENCODE_CONFIG_DIR=o.directory,i.OPENCODE_CONFIG_CONTENT=JSON.stringify(t.config),i.OPENCODE_DISABLE_PROJECT_CONFIG="true",i.OPENCODE_DISABLE_CLAUDE_CODE="1",i.OPENCODE_DISABLE_CLAUDE_CODE_PROMPT="1",i.OPENCODE_DISABLE_CLAUDE_CODE_SKILLS="1",{args:t.args,env:i,configDir:o.directory}}async disposeManagedConfig(e){e&&(this.managedConfigDir===e&&(this.managedConfigDir=null),await x(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":l(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:p(e.error)});return}e.type==="step_finish"&&this.recordStepUsage(R(t?.tokens))}handleProcessLine(e,t){if(!(this.process!==e||!t.trim()))try{this.handleStreamEvent(JSON.parse(t))}catch{this.emitEvent({state:"delta",text:t})}}recordStepUsage(e){e&&(this.runUsage={inputTokens:(this.runUsage?.inputTokens??0)+e.inputTokens,outputTokens:(this.runUsage?.outputTokens??0)+e.outputTokens})}completeRunAfterProcessExit(e=0,t=null,o=""){if(e!==0||t!==null){this.emitErrorIfOpen({state:"error",message:o||(t?`opencode exited with signal ${t}`:`opencode exited with code ${String(e)}`)});return}this.emitFinalIfOpen({state:"final",agentSessionId:this.agentSessionId??void 0,usage:this.runUsage})}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,await O(e),await this.disposeManagedConfig(this.managedConfigDir)}}h("opencode",()=>new M);export{M as OpenCodeAdapter};
@@ -0,0 +1,4 @@
1
+ function c(n){return!n._hidden}function d(n,r){n.addHelpCommand(!1),n.command("help [path...]").description("Show current Client commands and usage").action(i=>{let e=n;for(const o of i){const s=e.commands.find(t=>c(t)&&(t.name()===o||t.aliases().includes(o)));if(!s){process.stderr.write(`Unknown command: ${i.join(" ")}
2
+ `),process.exitCode=1;return}e=s}process.stdout.write(`Shennian Client ${r}
3
+ ${e.helpInformation()}
4
+ `)})}export{d as registerCommandReference};
@@ -1,4 +1,6 @@
1
- import x from"node:crypto";import O from"node:fs";import I from"node:path";import{PublicAttachmentCache as $}from"../room/public-attachment-cache.js";import{PublicRoomClient as E,PublicRoomClientError as N}from"../room/public-room-client.js";import{callDaemonIpc as C}from"../daemon-ipc/client.js";import{DaemonIpcError as M}from"../daemon-ipc/protocol.js";import{MANAGED_ROOM_IPC_ROOT_ENV as F,resolveManagedRoomIpcPaths as V}from"../daemon-ipc/paths.js";import{MANAGED_ROOM_CONTEXT_ENV as G}from"../room/managed-context.js";import{createRoomInviteQr as H}from"../room/invite-qr.js";import{externalRoomNextAction as j,managedRoomNextAction as J,SHENNIAN_INSTALL_URL as z}from"../room/room-error-guidance.js";import{attachmentMediaKind as Q}from"../room/attachment-upload.js";const Z={write:n=>{process.stdout.write(n)},fail:()=>{process.exitCode=1}};function ue(n,o={}){const t=o.client??new E,m=o.cache??new $({client:t}),a=o.io??Z,v=o.createInviteQr??H,p=async(i,e)=>{const s=await v(i).catch(()=>({imagePath:null,mimeType:"image/png",payload:i,dataUrl:null}));return{inviteUrl:i,installUrl:z,text:K(i,e),inviteQr:s}},c=n.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 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})})),c.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)}})),c.command("list").description("List Rooms for the paired user; duplicate names remain separate candidates").action(()=>l("room.list",a,()=>(u("list"),t.list()))),c.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))))),c.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:D(e.version),...s===void 0?{}:{name:s},...e.visibility===void 0?{}:{visibility:e.visibility},...e.joinPolicy===void 0?{}:{joinPolicy:e.joinPolicy}})})),c.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),D(e.version))))),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(i,e)=>l("room.join",a,async()=>{u("join");const s=q(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)})),c.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=q(i,"stable");return t.open(e.roomId)})),c.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(S(i))})),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(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=S(i),w=await t.read(R,{before:e.before,after:e.after,limit:s});return Y(w,R,m)})}),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",B,[]).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=S(i),w=[];for(const T of e.file)w.push(await X(t,R,T));return t.send(R,{clientMessageId:`cli:${x.randomUUID()}`,...s?{text:s}:{},...w.length>0?{attachmentIds:w}:{}})})}),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 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})})),c.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}):m.materialize(S(i),d)})}),c.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=c.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),k(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 _=c.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,k(e.version,"room_member_version"))))),c.command("search",{hidden:!0}).allowUnknownOption(!0).action(()=>A(a,"command_removed","Room search is not available to Agents.",null)),c.command("agent",{hidden:!0}).allowUnknownOption(!0).action(()=>A(a,"command_removed","Managed Agents are created in the Shennian mobile App.",null))}function K(n,o){return[o?"\u9080\u8BF7\u4F60\u52A0\u5165\u795E\u5FF5\u7FA4\u804A\u300C"+o+"\u300D\u3002":"\u70B9\u51FB\u52A0\u5165\u795E\u5FF5\u7FA4\u804A\uFF0C\u6216\u628A\u672C\u6D88\u606F\u53D1\u7ED9\u4F60\u7684 AI\uFF1A",...o?["\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"+n,"\u5B89\u88C5\uFF1A"+z].join(`
2
- `)}async function l(n,o,t){try{const m=await t();o.write(`${JSON.stringify({ok:!0,command:n,result:P(m)})}
3
- `)}catch(m){if(m instanceof N){A(o,m.code,m.message,m.nextAction);return}if(m instanceof M){A(o,m.code,m.message,J(m.code));return}A(o,"room_command_failed","Shennian Room command failed.",null)}}function P(n){if(Array.isArray(n))return n.map(P);if(!n||typeof n!="object")return n;const o={};for(const[t,m]of Object.entries(n))t==="launchUrl"||t==="access"||(o[t]=P(m));return o}function b(){return process.env[G]?.trim()||null}async function g(n,o,t){const m=process.env[F]?.trim();if(!m||!I.isAbsolute(m))throw new M("managed_room_context_unavailable","Managed Room IPC context is unavailable.");return C({method:"managed-room.execute",params:{context:n,action:o,...t},paths:V(m),timeoutMs:o==="send"||o==="attachment.get"?18e4:15e3})}function u(n){b()&&r("managed_room_action_forbidden",`${n} is not available in managed Room context.`)}function S(n){return n||r("room_ref_required","Use an official Room URL returned by Shennian.",j()),h(n)}function h(n){return q(n,"stable").roomId}function q(n,o){let t;const m=o==="invite"?"invalid_invite_url":"invalid_room_ref",a=o==="invite"?"Use an official Shennian Room invitation URL.":"Use an official Room URL returned by Shennian.";try{t=new URL(n)}catch{r(m,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(c=>c!=="invite")||o==="stable"&&p!=null||o==="invite"&&(p==null||!/^[A-Za-z0-9_-]{32,256}$/.test(p)))&&r(m,a),{roomId:v[1],...p?{inviteToken:p}:{}}}function L(n){return/^(0|[1-9]\d{0,19})$/.test(n)}function D(n){const o=new Date(n);return(Number.isNaN(o.getTime())||o.toISOString()!==n)&&r("invalid_room_version","Use the exact version returned by room get or room list."),n}function k(n,o){/^[1-9]\d*$/.test(n)||r(o,"Version must be a positive integer.");const t=Number(n);return Number.isSafeInteger(t)||r(o,"Version must be a positive integer."),t}function B(n,o){return[...o,n]}async function X(n,o,t){const m=I.resolve(t);let a;try{a=await O.promises.lstat(m)}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 O.promises.readFile(m),p=x.createHash("sha256").update(v).digest("hex"),c=W(m),U=await n.initializeAttachment(o,{clientUploadId:`cli:${x.randomUUID()}`,name:I.basename(m),mediaKind:Q(I.basename(m),c),mimeType:c,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."),ee(_.url),await n.upload(_.url,i,v),await n.completeAttachment(o,y.id),y.id}function W(n){const o=I.extname(n).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"}[o]??"application/octet-stream"}function Y(n,o,t){return Array.isArray(n.messages)||r("invalid_server_response","Shennian returned invalid messages."),{messages:n.messages.map(m=>{const a=f(m,"invalid_server_response"),v=Array.isArray(a.attachments)?a.attachments:[];return{...a,attachments:v.map(p=>{const c=f(p,"invalid_server_response");return(typeof c.attachmentId!="string"||typeof c.name!="string"||typeof c.mimeType!="string"||typeof c.byteSize!="string"||typeof c.sha256!="string")&&r("invalid_server_response","Shennian returned invalid attachment metadata."),{...c,...t.lookup(o,c)}})}})}}function f(n,o){return(!n||typeof n!="object"||Array.isArray(n))&&r(o,"Invalid structured response."),n}function ee(n){let o;try{o=new URL(n)}catch{r("invalid_attachment_response","Invalid upload URL.")}const t=o.protocol==="http:"&&(o.hostname==="localhost"||o.hostname==="127.0.0.1");(o.protocol!=="https:"&&!t||o.username||o.password||o.hash)&&r("invalid_attachment_response","Invalid upload URL.")}function r(n,o,t=null){throw new N(n,o,t)}function A(n,o,t,m){n.write(`${JSON.stringify({ok:!1,error:o,message:t,nextAction:m})}
4
- `),n.fail()}export{ue as registerRoomCommand};
1
+ import x from"node:crypto";import M from"node:fs";import I from"node:path";import{PublicAttachmentCache as $}from"../room/public-attachment-cache.js";import{PublicRoomClient as E,PublicRoomClientError as N}from"../room/public-room-client.js";import{callDaemonIpc as C}from"../daemon-ipc/client.js";import{DaemonIpcError as z}from"../daemon-ipc/protocol.js";import{MANAGED_ROOM_IPC_ROOT_ENV as F,resolveManagedRoomIpcPaths as V}from"../daemon-ipc/paths.js";import{MANAGED_ROOM_CONTEXT_ENV as G}from"../room/managed-context.js";import{createRoomInviteQr as H}from"../room/invite-qr.js";import{externalRoomNextAction as U,managedRoomNextAction as J,SHENNIAN_INSTALL_URL as L}from"../room/room-error-guidance.js";import{attachmentMediaKind as Q}from"../room/attachment-upload.js";import{parseWaitTimeout as Z,waitForMessages as W}from"../room/wait-for-messages.js";const B={write:n=>{process.stdout.write(n)},fail:()=>{process.exitCode=1}};function _e(n,o={}){const t=o.client??new E,m=o.cache??new $({client:t}),s=o.io??B,v=o.createInviteQr??H,u=async(i,e)=>{const a=await v(i).catch(()=>({imagePath:null,mimeType:"image/png",payload:i,dataUrl:null}));return{inviteUrl:i,installUrl:L,text:K(i,e),inviteQr:a}},c=n.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 i=>l("room.schedule",s,async()=>{const e=y();return e||r("managed_room_context_required","Scheduling is only available in managed Room context.",U()),R(e,"schedule",{...i,version:i.version?Number(i.version):void 0})})),c.command("create").description("Create a Room as the paired user").requiredOption("--name <name>","Room name").action(async i=>l("room.create",s,async()=>{p("create");const e=i.name.trim();(!e||e.length>80)&&r("invalid_room_name","Room name must contain 1 to 80 characters.");const a=await t.create(e,x.randomUUID()),d=f(a.room,"invalid_room_response");return typeof d.inviteUrl!="string"&&r("invalid_room_response","Shennian returned an invalid Room invitation."),{...a,sharing:await u(d.inviteUrl,typeof d.name=="string"?d.name:e)}})),c.command("list").description("List Rooms for the paired user; duplicate names remain separate candidates").action(()=>l("room.list",s,()=>(p("list"),t.list()))),c.command("get <room-ref>").description("Get one explicitly referenced Room and its current version").action(i=>l("room.get",s,()=>(p("get"),t.get(h(i))))),c.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",s,()=>{p("update");const a=e.name?.trim();return e.name!==void 0&&(!a||a.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."),a===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:k(e.version),...a===void 0?{}:{name:a},...e.visibility===void 0?{}:{visibility:e.visibility},...e.joinPolicy===void 0?{}:{joinPolicy:e.joinPolicy}})})),c.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",s,()=>(p("delete"),t.deleteRecord(h(i),k(e.version))))),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(i,e)=>l("room.join",s,async()=>{p("join");const a=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(a.roomId,a.inviteToken,d)})),c.command("open <room-url>").description("Open one Room where the paired user is already a member").action(async i=>l("room.open",s,async()=>{p("open");const e=P(i,"stable");return t.open(e.roomId)})),c.command("status [room-ref]").description("Show the managed Room, or one explicitly referenced Room").action(async i=>l("room.status",s,()=>{const e=y();return e?(i&&r("managed_room_ref_forbidden","Do not pass a Room reference in managed Room context."),R(e,"status",{})):t.status(A(i))})),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(i,e)=>{await l("room.read",s,async()=>{e.before&&e.after&&r("ambiguous_sequence_cursor","--before and --after are mutually exclusive."),e.before&&!O(e.before)&&r("invalid_sequence","--before sequence is invalid."),e.after&&!O(e.after)&&r("invalid_sequence","--after sequence is invalid.");const a=Number(e.limit);(!Number.isInteger(a)||a<1||a>50)&&r("invalid_limit","--limit must be between 1 and 50.");const d=y();if(d)return i&&r("managed_room_ref_forbidden","Do not pass a Room reference in managed Room context."),R(d,"read",{...e.before?{before:e.before}:{},...e.after?{after:e.after}:{},limit:a});const b=A(i),w=await t.read(b,{before:e.before,after:e.after,limit:a});return oe(w,b,m)})}),c.command("wait <room-ref>").description("Wait up to 60 seconds for new messages after an exact sequence").requiredOption("--after <sequence>","Sequence returned by room send or read").option("--timeout <seconds>","Wait for 1 to 60 seconds","60").action((i,e)=>l("room.wait",s,async()=>{p("wait"),O(e.after)||r("invalid_sequence","--after sequence is invalid.");let a;try{a=Z(e.timeout)}catch{r("invalid_wait_timeout","--timeout must be between 1 and 60 seconds.")}const d=A(i);return W(b=>t.read(d,{after:b,limit:50}),e.after,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",X,[]).action(async(i,e)=>{await l("room.send",s,async()=>{const a=Y(e.text?.trim()??"");!a&&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=y();if(d)return i&&r("managed_room_ref_forbidden","Do not pass a Room reference in managed Room context."),R(d,"send",{clientMessageId:`managed:${x.randomUUID()}`,text:a,files:e.file});const b=A(i),w=[];for(const T of e.file)w.push(await ee(t,b,T));return t.send(b,{clientMessageId:`cli:${x.randomUUID()}`,...a?{text:a}:{},...w.length>0?{attachmentIds:w}:{}})})}),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 i=>l("room.pause",s,()=>{const e=y();return e||r("managed_room_context_required","Pause is only available in managed Room context.",U()),R(e,"pause",{reason:i.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(i,e)=>{await l("room.attachment.get",s,async()=>{const a=y(),d=a?i:e;return(!d||!/^ratt_[A-Za-z0-9_-]{1,191}$/.test(d))&&r("invalid_attachment_ref","attachment-id is invalid."),a?R(a,"attachment.get",{attachmentId:d}):m.materialize(A(i),d)})}),c.command("leave <room-ref>").description("Leave one explicitly referenced Room").action(async i=>l("room.leave",s,()=>(p("leave"),t.leave(h(i)))));const g=c.command("invite").description("Read or reset the current Room invitation");g.command("get <room-ref>").description("Get the current invitation and version for one Room").action(i=>l("room.invite.get",s,async()=>{p("invite get");const e=f(await t.getInvite(h(i)),"invalid_room_response"),a=f(e.invite,"invalid_room_response");return typeof a.url!="string"&&r("invalid_room_response","Shennian returned an invalid Room invitation."),{...e,sharing:await u(a.url)}})),g.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",s,async()=>{p("invite reset");const a=f(await t.resetInvite(h(i),D(e.version,"room_invite_version")),"invalid_room_response"),d=f(a.invite,"invalid_room_response");return typeof d.url!="string"&&r("invalid_room_response","Shennian returned an invalid Room invitation."),{...a,sharing:await u(d.url)}}));const _=c.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",s,()=>(p("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",s,()=>(p("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"))))),c.command("search",{hidden:!0}).allowUnknownOption(!0).action(()=>S(s,"command_removed","Room search is not available to Agents.",null)),c.command("agent",{hidden:!0}).allowUnknownOption(!0).action(()=>S(s,"command_removed","Managed Agents are created in the Shennian mobile App.",null))}function K(n,o){return[o?"\u9080\u8BF7\u4F60\u52A0\u5165\u795E\u5FF5\u7FA4\u804A\u300C"+o+"\u300D\u3002":"\u70B9\u51FB\u52A0\u5165\u795E\u5FF5\u7FA4\u804A\uFF0C\u6216\u628A\u672C\u6D88\u606F\u53D1\u7ED9\u4F60\u7684 AI\uFF1A",...o?["\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"+n,"\u5B89\u88C5\uFF1A"+L].join(`
2
+ `)}async function l(n,o,t){try{const m=await t();o.write(`${JSON.stringify({ok:!0,command:n,result:j(m)})}
3
+ `)}catch(m){if(m instanceof N){S(o,m.code,m.message,m.nextAction);return}if(m instanceof z){S(o,m.code,m.message,J(m.code));return}S(o,"room_command_failed","Shennian Room command failed.",null)}}function j(n){if(Array.isArray(n))return n.map(j);if(!n||typeof n!="object")return n;const o={};for(const[t,m]of Object.entries(n))t==="launchUrl"||t==="access"||(o[t]=j(m));return o}function y(){return process.env[G]?.trim()||null}async function R(n,o,t){const m=process.env[F]?.trim();if(!m||!I.isAbsolute(m))throw new z("managed_room_context_unavailable","Managed Room IPC context is unavailable.");return C({method:"managed-room.execute",params:{context:n,action:o,...t},paths:V(m),timeoutMs:o==="send"||o==="attachment.get"?18e4:15e3})}function p(n){y()&&r("managed_room_action_forbidden",`${n} is not available in managed Room context.`)}function A(n){return n||r("room_ref_required","Use an official Room URL returned by Shennian.",U()),h(n)}function h(n){return P(n,"stable").roomId}function P(n,o){let t;const m=o==="invite"?"invalid_invite_url":"invalid_room_ref",s=o==="invite"?"Use an official Shennian Room invitation URL.":"Use an official Room URL returned by Shennian.";try{t=new URL(n)}catch{r(m,s)}const v=t.pathname.match(/^\/spaces\/([A-Za-z0-9_-]{1,191})$/),u=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(c=>c!=="invite")||o==="stable"&&u!=null||o==="invite"&&(u==null||!/^[A-Za-z0-9_-]{32,256}$/.test(u)))&&r(m,s),{roomId:v[1],...u?{inviteToken:u}:{}}}function O(n){return/^(0|[1-9]\d{0,19})$/.test(n)}function k(n){const o=new Date(n);return(Number.isNaN(o.getTime())||o.toISOString()!==n)&&r("invalid_room_version","Use the exact version returned by room get or room list."),n}function D(n,o){/^[1-9]\d*$/.test(n)||r(o,"Version must be a positive integer.");const t=Number(n);return Number.isSafeInteger(t)||r(o,"Version must be a positive integer."),t}function X(n,o){return[...o,n]}function Y(n){return n.includes(`
4
+ `)||!n.includes("\\n\\n")?n:n.replace(/(?<!\\)\\n/g,`
5
+ `)}async function ee(n,o,t){const m=I.resolve(t);let s;try{s=await M.promises.lstat(m)}catch{r("file_not_found",`File does not exist: ${t}`)}(!s.isFile()||s.isSymbolicLink())&&r("invalid_file",`Not a regular file: ${t}`),(s.size<1||s.size>100*1024*1024)&&r("invalid_file_size","Each file must be between 1 byte and 100 MB.");const v=await M.promises.readFile(m),u=x.createHash("sha256").update(v).digest("hex"),c=ne(m),q=await n.initializeAttachment(o,{clientUploadId:`cli:${x.randomUUID()}`,name:I.basename(m),mediaKind:Q(I.basename(m),c),mimeType:c,byteSize:s.size,sha256:u}),g=f(q.attachment,"invalid_attachment_response"),_=f(q.upload,"invalid_attachment_response");(typeof g.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."),te(_.url),await n.upload(_.url,i,v),await n.completeAttachment(o,g.id),g.id}function ne(n){const o=I.extname(n).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"}[o]??"application/octet-stream"}function oe(n,o,t){return Array.isArray(n.messages)||r("invalid_server_response","Shennian returned invalid messages."),{messages:n.messages.map(m=>{const s=f(m,"invalid_server_response"),v=Array.isArray(s.attachments)?s.attachments:[];return{...s,attachments:v.map(u=>{const c=f(u,"invalid_server_response");return(typeof c.attachmentId!="string"||typeof c.name!="string"||typeof c.mimeType!="string"||typeof c.byteSize!="string"||typeof c.sha256!="string")&&r("invalid_server_response","Shennian returned invalid attachment metadata."),{...c,...t.lookup(o,c)}})}})}}function f(n,o){return(!n||typeof n!="object"||Array.isArray(n))&&r(o,"Invalid structured response."),n}function te(n){let o;try{o=new URL(n)}catch{r("invalid_attachment_response","Invalid upload URL.")}const t=o.protocol==="http:"&&(o.hostname==="localhost"||o.hostname==="127.0.0.1");(o.protocol!=="https:"&&!t||o.username||o.password||o.hash)&&r("invalid_attachment_response","Invalid upload URL.")}function r(n,o,t=null){throw new N(n,o,t)}function S(n,o,t,m){n.write(`${JSON.stringify({ok:!1,error:o,message:t,nextAction:m})}
6
+ `),n.fail()}export{_e as registerRoomCommand};
@@ -1,4 +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})}
1
+ import{SupportClient as d,SupportClientError as p}from"../support/support-client.js";import{parseWaitTimeout as f,waitForMessages as l}from"../room/wait-for-messages.js";const _=2e3,b=[/\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],h={write:e=>process.stdout.write(e),fail:()=>{process.exitCode=1}};function w(e,t={}){const i=t.client??new d,r=t.io??h,a=e.command("support").description("Contact Shennian support through the official Room after pairing");a.command("status").description("Check official support Room membership without joining").action(()=>u("support.status",r,()=>i.status())),a.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(o=>u("support.ask",r,()=>i.ask(S(o.text)))),a.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(o=>u("support.read",r,()=>{o.before&&o.after&&n("ambiguous_sequence_cursor","--before and --after are mutually exclusive."),o.before&&!c(o.before)&&n("invalid_sequence","--before sequence is invalid."),o.after&&!c(o.after)&&n("invalid_sequence","--after sequence is invalid.");const s=Number(o.limit);return(!Number.isInteger(s)||s<1||s>50)&&n("invalid_limit","--limit must be between 1 and 50."),i.read({before:o.before,after:o.after,limit:s})})),a.command("wait").description("Wait up to 60 seconds for new messages after an exact sequence").requiredOption("--after <sequence>","Sequence returned by support ask or read").option("--timeout <seconds>","Wait for 1 to 60 seconds","60").action(o=>u("support.wait",r,()=>{c(o.after)||n("invalid_sequence","--after sequence is invalid.");let s;try{s=f(o.timeout)}catch{n("invalid_wait_timeout","--timeout must be between 1 and 60 seconds.")}return l(m=>i.read({after:m,limit:50}),o.after,s)}))}async function u(e,t,i){try{const r=await i();t.write(`${JSON.stringify({ok:!0,command:e,result:r})}
2
+ `)}catch(r){if(r instanceof p){const a=r.nextAction??q(r.code);t.write(`${JSON.stringify({ok:!1,error:r.code,message:r.message,nextAction:a})}
3
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};
4
+ `),t.fail()}}function S(e){const t=e.trim();return t||n("support_text_required","Support text is required."),t.length>_&&n("support_text_too_long","Support text must be at most 2000 characters."),b.some(i=>i.test(t))&&n("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 q(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 c(e){return/^(0|[1-9]\d{0,19})$/.test(e)}function n(e,t,i=null){throw new p(e,t,i)}export{w as registerSupportCommand};
package/dist/src/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import{registerScheduleCommand as Se}from"./commands/schedule.js";import{resolvePersonalScheduleContext as ye}from"./schedules/personal-context.js";import{executeServerSchedule as oe,deliverServerSchedule as we}from"./schedules/server-client.js";import{Command as Ce}from"commander";import E from"chalk";import Re from"node:os";import G from"node:fs";import{loadConfig as p,saveConfig as be,getConfigPath as ke,getShennianDir as Pe,resolveShennianPath as W,normalizeAutoUpgradePolicy as Ae}from"./config/index.js";import{CliRelayClient as Ee}from"./relay/client.js";import{collectKnownLogSecrets as ne,installCliLogRedaction as Te,sanitizeCliLogValue as De}from"./log-redaction.js";import{registerPairCommand as Ue,runSmartStart as Me}from"./commands/pair.js";import{registerLogoutCommand as _e}from"./commands/logout.js";import{clearDaemonPidIfOwner as te,createDaemonInstanceId as Ne,clearDaemonLauncher as re,findRunningDaemonProcessIds as xe,isRemoteAccessDisabled as ae,prepareRemoteAccessPause as Oe,stopPreparedRemoteAccessService as $e,isIsolatedDaemonE2eInstance as Ve,getDaemonStatus as ie,registerDaemonCommand as Le,writeDaemonPid as se,writeDaemonLauncher as ce}from"./commands/daemon.js";import{registerRuntimeCommand as je}from"./commands/runtime.js";import{registerAdapterCommand as He}from"./commands/adapter.js";import{registerAgentCommand as Be}from"./commands/agent.js";import{registerManagerCommand as Fe}from"./commands/manager.js";import{registerWorkerCommand as Ge}from"./commands/worker.js";import{handleUpgradeStart as We,registerUpgradeCommand as ze}from"./commands/upgrade.js";import{registerToolsCommand as Je}from"./commands/tools.js";import{registerQueueCommand as Ke}from"./commands/queue.js";import{registerSkillCommand as qe}from"./commands/skill.js";import{registerIntegrationCommand as Qe}from"./commands/integration.js";import{registerRoomCommand as Ye}from"./commands/room.js";import{registerSupportCommand as Ze}from"./commands/support.js";import{SessionManager as Xe}from"./session/manager.js";import{createTrustedProjectFsRequestResolver as eo}from"./session/handlers/project-fs.js";import{SERVERS as z,regionToUrl as oo,urlToRegion as no}from"./region.js";import{clearUpgradeAttempt as to,getCurrentVersion as de,handleStartupCrashCheck as ro,readUpgradeAttempt as ao}from"./upgrade/engine.js";import{AutoUpgradeScheduler as io}from"./upgrade/scheduler.js";import{ensureUpdaterInstalled as so}from"./upgrade/updater-bootstrap.js";import{launchUpdater as co,markUpdaterHealthy as lo}from"./upgrade/updater-launcher.js";import{detectAgents as mo}from"./agents/detect.js";import{augmentProcessPath as le}from"./env-path.js";import{acquireDaemonInstanceLock as po}from"./commands/daemon-instance-lock.js";le();const O=de();import{getCachedAgentInfos as $,resolveAgentInfos as go}from"./agents/model-registry.js";import{initCliLogReporter as uo,reportLog as T,reportUpgradeAggregateMetric as fo}from"./log-reporter.js";import{isNativeFusionEnabled as ho}from"./native-fusion/config.js";import{NativeSessionFusionService as vo}from"./native-fusion/service.js";import{startDaemonLogRetention as Io}from"./daemon-log.js";import{startDaemonIpcServer as me}from"./daemon-ipc/server.js";import{resolveDaemonIpcPaths as So,resolveManagedRoomIpcPaths as yo}from"./daemon-ipc/paths.js";import{createDaemonControlHandlers as wo}from"./daemon-ipc/control.js";import{createAgentReadinessHandlers as Co}from"./daemon-ipc/agent-readiness.js";import{scanMachineAgents as Ro}from"./session/handlers/agents.js";import{validateAgentWorkspaceConfiguration as bo}from"./session/handlers/agent-workspace-validation.js";import{DAEMON_IPC_VERSION as ko}from"./daemon-ipc/protocol.js";import{RoomSessionRegistry as Po}from"./room/session-registry.js";import{HttpRoomBindingClient as Ao}from"./room/room-binding-client.js";import{TrustedManagedRoomBinder as Eo}from"./room/managed-room-binding.js";import{RoomHostSessionObservationStore as To}from"./room/host-session-observations.js";import{RoomMirrorSyncService as Do}from"./room/mirror-sync-service.js";import{handleLiveRoomDelivery as Uo}from"./room/live-delivery.js";import{NativeSourceDeletionStore as Mo}from"./native-fusion/native-source-deletions.js";import{matchesRoomOwner as _o,isCurrentRegistration as No}from"./room/registration-authority.js";import{RoomActivationService as xo,resolveManagedAgentPolicy as Oo}from"./room/activation-service.js";import{RoomActivationAudit as $o}from"./room/activation-audit.js";import{RoomAttachmentCache as Vo}from"./room/attachment-cache.js";import{RoomAttachmentUploader as Lo}from"./room/attachment-upload.js";import{ManagedRoomContextAuthority as jo}from"./room/managed-context.js";import{createManagedRoomExecutor as Ho}from"./room/managed-room-ipc.js";import{SESSION_SYNC_PROTOCOL_VERSION as pe,SESSION_SYNC_ADVERTISED_CAPABILITIES as ge}from"@shennian/wire";import{getDaemonInstallationId as Bo}from"./session/store.js";import{OWNER_DIRECT_TURN_OPTIONS as Fo}from"./session/queue-helpers.js";import{synchronizeMachinePersonalAuthority as Go}from"./personal-sync/machine-authority.js";import{CLI_SESSION_SYNC_DATA_EPOCH as ue,ensurePersonalDataEpoch as Wo}from"./personal-sync/epoch.js";import{DeletionSyncRetryWaitError as zo,DeletionSyncTerminalError as Jo}from"./session/deletion-sync-state.js";const fe=Pe(),Ko=W("daemon.pid"),qo=W("daemon.instance.lock");function Qo(){try{const a=G.readFileSync(Ko,"utf-8").trim();if(a.startsWith("{")){const g=JSON.parse(a),c=Number(g.pid);return Number.isInteger(c)&&c>0?c:null}const s=parseInt(a,10);return Number.isInteger(s)&&s>0?s:null}catch{return null}}function Yo(a){return a.replace(/^https:\/\//,"wss://").replace(/^http:\/\//,"ws://")}function J(){process.stdout.write(`${JSON.stringify({ok:!1,error:"integration_command_retired",message:"Room MCP and Hooks were retired. Use the Shennian Skill and CLI.",nextAction:{type:"open_install_guide",url:"https://shennian.net/install.md"}})}
2
- `),process.exitCode=1}function Zo(a){const s=[`trigger=${a.trigger??"unknown"}`,`phase=${a.phase}`,`code=${a.code??"unknown"}`,`attempt=${a.reconnectAttempt}`];return a.reason&&s.push(`reason=${a.reason}`),a.error&&s.push(`error=${a.error}`),s.join(" ")}const i=new Ce;i.name("shennian").description("Shennian \u2014 AI Agent Control Plane").version(O),i.enablePositionalOptions(),Qe(i),Ye(i),Ze(i),Se(i),i.option("--api <url>","Server URL override").option("--name <name>","Machine name",Re.hostname()).action(async a=>{const s=p(),g=a.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??s.serverUrl??void 0;await Me(g,a.name)}),i.command("run-service",{hidden:!0}).description("(internal) Connect to relay server, called by the background service").option("--api <url>","Server URL override").action(async a=>{const s=W("env.json");try{const e=JSON.parse(G.readFileSync(s,"utf-8"));for(const[n,o]of Object.entries(e))process.env[n]||(process.env[n]=o);le()}catch{}Te(ne(process.env,p()));const g=Io();ae()&&(console.log(`[${new Date().toISOString()}] remote access disabled, service start skipped`),clearInterval(g),process.exit(0));const c=Ne(),w=po({lockPath:qo,instanceId:c});w.acquired||(console.log(`[${new Date().toISOString()}] daemon already running`+(w.ownerPid?` (PID ${w.ownerPid})`:"")+", skipping duplicate start"),clearInterval(g),process.exit(0));try{const e=Qo();if(e&&e!==process.pid)try{process.kill(e,0),console.log(`[${new Date().toISOString()}] daemon already running (PID ${e}), skipping duplicate start`),w.release(),process.exit(0)}catch{}}catch{}const K=Ve(process.env,fe)?[]:xe(process.pid);K.length>0&&(console.log(`[${new Date().toISOString()}] daemon already running (PID ${K[0]}), skipping duplicate start`),w.release(),process.exit(0)),se(process.pid,c,{version:O}),ce(process.pid,void 0,c),process.on("exit",()=>{te(process.pid,c),re(c)}),process.on("exit",w.release);let V=null;try{V=so()}catch(e){console.error(`[upgrade] updater bootstrap unavailable: ${e instanceof Error?e.message:String(e)}`)}const D=await ro();if(D){V||(console.error("[upgrade] Crash-loop rollback blocked: updater bootstrap unavailable"),process.exit(1));try{co({fromVersion:O,targetVersion:D.from,oldDaemonPid:process.pid,operation:"rollback",rollbackJobId:D.jobId},{readiness:V}),console.error(`[upgrade] New version crashed ${D.attemptCount} times; external rollback scheduled.`),process.stdin.resume();return}catch(e){console.error(`[upgrade] Crash-loop rollback could not start: ${e instanceof Error?e.message:String(e)}`),process.exit(1)}}try{Wo()}catch(e){console.error(`[personal-sync] data epoch initialization failed: ${e instanceof Error?e.message:String(e)}`),process.exitCode=1;return}const m=p(),u=a.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??m.serverUrl??z.cn.url,h=de(),l=await mo({serverUrl:u,authToken:m.machineToken??m.accessToken}),C=l.map(e=>e.type),L=$(l),f=new Po,U=new To,R=new jo(f),S=new Ao(u),ve=new Vo({actionClient:S}),Ie=new Lo({actionClient:S});let y=null,r;const q=Ho({authority:R,actionClient:S,attachmentCache:ve,attachmentUploader:Ie,observations:U,getMachineCredentials:()=>({machineToken:p().machineToken}),managerExecutor:e=>r.executeManagedManagerAction(e),onSendOutcome:e=>r.noteManagedRoomSendOutcome(e),getActiveRunId:(e,n)=>r.getManagedRoomActiveRunId(e,n),scheduleExecutor:(e,n,o)=>oe({serverUrl:u,machineToken:p().machineToken,sessionId:e,action:n,args:o}),onPaused:({registryKey:e,sessionId:n})=>{const t=f.getByRegistryKey(e)?.binding;t&&f.setBinding({registryKey:e,bindingId:t.bindingId,roomId:t.roomId,agentIdentityId:t.agentIdentityId,agentMembershipId:t.agentMembershipId,status:"paused",activationMode:"paused"}),R.revokeSession(e),r?.discardPendingManagedRoomMessages(n)}});let j=null,H=null,B=null;const b=await me({paths:So(),handlers:{...wo({daemonVersion:h,protocolVersion:ko,getStatus:()=>ie(),getAgents:()=>l,completeAgentAuthentication:e=>r.resumePendingAcpAuthentication(e),bindManagedRoom:e=>r.bindManagedRoomFromLocal(e),startUpgrade:async e=>{if(!j)throw new Error("upgrade_unavailable");return j(e)},prepareExternalUpgrade:e=>{if(!H)throw new Error("external_upgrade_unavailable");return H(e)},cancelExternalUpgrade:()=>{if(!B)throw new Error("external_upgrade_unavailable");return B()}}),...Co({getStatus:()=>ie(),getSnapshot:()=>$(l).map(e=>{const n=l.find(o=>o.type===e.type);return{...e,runtimePlatform:n?.runtimePlatform,runtimeIsWsl:n?.runtimeIsWsl}}),discover:async e=>{if(!r)throw new Error("daemon_starting");const n=await Ro(!0,e);if(e){const t=l.filter(I=>I.type!==e);l.splice(0,l.length,...t,...n.detectedAgents)}else l.splice(0,l.length,...n.detectedAgents);const o=$(l);return d.sendEvent({type:"event",event:"machine.agents",payload:{agentList:l.map(t=>t.type),agents:o}}),n.agents.map(t=>{const I=n.detectedAgents.find(F=>F.type===t.type);return{...t,runtimePlatform:I?.runtimePlatform,runtimeIsWsl:I?.runtimeIsWsl}})},validate:async e=>{if(!r)throw new Error("daemon_starting");return bo(e,{ignoreNativeSession:(n,o)=>P?.ignoreNativeSource(n,o)})}})}});let k;try{k=await me({paths:yo(),handlers:{"managed-room.execute":e=>q(e),"schedule.personal":async e=>{const n=e,o=ye(n?.context);if(f.getByNianSessionId(o)?.binding)throw new Error("schedule_personal_context_required");return await oe({serverUrl:u,machineToken:p().machineToken,sessionId:o,action:n.operation,args:n})}}})}catch(e){throw await b.close(),e}let Q=!1;const M=()=>{Q||(Q=!0,Promise.allSettled([b.close(),k.close()]).finally(()=>process.exit(0)))};if(process.once("SIGINT",M),process.once("SIGTERM",M),!m.machineToken){console.error('Shennian CLI is not paired. Run "shennian pair --browser" or "shennian pair --token" first.'),await Promise.allSettled([b.close(),k.close()]),process.exitCode=1;return}process.removeListener("SIGINT",M),process.removeListener("SIGTERM",M);const Y=`${Yo(u)}/relay/machine`,_=m.machineToken;if(!_)return;try{await Go({serverUrl:u,machineToken:_,machineId:m.machineId})}catch(e){console.error(`[personal-sync] machine authority verification failed: ${e instanceof Error?e.message:String(e)}`),await Promise.allSettled([b.close(),k.close()]),process.exit(1)}m.machineId&&uo(u,m.machineId),console.log(`[${new Date().toISOString()}] Connecting to ${Y}... (v${h}) agents: ${C.join(",")}`),T({level:"info",wsEvent:"daemon.start",metadata:{version:h,agents:C}});let P=null,A=null,N=0,v=ao();const Z=Ae(m.autoUpgrade),d=new Ee({serverUrl:Y,machineToken:_,cliVersion:h,agentList:C,autoUpgradePolicy:Z,sessionSyncProtocolVersion:pe,sessionSyncCapabilities:ge,daemonInstallationId:Bo(),dataEpoch:ue,updaterProtocolVersion:1,onConnected:()=>{console.log(`[${new Date().toISOString()}] \u2713 Connected`),T({level:"info",wsEvent:"daemon.connected"}),L.some(o=>o.models.length>0)&&d.sendEvent({type:"event",event:"machine.agents",payload:{agentList:C,agents:L}}),go(l,{serverUrl:m.serverUrl??u,authToken:m.machineToken??m.accessToken}).then(o=>{JSON.stringify(o)!==JSON.stringify(L)&&d.sendEvent({type:"event",event:"machine.agents",payload:{agentList:C,agents:o}})}).catch(()=>{}),v&&d.sendEvent({type:"event",event:"upgrade",payload:{state:"recovering",machineId:"self",from:v.from,to:v.to,...v.jobId?{jobId:v.jobId}:{},activeRunCount:r.getPendingRecoveryCount()}}),x.start(),A?.start();const e=++N,n=async()=>{try{if(await r.synchronizeSessionDeletions(),await r.recoverInterruptedRuns(),v){const o=v;v=null,o.jobId&&lo(o.jobId,o.to),to(),console.log(`[${new Date().toISOString()}] [upgrade] ${o.operation==="rollback"?"Rolled back":"Healthy"}: ${o.from} \u2192 ${o.to}`),fo({kind:"upgrade",result:o.operation==="rollback"?"rolled_back":"healthy",stage:"terminal"}),d.sendEvent({type:"event",event:"upgrade",payload:{state:o.operation==="rollback"?"rolled_back":"healthy",machineId:"self",from:o.from,to:o.to,...o.jobId?{jobId:o.jobId}:{}}}),o.operation!=="rollback"&&d.sendEvent({type:"event",event:"upgrade",payload:{state:"success",machineId:"self",from:o.from,to:o.to}})}e===N&&d.getState()==="connected"&&P?.handleConnected()}catch(o){if(console.error("[session.deletions] startup catch-up failed",o),e===N&&d.getState()==="connected"){if(o instanceof Jo)return;const t=o instanceof zo?Math.max(1e3,o.retryAfterMs):5e3;setTimeout(()=>{n()},t).unref?.()}}};n()},onDisconnected:e=>{N+=1,r?.revokeProjectFsHandles(),A?.stop(),console.log(`[${new Date().toISOString()}] \u26A0 Disconnected, reconnecting... ${Zo(e)}`),T({level:"warn",wsEvent:"daemon.disconnected",metadata:{code:e.code,reason:e.reason,error:e.error,phase:e.phase,trigger:e.trigger,reconnectAttempt:e.reconnectAttempt}})},onProjectFsCapabilityChanged:e=>{e||r?.revokeProjectFsHandles()},onReq:e=>{if(console.log(`[${new Date().toISOString()}] [req] ${e.method}`),T({level:"info",type:"ws",wsEvent:e.method,wsDirection:"in",traceId:e.traceId}),e.method==="room.message.deliver"){Uo(d,A,e);return}r.handleReq(e)}});P=ho()?new vo(d):null,A=new Do({registry:f,observations:U,actionClient:S,getMachineCredentials:()=>({machineToken:p().machineToken}),isSessionDeleted:e=>new Mo().hasSession(e),onMessages:async(e,n,o,t)=>{await y?.processMessages(e,n,o,t)},onBindingPaused:e=>{e.lifecycleState==="closed"?(R.revokeSession(e.registryKey),r?.retireManagedRoomSession(e.nianSessionId).catch(()=>{console.warn("[room.sync] retired session cleanup failed")})):r?.discardPendingManagedRoomMessages(e.nianSessionId)}});const x=new io({currentVersion:h,machineId:m.machineId??"unknown-machine",serverUrl:m.serverUrl??u,getIdleState:()=>r.getUpgradeIdleState(),sessionSyncProtocolVersion:pe,dataEpoch:ue,advertisedCapabilities:ge,updaterProtocolVersion:1,startUpgrade:async(e,n)=>{const{handleUpgradeStart:o}=await import("./commands/upgrade.js");await o(d,"auto-upgrade",e,{currentVersion:h,prepareForUpgrade:t=>r.prepareForUpgrade(t),cancelUpgradePreparation:()=>r.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()},suppressResponse:!0,waitForCompletion:!0,canHandoff:n?void 0:()=>r.getUpgradeIdleState().idle})}});x.setPolicy(Z),r=new Xe(d,P,h,x,new Eo({registry:f,observations:U,bindingClient:S,getMachineCredentials:()=>{const e=p();return{machineId:e.machineId,machineToken:e.machineToken}},hostProcessIdentity:()=>`daemon:${c}`}),{prepare:()=>Oe(),isDisabled:()=>ae(),shutdownAfterResponse:()=>{setTimeout(()=>{$e(),process.kill(process.pid,"SIGTERM")},100).unref?.()}},e=>R.revokeToken(e),q,null,eo(_),()=>$(l)),j=async e=>{const n=e&&typeof e=="object"&&!Array.isArray(e)?e:{},o=typeof n.targetVersion=="string"?n.targetVersion:void 0,t=n.confirmedMajor===!0;return await We(d,`local-upgrade-${Date.now()}`,o,{currentVersion:h,confirmedMajor:t,prepareForUpgrade:F=>r.prepareForUpgrade(F),cancelUpgradePreparation:()=>r.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()},suppressResponse:!0})},H=e=>{const n=e&&typeof e=="object"&&!Array.isArray(e)?e:{},t=`desktop-${(typeof n.targetVersion=="string"?n.targetVersion:"unknown").replace(/[^A-Za-z0-9._-]/g,"_")}-${Date.now()}`;return{jobId:t,...r.prepareForUpgrade(t)}},B=()=>(r.cancelUpgradePreparation(),{cancelled:!0}),y=new xo({registry:f,observations:U,enqueueConversationBatch:e=>r.enqueueManagedRoomBatch(e),loadContext:async e=>{const n=p().machineToken?.trim(),o=e.binding;if(!n||!o)throw new Error("managed_room_context_unavailable");const t=await S.status({machineToken:n,roomId:o.roomId,bindingId:o.bindingId});if(!No(f,e)||!_o(e,t))throw new Error("managed_room_binding_unavailable");return{roomName:t.room.name,memberCount:t.room.memberCount,members:t.room.members,agentsByIdentityId:t.agent?{[t.agentIdentityId]:t.agent}:{},effectiveModelsBySessionId:t.effectiveModelId!==void 0?{[e.nianSessionId]:t.effectiveModelId}:void 0,agentSystemPromptsByIdentityId:t.agent?{[t.agentIdentityId]:t.agent.systemPrompt??""}:{}}},issueManagedRoomContext:(e,n,o,t)=>{const I=p().machineToken;if(!I)throw new Error("machine_not_authorized");return R.issue({registryKey:e,machineCredential:I,activationCauseMessageId:n,allowedActions:o,propagateWorkerRoomOrigin:t})},audit:new $o,resolveManagedAgentPolicy:e=>Oo({...e,runtime:l.find(n=>n.type===e.registration.runtimeAdapter)})}),r.scheduleHandler=e=>we({serverUrl:u,machineToken:p().machineToken,req:e,client:d,queue:r.getScheduleQueue(),registry:f}),r.getScheduleQueue().validateScheduledTarget=async e=>{if(!e.bindingId||!e.roomId)return"ready";if(f.getByNianSessionId(e.sessionId)?.binding?.bindingId!==e.bindingId)return"ended";const o=await S.status({machineToken:p().machineToken,roomId:e.roomId,bindingId:e.bindingId});return o.status==="active"&&o.room.status==="active"?"ready":"ended"},r.setManagedRoomTurnPreparer(e=>y.prepareConversationTurn(e)),r.setManagedAgentDirectTurnPreparer((e,n)=>y.prepareDirectConversationTurn(e,n)),r.setManagedRecoveryPreparer(async e=>e.source==="managed_direct"?y.prepareDirectConversationTurn(e.sessionId,Fo):e.source==="manager_event"?y.prepareDirectConversationTurn(e.sessionId,{allowedRoomActions:[],managerEvent:!0}):e.source!=="managed_room"||!e.roomContextRegistryKey||!e.activationCauseMessageId||!e.rootCauseMessageId||!e.activationSequence||e.activationHopCount===null?null:y.prepareConversationTurn([{registryKey:e.roomContextRegistryKey,causeMessageId:e.activationCauseMessageId,rootCauseMessageId:e.rootCauseMessageId,sequence:e.activationSequence,hopCount:e.activationHopCount}])),G.mkdirSync(fe,{recursive:!0}),se(process.pid,c,{version:h}),ce(process.pid,void 0,c),d.connect(),process.stdin.resume();let X=!1;const ee=async()=>{if(!X){X=!0,console.log(E.gray(`
3
- Disconnecting...`)),T({level:"info",wsEvent:"daemon.stop"}),clearInterval(g);try{await r.cleanup(),x.stop(),P?.stop(),A?.stop(),d.disconnect(),await Promise.allSettled([b.close(),k.close()]),te(process.pid,c),re(c)}finally{process.exit(0)}}};process.on("SIGINT",()=>{ee()}),process.on("SIGTERM",()=>{ee()})}),i.command("workbuddy-session-hook",{hidden:!0}).description("(retired) WorkBuddy Room Hook").allowUnknownOption(!0).action(()=>J()),i.command("room-session-hook",{hidden:!0}).description("(internal) Authorize the official Room tool for one trusted Agent Session").argument("<runtime>","Trusted runtime adapter").allowUnknownOption(!0).action(()=>J()),i.command("room-tool-server",{hidden:!0}).description("(internal) Run the official Shennian Room MCP tool").allowUnknownOption(!0).action(()=>J());const he=i.command("config").description("View or modify configuration");he.command("show",{isDefault:!0}).description("Show current configuration").action(()=>{console.log(`Config file: ${ke()}`);const a=p(),s=De(a,ne(process.env,a));if(a.serverUrl){const g=no(a.serverUrl);console.log(`Region: ${g} (${z[g].label})`)}console.log(JSON.stringify(s,null,2))}),he.command("set").description("Update a config value").argument("<key>",'Config key (e.g. "server")').argument("<value>",'Config value (e.g. "cn" or "global")').action((a,s)=>{const g=p();if(a==="server"){s!=="cn"&&s!=="global"&&(console.error(E.red('\u2717 Value must be "cn" or "global"')),process.exit(1));const c=s;g.serverUrl=oo(c),be(g),console.log(E.green(`\u2713 Server set to ${z[c].label}`)),console.log(E.yellow(" Restart the background service for this to take effect: shennian stop && shennian start"));return}console.error(E.red(`\u2717 Unknown config key: ${a}. Supported: server`)),process.exit(1)}),Ue(i),_e(i),Le(i),je(i),He(i,{clientVersion:O}),Be(i),Fe(i),Ge(i),ze(i),Je(i),Ke(i),qe(i),i.parse();
1
+ import{registerScheduleCommand as Se}from"./commands/schedule.js";import{resolvePersonalScheduleContext as ye}from"./schedules/personal-context.js";import{executeServerSchedule as oe,deliverServerSchedule as we}from"./schedules/server-client.js";import{Command as Ce}from"commander";import E from"chalk";import Re from"node:os";import G from"node:fs";import{loadConfig as p,saveConfig as be,getConfigPath as ke,getShennianDir as Pe,resolveShennianPath as W,normalizeAutoUpgradePolicy as Ae}from"./config/index.js";import{CliRelayClient as Ee}from"./relay/client.js";import{collectKnownLogSecrets as ne,installCliLogRedaction as Te,sanitizeCliLogValue as De}from"./log-redaction.js";import{registerPairCommand as Ue,runSmartStart as Me}from"./commands/pair.js";import{registerLogoutCommand as _e}from"./commands/logout.js";import{clearDaemonPidIfOwner as te,createDaemonInstanceId as Ne,clearDaemonLauncher as re,findRunningDaemonProcessIds as xe,isRemoteAccessDisabled as ae,prepareRemoteAccessPause as Oe,stopPreparedRemoteAccessService as $e,isIsolatedDaemonE2eInstance as Ve,getDaemonStatus as ie,registerDaemonCommand as Le,writeDaemonPid as se,writeDaemonLauncher as ce}from"./commands/daemon.js";import{registerRuntimeCommand as je}from"./commands/runtime.js";import{registerAdapterCommand as He}from"./commands/adapter.js";import{registerAgentCommand as Be}from"./commands/agent.js";import{registerManagerCommand as Fe}from"./commands/manager.js";import{registerWorkerCommand as Ge}from"./commands/worker.js";import{handleUpgradeStart as We,registerUpgradeCommand as ze}from"./commands/upgrade.js";import{registerToolsCommand as Je}from"./commands/tools.js";import{registerQueueCommand as Ke}from"./commands/queue.js";import{registerSkillCommand as qe}from"./commands/skill.js";import{registerCommandReference as Qe}from"./commands/command-reference.js";import{registerIntegrationCommand as Ye}from"./commands/integration.js";import{registerRoomCommand as Ze}from"./commands/room.js";import{registerSupportCommand as Xe}from"./commands/support.js";import{SessionManager as eo}from"./session/manager.js";import{createTrustedProjectFsRequestResolver as oo}from"./session/handlers/project-fs.js";import{SERVERS as z,regionToUrl as no,urlToRegion as to}from"./region.js";import{clearUpgradeAttempt as ro,getCurrentVersion as de,handleStartupCrashCheck as ao,readUpgradeAttempt as io}from"./upgrade/engine.js";import{AutoUpgradeScheduler as so}from"./upgrade/scheduler.js";import{ensureUpdaterInstalled as co}from"./upgrade/updater-bootstrap.js";import{launchUpdater as lo,markUpdaterHealthy as mo}from"./upgrade/updater-launcher.js";import{detectAgents as po}from"./agents/detect.js";import{augmentProcessPath as le}from"./env-path.js";import{acquireDaemonInstanceLock as go}from"./commands/daemon-instance-lock.js";le();const T=de();import{getCachedAgentInfos as $,resolveAgentInfos as uo}from"./agents/model-registry.js";import{initCliLogReporter as fo,reportLog as D,reportUpgradeAggregateMetric as ho}from"./log-reporter.js";import{isNativeFusionEnabled as vo}from"./native-fusion/config.js";import{NativeSessionFusionService as Io}from"./native-fusion/service.js";import{startDaemonLogRetention as So}from"./daemon-log.js";import{startDaemonIpcServer as me}from"./daemon-ipc/server.js";import{resolveDaemonIpcPaths as yo,resolveManagedRoomIpcPaths as wo}from"./daemon-ipc/paths.js";import{createDaemonControlHandlers as Co}from"./daemon-ipc/control.js";import{createAgentReadinessHandlers as Ro}from"./daemon-ipc/agent-readiness.js";import{scanMachineAgents as bo}from"./session/handlers/agents.js";import{validateAgentWorkspaceConfiguration as ko}from"./session/handlers/agent-workspace-validation.js";import{DAEMON_IPC_VERSION as Po}from"./daemon-ipc/protocol.js";import{RoomSessionRegistry as Ao}from"./room/session-registry.js";import{HttpRoomBindingClient as Eo}from"./room/room-binding-client.js";import{TrustedManagedRoomBinder as To}from"./room/managed-room-binding.js";import{RoomHostSessionObservationStore as Do}from"./room/host-session-observations.js";import{RoomMirrorSyncService as Uo}from"./room/mirror-sync-service.js";import{handleLiveRoomDelivery as Mo}from"./room/live-delivery.js";import{NativeSourceDeletionStore as _o}from"./native-fusion/native-source-deletions.js";import{matchesRoomOwner as No,isCurrentRegistration as xo}from"./room/registration-authority.js";import{RoomActivationService as Oo,resolveManagedAgentPolicy as $o}from"./room/activation-service.js";import{RoomActivationAudit as Vo}from"./room/activation-audit.js";import{RoomAttachmentCache as Lo}from"./room/attachment-cache.js";import{RoomAttachmentUploader as jo}from"./room/attachment-upload.js";import{ManagedRoomContextAuthority as Ho}from"./room/managed-context.js";import{createManagedRoomExecutor as Bo}from"./room/managed-room-ipc.js";import{SESSION_SYNC_PROTOCOL_VERSION as pe,SESSION_SYNC_ADVERTISED_CAPABILITIES as ge}from"@shennian/wire";import{getDaemonInstallationId as Fo}from"./session/store.js";import{OWNER_DIRECT_TURN_OPTIONS as Go}from"./session/queue-helpers.js";import{synchronizeMachinePersonalAuthority as Wo}from"./personal-sync/machine-authority.js";import{CLI_SESSION_SYNC_DATA_EPOCH as ue,ensurePersonalDataEpoch as zo}from"./personal-sync/epoch.js";import{DeletionSyncRetryWaitError as Jo,DeletionSyncTerminalError as Ko}from"./session/deletion-sync-state.js";const fe=Pe(),qo=W("daemon.pid"),Qo=W("daemon.instance.lock");function Yo(){try{const a=G.readFileSync(qo,"utf-8").trim();if(a.startsWith("{")){const g=JSON.parse(a),c=Number(g.pid);return Number.isInteger(c)&&c>0?c:null}const s=parseInt(a,10);return Number.isInteger(s)&&s>0?s:null}catch{return null}}function Zo(a){return a.replace(/^https:\/\//,"wss://").replace(/^http:\/\//,"ws://")}function J(){process.stdout.write(`${JSON.stringify({ok:!1,error:"integration_command_retired",message:"Room MCP and Hooks were retired. Use the Shennian Skill and CLI.",nextAction:{type:"open_install_guide",url:"https://shennian.net/install.md"}})}
2
+ `),process.exitCode=1}function Xo(a){const s=[`trigger=${a.trigger??"unknown"}`,`phase=${a.phase}`,`code=${a.code??"unknown"}`,`attempt=${a.reconnectAttempt}`];return a.reason&&s.push(`reason=${a.reason}`),a.error&&s.push(`error=${a.error}`),s.join(" ")}const i=new Ce;i.name("shennian").description("Shennian \u2014 AI Agent Control Plane").version(T),i.enablePositionalOptions(),Ye(i),Ze(i),Xe(i),Se(i),i.option("--api <url>","Server URL override").option("--name <name>","Machine name",Re.hostname()).action(async a=>{const s=p(),g=a.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??s.serverUrl??void 0;await Me(g,a.name)}),i.command("run-service",{hidden:!0}).description("(internal) Connect to relay server, called by the background service").option("--api <url>","Server URL override").action(async a=>{const s=W("env.json");try{const e=JSON.parse(G.readFileSync(s,"utf-8"));for(const[n,o]of Object.entries(e))process.env[n]||(process.env[n]=o);le()}catch{}Te(ne(process.env,p()));const g=So();ae()&&(console.log(`[${new Date().toISOString()}] remote access disabled, service start skipped`),clearInterval(g),process.exit(0));const c=Ne(),w=go({lockPath:Qo,instanceId:c});w.acquired||(console.log(`[${new Date().toISOString()}] daemon already running`+(w.ownerPid?` (PID ${w.ownerPid})`:"")+", skipping duplicate start"),clearInterval(g),process.exit(0));try{const e=Yo();if(e&&e!==process.pid)try{process.kill(e,0),console.log(`[${new Date().toISOString()}] daemon already running (PID ${e}), skipping duplicate start`),w.release(),process.exit(0)}catch{}}catch{}const K=Ve(process.env,fe)?[]:xe(process.pid);K.length>0&&(console.log(`[${new Date().toISOString()}] daemon already running (PID ${K[0]}), skipping duplicate start`),w.release(),process.exit(0)),se(process.pid,c,{version:T}),ce(process.pid,void 0,c),process.on("exit",()=>{te(process.pid,c),re(c)}),process.on("exit",w.release);let V=null;try{V=co()}catch(e){console.error(`[upgrade] updater bootstrap unavailable: ${e instanceof Error?e.message:String(e)}`)}const U=await ao();if(U){V||(console.error("[upgrade] Crash-loop rollback blocked: updater bootstrap unavailable"),process.exit(1));try{lo({fromVersion:T,targetVersion:U.from,oldDaemonPid:process.pid,operation:"rollback",rollbackJobId:U.jobId},{readiness:V}),console.error(`[upgrade] New version crashed ${U.attemptCount} times; external rollback scheduled.`),process.stdin.resume();return}catch(e){console.error(`[upgrade] Crash-loop rollback could not start: ${e instanceof Error?e.message:String(e)}`),process.exit(1)}}try{zo()}catch(e){console.error(`[personal-sync] data epoch initialization failed: ${e instanceof Error?e.message:String(e)}`),process.exitCode=1;return}const m=p(),u=a.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??m.serverUrl??z.cn.url,h=de(),l=await po({serverUrl:u,authToken:m.machineToken??m.accessToken}),C=l.map(e=>e.type),L=$(l),f=new Ao,M=new Do,R=new Ho(f),S=new Eo(u),ve=new Lo({actionClient:S}),Ie=new jo({actionClient:S});let y=null,r;const q=Bo({authority:R,actionClient:S,attachmentCache:ve,attachmentUploader:Ie,observations:M,getMachineCredentials:()=>({machineToken:p().machineToken}),managerExecutor:e=>r.executeManagedManagerAction(e),onSendOutcome:e=>r.noteManagedRoomSendOutcome(e),getActiveRunId:(e,n)=>r.getManagedRoomActiveRunId(e,n),scheduleExecutor:(e,n,o)=>oe({serverUrl:u,machineToken:p().machineToken,sessionId:e,action:n,args:o}),onPaused:({registryKey:e,sessionId:n})=>{const t=f.getByRegistryKey(e)?.binding;t&&f.setBinding({registryKey:e,bindingId:t.bindingId,roomId:t.roomId,agentIdentityId:t.agentIdentityId,agentMembershipId:t.agentMembershipId,status:"paused",activationMode:"paused"}),R.revokeSession(e),r?.discardPendingManagedRoomMessages(n)}});let j=null,H=null,B=null;const b=await me({paths:yo(),handlers:{...Co({daemonVersion:h,protocolVersion:Po,getStatus:()=>ie(),getAgents:()=>l,completeAgentAuthentication:e=>r.resumePendingAcpAuthentication(e),bindManagedRoom:e=>r.bindManagedRoomFromLocal(e),startUpgrade:async e=>{if(!j)throw new Error("upgrade_unavailable");return j(e)},prepareExternalUpgrade:e=>{if(!H)throw new Error("external_upgrade_unavailable");return H(e)},cancelExternalUpgrade:()=>{if(!B)throw new Error("external_upgrade_unavailable");return B()}}),...Ro({getStatus:()=>ie(),getSnapshot:()=>$(l).map(e=>{const n=l.find(o=>o.type===e.type);return{...e,runtimePlatform:n?.runtimePlatform,runtimeIsWsl:n?.runtimeIsWsl}}),discover:async e=>{if(!r)throw new Error("daemon_starting");const n=await bo(!0,e);if(e){const t=l.filter(I=>I.type!==e);l.splice(0,l.length,...t,...n.detectedAgents)}else l.splice(0,l.length,...n.detectedAgents);const o=$(l);return d.sendEvent({type:"event",event:"machine.agents",payload:{agentList:l.map(t=>t.type),agents:o}}),n.agents.map(t=>{const I=n.detectedAgents.find(F=>F.type===t.type);return{...t,runtimePlatform:I?.runtimePlatform,runtimeIsWsl:I?.runtimeIsWsl}})},validate:async e=>{if(!r)throw new Error("daemon_starting");return ko(e,{ignoreNativeSession:(n,o)=>P?.ignoreNativeSource(n,o)})}})}});let k;try{k=await me({paths:wo(),handlers:{"managed-room.execute":e=>q(e),"schedule.personal":async e=>{const n=e,o=ye(n?.context);if(f.getByNianSessionId(o)?.binding)throw new Error("schedule_personal_context_required");return await oe({serverUrl:u,machineToken:p().machineToken,sessionId:o,action:n.operation,args:n})}}})}catch(e){throw await b.close(),e}let Q=!1;const _=()=>{Q||(Q=!0,Promise.allSettled([b.close(),k.close()]).finally(()=>process.exit(0)))};if(process.once("SIGINT",_),process.once("SIGTERM",_),!m.machineToken){console.error('Shennian CLI is not paired. Run "shennian pair --browser" or "shennian pair --token" first.'),await Promise.allSettled([b.close(),k.close()]),process.exitCode=1;return}process.removeListener("SIGINT",_),process.removeListener("SIGTERM",_);const Y=`${Zo(u)}/relay/machine`,N=m.machineToken;if(!N)return;try{await Wo({serverUrl:u,machineToken:N,machineId:m.machineId})}catch(e){console.error(`[personal-sync] machine authority verification failed: ${e instanceof Error?e.message:String(e)}`),await Promise.allSettled([b.close(),k.close()]),process.exit(1)}m.machineId&&fo(u,m.machineId),console.log(`[${new Date().toISOString()}] Connecting to ${Y}... (v${h}) agents: ${C.join(",")}`),D({level:"info",wsEvent:"daemon.start",metadata:{version:h,agents:C}});let P=null,A=null,x=0,v=io();const Z=Ae(m.autoUpgrade),d=new Ee({serverUrl:Y,machineToken:N,cliVersion:h,agentList:C,autoUpgradePolicy:Z,sessionSyncProtocolVersion:pe,sessionSyncCapabilities:ge,daemonInstallationId:Fo(),dataEpoch:ue,updaterProtocolVersion:1,onConnected:()=>{console.log(`[${new Date().toISOString()}] \u2713 Connected`),D({level:"info",wsEvent:"daemon.connected"}),L.some(o=>o.models.length>0)&&d.sendEvent({type:"event",event:"machine.agents",payload:{agentList:C,agents:L}}),uo(l,{serverUrl:m.serverUrl??u,authToken:m.machineToken??m.accessToken}).then(o=>{JSON.stringify(o)!==JSON.stringify(L)&&d.sendEvent({type:"event",event:"machine.agents",payload:{agentList:C,agents:o}})}).catch(()=>{}),v&&d.sendEvent({type:"event",event:"upgrade",payload:{state:"recovering",machineId:"self",from:v.from,to:v.to,...v.jobId?{jobId:v.jobId}:{},activeRunCount:r.getPendingRecoveryCount()}}),O.start(),A?.start();const e=++x,n=async()=>{try{if(await r.synchronizeSessionDeletions(),await r.recoverInterruptedRuns(),v){const o=v;v=null,o.jobId&&mo(o.jobId,o.to),ro(),console.log(`[${new Date().toISOString()}] [upgrade] ${o.operation==="rollback"?"Rolled back":"Healthy"}: ${o.from} \u2192 ${o.to}`),ho({kind:"upgrade",result:o.operation==="rollback"?"rolled_back":"healthy",stage:"terminal"}),d.sendEvent({type:"event",event:"upgrade",payload:{state:o.operation==="rollback"?"rolled_back":"healthy",machineId:"self",from:o.from,to:o.to,...o.jobId?{jobId:o.jobId}:{}}}),o.operation!=="rollback"&&d.sendEvent({type:"event",event:"upgrade",payload:{state:"success",machineId:"self",from:o.from,to:o.to}})}e===x&&d.getState()==="connected"&&P?.handleConnected()}catch(o){if(console.error("[session.deletions] startup catch-up failed",o),e===x&&d.getState()==="connected"){if(o instanceof Ko)return;const t=o instanceof Jo?Math.max(1e3,o.retryAfterMs):5e3;setTimeout(()=>{n()},t).unref?.()}}};n()},onDisconnected:e=>{x+=1,r?.revokeProjectFsHandles(),A?.stop(),console.log(`[${new Date().toISOString()}] \u26A0 Disconnected, reconnecting... ${Xo(e)}`),D({level:"warn",wsEvent:"daemon.disconnected",metadata:{code:e.code,reason:e.reason,error:e.error,phase:e.phase,trigger:e.trigger,reconnectAttempt:e.reconnectAttempt}})},onProjectFsCapabilityChanged:e=>{e||r?.revokeProjectFsHandles()},onReq:e=>{if(console.log(`[${new Date().toISOString()}] [req] ${e.method}`),D({level:"info",type:"ws",wsEvent:e.method,wsDirection:"in",traceId:e.traceId}),e.method==="room.message.deliver"){Mo(d,A,e);return}r.handleReq(e)}});P=vo()?new Io(d):null,A=new Uo({registry:f,observations:M,actionClient:S,getMachineCredentials:()=>({machineToken:p().machineToken}),isSessionDeleted:e=>new _o().hasSession(e),onMessages:async(e,n,o,t)=>{await y?.processMessages(e,n,o,t)},onBindingPaused:e=>{e.lifecycleState==="closed"?(R.revokeSession(e.registryKey),r?.retireManagedRoomSession(e.nianSessionId).catch(()=>{console.warn("[room.sync] retired session cleanup failed")})):r?.discardPendingManagedRoomMessages(e.nianSessionId)}});const O=new so({currentVersion:h,machineId:m.machineId??"unknown-machine",serverUrl:m.serverUrl??u,getIdleState:()=>r.getUpgradeIdleState(),sessionSyncProtocolVersion:pe,dataEpoch:ue,advertisedCapabilities:ge,updaterProtocolVersion:1,startUpgrade:async(e,n)=>{const{handleUpgradeStart:o}=await import("./commands/upgrade.js");await o(d,"auto-upgrade",e,{currentVersion:h,prepareForUpgrade:t=>r.prepareForUpgrade(t),cancelUpgradePreparation:()=>r.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()},suppressResponse:!0,waitForCompletion:!0,canHandoff:n?void 0:()=>r.getUpgradeIdleState().idle})}});O.setPolicy(Z),r=new eo(d,P,h,O,new To({registry:f,observations:M,bindingClient:S,getMachineCredentials:()=>{const e=p();return{machineId:e.machineId,machineToken:e.machineToken}},hostProcessIdentity:()=>`daemon:${c}`}),{prepare:()=>Oe(),isDisabled:()=>ae(),shutdownAfterResponse:()=>{setTimeout(()=>{$e(),process.kill(process.pid,"SIGTERM")},100).unref?.()}},e=>R.revokeToken(e),q,null,oo(N),()=>$(l)),j=async e=>{const n=e&&typeof e=="object"&&!Array.isArray(e)?e:{},o=typeof n.targetVersion=="string"?n.targetVersion:void 0,t=n.confirmedMajor===!0;return await We(d,`local-upgrade-${Date.now()}`,o,{currentVersion:h,confirmedMajor:t,prepareForUpgrade:F=>r.prepareForUpgrade(F),cancelUpgradePreparation:()=>r.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()},suppressResponse:!0})},H=e=>{const n=e&&typeof e=="object"&&!Array.isArray(e)?e:{},t=`desktop-${(typeof n.targetVersion=="string"?n.targetVersion:"unknown").replace(/[^A-Za-z0-9._-]/g,"_")}-${Date.now()}`;return{jobId:t,...r.prepareForUpgrade(t)}},B=()=>(r.cancelUpgradePreparation(),{cancelled:!0}),y=new Oo({registry:f,observations:M,enqueueConversationBatch:e=>r.enqueueManagedRoomBatch(e),loadContext:async e=>{const n=p().machineToken?.trim(),o=e.binding;if(!n||!o)throw new Error("managed_room_context_unavailable");const t=await S.status({machineToken:n,roomId:o.roomId,bindingId:o.bindingId});if(!xo(f,e)||!No(e,t))throw new Error("managed_room_binding_unavailable");return{roomName:t.room.name,memberCount:t.room.memberCount,members:t.room.members,agentsByIdentityId:t.agent?{[t.agentIdentityId]:t.agent}:{},effectiveModelsBySessionId:t.effectiveModelId!==void 0?{[e.nianSessionId]:t.effectiveModelId}:void 0,agentSystemPromptsByIdentityId:t.agent?{[t.agentIdentityId]:t.agent.systemPrompt??""}:{}}},issueManagedRoomContext:(e,n,o,t)=>{const I=p().machineToken;if(!I)throw new Error("machine_not_authorized");return R.issue({registryKey:e,machineCredential:I,activationCauseMessageId:n,allowedActions:o,propagateWorkerRoomOrigin:t})},audit:new Vo,resolveManagedAgentPolicy:e=>$o({...e,runtime:l.find(n=>n.type===e.registration.runtimeAdapter)})}),r.scheduleHandler=e=>we({serverUrl:u,machineToken:p().machineToken,req:e,client:d,queue:r.getScheduleQueue(),registry:f}),r.getScheduleQueue().validateScheduledTarget=async e=>{if(!e.bindingId||!e.roomId)return"ready";if(f.getByNianSessionId(e.sessionId)?.binding?.bindingId!==e.bindingId)return"ended";const o=await S.status({machineToken:p().machineToken,roomId:e.roomId,bindingId:e.bindingId});return o.status==="active"&&o.room.status==="active"?"ready":"ended"},r.setManagedRoomTurnPreparer(e=>y.prepareConversationTurn(e)),r.setManagedAgentDirectTurnPreparer((e,n)=>y.prepareDirectConversationTurn(e,n)),r.setManagedRecoveryPreparer(async e=>e.source==="managed_direct"?y.prepareDirectConversationTurn(e.sessionId,Go):e.source==="manager_event"?y.prepareDirectConversationTurn(e.sessionId,{allowedRoomActions:[],managerEvent:!0}):e.source!=="managed_room"||!e.roomContextRegistryKey||!e.activationCauseMessageId||!e.rootCauseMessageId||!e.activationSequence||e.activationHopCount===null?null:y.prepareConversationTurn([{registryKey:e.roomContextRegistryKey,causeMessageId:e.activationCauseMessageId,rootCauseMessageId:e.rootCauseMessageId,sequence:e.activationSequence,hopCount:e.activationHopCount}])),G.mkdirSync(fe,{recursive:!0}),se(process.pid,c,{version:h}),ce(process.pid,void 0,c),d.connect(),process.stdin.resume();let X=!1;const ee=async()=>{if(!X){X=!0,console.log(E.gray(`
3
+ Disconnecting...`)),D({level:"info",wsEvent:"daemon.stop"}),clearInterval(g);try{await r.cleanup(),O.stop(),P?.stop(),A?.stop(),d.disconnect(),await Promise.allSettled([b.close(),k.close()]),te(process.pid,c),re(c)}finally{process.exit(0)}}};process.on("SIGINT",()=>{ee()}),process.on("SIGTERM",()=>{ee()})}),i.command("workbuddy-session-hook",{hidden:!0}).description("(retired) WorkBuddy Room Hook").allowUnknownOption(!0).action(()=>J()),i.command("room-session-hook",{hidden:!0}).description("(internal) Authorize the official Room tool for one trusted Agent Session").argument("<runtime>","Trusted runtime adapter").allowUnknownOption(!0).action(()=>J()),i.command("room-tool-server",{hidden:!0}).description("(internal) Run the official Shennian Room MCP tool").allowUnknownOption(!0).action(()=>J());const he=i.command("config").description("View or modify configuration");he.command("show",{isDefault:!0}).description("Show current configuration").action(()=>{console.log(`Config file: ${ke()}`);const a=p(),s=De(a,ne(process.env,a));if(a.serverUrl){const g=to(a.serverUrl);console.log(`Region: ${g} (${z[g].label})`)}console.log(JSON.stringify(s,null,2))}),he.command("set").description("Update a config value").argument("<key>",'Config key (e.g. "server")').argument("<value>",'Config value (e.g. "cn" or "global")').action((a,s)=>{const g=p();if(a==="server"){s!=="cn"&&s!=="global"&&(console.error(E.red('\u2717 Value must be "cn" or "global"')),process.exit(1));const c=s;g.serverUrl=no(c),be(g),console.log(E.green(`\u2713 Server set to ${z[c].label}`)),console.log(E.yellow(" Restart the background service for this to take effect: shennian stop && shennian start"));return}console.error(E.red(`\u2717 Unknown config key: ${a}. Supported: server`)),process.exit(1)}),Ue(i),_e(i),Le(i),je(i),He(i,{clientVersion:T}),Be(i),Fe(i),Ge(i),ze(i),Je(i),Ke(i),qe(i),Qe(i,T),i.parse();
@@ -0,0 +1 @@
1
+ async function o(s,e,r){const a=Date.now()+r*1e3;for(;;){const t=await s(e);if(!Array.isArray(t.messages))throw new Error("invalid_message_page");if(t.messages.length>0)return{messages:t.messages,timedOut:!1,after:e};const i=a-Date.now();if(i<=0)return{messages:[],timedOut:!0,after:e};await new Promise(n=>setTimeout(n,Math.min(2e3,i)))}}function m(s){const e=Number(s);if(!Number.isInteger(e)||e<1||e>60)throw new Error("invalid_wait_timeout");return e}export{m as parseWaitTimeout,o as waitForMessages};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shennian",
3
- "version": "0.5.6",
3
+ "version": "0.5.7",
4
4
  "description": "Shennian \u2014 AI Agent Control Plane CLI",
5
5
  "type": "module",
6
6
  "bin": {