shennian 0.3.40 → 0.3.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -24,7 +24,8 @@
24
24
  | `shennian logs -n 100` | 查看最近日志 |
25
25
  | `shennian config` | 查看本地配置 |
26
26
  | `shennian config set server cn\|global` | 切换国内 / 海外中继节点 |
27
- | `shennian agent add/list/remove` | 管理自定义 Agent |
27
+ | `shennian room create/join/status/read/send/attachment get/leave` | 创建或参与明确的神念群聊 |
28
+ | `shennian runtime add/list/remove` | 管理自定义 Agent Runtime |
28
29
  | `shennian upgrade` | 升级 CLI |
29
30
 
30
31
  `start`、`stop`、`status` 可选加 `--json`。`logs` 输出日志文本,不支持 `--json`。
@@ -21,8 +21,8 @@
21
21
  },
22
22
  {
23
23
  "file": "src/agents/claude.js",
24
- "beforeBytes": 11715,
25
- "afterBytes": 6026
24
+ "beforeBytes": 11959,
25
+ "afterBytes": 6164
26
26
  },
27
27
  {
28
28
  "file": "src/agents/codex-utils.js",
@@ -31,8 +31,8 @@
31
31
  },
32
32
  {
33
33
  "file": "src/agents/codex.js",
34
- "beforeBytes": 43923,
35
- "afterBytes": 22006
34
+ "beforeBytes": 47055,
35
+ "afterBytes": 23670
36
36
  },
37
37
  {
38
38
  "file": "src/agents/command-spec.js",
@@ -61,7 +61,7 @@
61
61
  },
62
62
  {
63
63
  "file": "src/agents/external-channel-instructions.js",
64
- "beforeBytes": 9456,
64
+ "beforeBytes": 9452,
65
65
  "afterBytes": 13591
66
66
  },
67
67
  {
@@ -139,11 +139,6 @@
139
139
  "beforeBytes": 14265,
140
140
  "afterBytes": 7150
141
141
  },
142
- {
143
- "file": "src/commands/agent.js",
144
- "beforeBytes": 7536,
145
- "afterBytes": 4438
146
- },
147
142
  {
148
143
  "file": "src/commands/daemon-process.js",
149
144
  "beforeBytes": 3288,
@@ -161,17 +156,17 @@
161
156
  },
162
157
  {
163
158
  "file": "src/commands/daemon.js",
164
- "beforeBytes": 38631,
165
- "afterBytes": 18928
159
+ "beforeBytes": 39808,
160
+ "afterBytes": 19439
166
161
  },
167
162
  {
168
163
  "file": "src/commands/integration.js",
169
- "beforeBytes": 815,
164
+ "beforeBytes": 827,
170
165
  "afterBytes": 468
171
166
  },
172
167
  {
173
168
  "file": "src/commands/manager.js",
174
- "beforeBytes": 6902,
169
+ "beforeBytes": 6853,
175
170
  "afterBytes": 4479
176
171
  },
177
172
  {
@@ -196,8 +191,8 @@
196
191
  },
197
192
  {
198
193
  "file": "src/commands/room.js",
199
- "beforeBytes": 15343,
200
- "afterBytes": 8914
194
+ "beforeBytes": 16095,
195
+ "afterBytes": 9244
201
196
  },
202
197
  {
203
198
  "file": "src/commands/runtime.js",
@@ -281,8 +276,8 @@
281
276
  },
282
277
  {
283
278
  "file": "src/index.js",
284
- "beforeBytes": 21131,
285
- "afterBytes": 11117
279
+ "beforeBytes": 21040,
280
+ "afterBytes": 11051
286
281
  },
287
282
  {
288
283
  "file": "src/log-reporter.js",
@@ -291,12 +286,12 @@
291
286
  },
292
287
  {
293
288
  "file": "src/manager/prompt.js",
294
- "beforeBytes": 4352,
289
+ "beforeBytes": 4350,
295
290
  "afterBytes": 7113
296
291
  },
297
292
  {
298
293
  "file": "src/manager/registry.js",
299
- "beforeBytes": 13897,
294
+ "beforeBytes": 13895,
300
295
  "afterBytes": 6747
301
296
  },
302
297
  {
@@ -306,7 +301,7 @@
306
301
  },
307
302
  {
308
303
  "file": "src/manager/runtime.js",
309
- "beforeBytes": 71951,
304
+ "beforeBytes": 71949,
310
305
  "afterBytes": 36578
311
306
  },
312
307
  {
@@ -399,6 +394,11 @@
399
394
  "beforeBytes": 6136,
400
395
  "afterBytes": 3363
401
396
  },
397
+ {
398
+ "file": "src/room/invite-qr.js",
399
+ "beforeBytes": 888,
400
+ "afterBytes": 519
401
+ },
402
402
  {
403
403
  "file": "src/room/managed-context.js",
404
404
  "beforeBytes": 4442,
@@ -431,13 +431,13 @@
431
431
  },
432
432
  {
433
433
  "file": "src/room/public-room-client.js",
434
- "beforeBytes": 5637,
435
- "afterBytes": 3101
434
+ "beforeBytes": 5771,
435
+ "afterBytes": 3197
436
436
  },
437
437
  {
438
438
  "file": "src/room/room-action-client.js",
439
- "beforeBytes": 23331,
440
- "afterBytes": 11775
439
+ "beforeBytes": 23728,
440
+ "afterBytes": 11957
441
441
  },
442
442
  {
443
443
  "file": "src/room/room-binding-client.js",
@@ -3,6 +3,7 @@ import type { ChatAttachmentMeta, ExternalChannelSessionStatus } from '@shennian
3
3
  export declare function normalizeClaudeModelId(modelId?: string | null): string;
4
4
  export declare function normalizeClaudeReasoningEffort(reasoningEffort?: string | null): string | undefined;
5
5
  export declare function resetClaudeCapabilityCacheForTests(): void;
6
+ export declare function shouldForwardClaudeStderrLine(line: string): boolean;
6
7
  export declare function supportsClaudeReasoningEffort(): boolean;
7
8
  export declare class ClaudeAdapter extends AgentAdapter {
8
9
  private readonly options;
@@ -1,8 +1,8 @@
1
- import{createInterface as f}from"node:readline";import{randomUUID as d}from"node:crypto";import{AgentAdapter as g,registerAgent as I}from"./adapter.js";import{resolveBuiltinCommand as p,spawnAgentCommand as S,spawnResolvedCommandSync as E}from"./command-spec.js";import{buildAgentProcessEnv as m}from"../agent-env.js";import{buildPlatformInstructions as c}from"./platform-instructions.js";function y(i){return i?.trim()||"default"}const x=new Set(["low","medium","high","xhigh","max"]),a=new Map;function w(i){const t=i?.trim();if(t){if(x.has(t))return t;throw new Error(`Unsupported Claude reasoning effort "${t}". Supported values: low, medium, high, xhigh, max.`)}}function R(){a.clear()}function C(){const i=p("claude");if(!i)return!1;const t=`${i.kind}:${i.path}:${i.command}:${i.args.join("\0")}`;if(a.has(t))return a.get(t)??!1;try{const e=E(i,["--help"],{encoding:"utf-8",stdio:["ignore","pipe","pipe"],timeout:3e3,env:m()}),s=`${e.stdout??""}
2
- ${e.stderr??""}`,o=/(?:^|\s)--effort(?:\s|,|<)/.test(s);return a.set(t,o),o}catch{return a.set(t,!1),!1}}class k extends g{options;type="claude";process=null;agentSessionId=null;sessionId=null;workDir=null;seq=0;runId="";hasEmittedText=!1;terminalState="open";constructor(t={}){super(),this.options=t}externalChannel=null;shennianSessionId=null;extraEnv={};configure(t){this.shennianSessionId=t.sessionId??null,this.externalChannel=t.externalChannel??null,this.extraEnv=t.env??{}}async start(t,e,s){this.sessionId=t,this.workDir=e,this.seq=0,s&&(this.agentSessionId=s)}async send(t,e,s,o,u){await this.killProcess(),this.runId=d(),this.resetRunState();const n=["-p",t,"--output-format","stream-json","--verbose"],r=this.options.systemPrompt??c(this.workDir??process.cwd(),this.externalChannel,this.shennianSessionId??void 0,u);r&&n.push("--append-system-prompt",r),this.options.hidden&&n.push("--name","Shennian Manager Substrate"),process.getuid?.()!==0&&n.push("--dangerously-skip-permissions"),n.push("--model",y(e));const l=w(s);l&&C()&&n.push("--effort",l),this.agentSessionId&&n.push("--resume",this.agentSessionId),this.spawnAndParse(n)}async resume(t){await this.killProcess(),this.agentSessionId=t,this.runId=d(),this.resetRunState();const e=["--resume",t,"--output-format","stream-json","--verbose"],s=this.options.systemPrompt??c(this.workDir??process.cwd(),this.externalChannel,this.shennianSessionId??void 0);s&&e.push("--append-system-prompt",s),this.options.hidden&&e.push("--name","Shennian Manager Substrate"),process.getuid?.()!==0&&e.push("--dangerously-skip-permissions"),this.spawnAndParse(e)}async stop(){await this.killProcess()}spawnAndParse(t){const e=p("claude");if(!e){this.emit("error",new Error('Command "claude" not found. Is Claude Code CLI installed?'));return}const s=S(e,t,{cwd:this.workDir??void 0,stdio:["ignore","pipe","pipe"],env:m(this.extraEnv)});this.process=s,f({input:s.stdout}).on("line",n=>{if(n.trim())try{const r=JSON.parse(n);this.handleStreamEvent(r)}catch{}});let u="";s.stderr?.on("data",n=>{const r=n.toString();if(u+=r,this.terminalState==="open")for(const l of r.split(`
3
- `)){const h=l.trim();h&&this.emitEvent({state:"delta",text:h+`
1
+ import{createInterface as f}from"node:readline";import{randomUUID as h}from"node:crypto";import{AgentAdapter as g,registerAgent as S}from"./adapter.js";import{resolveBuiltinCommand as p,spawnAgentCommand as I,spawnResolvedCommandSync as E}from"./command-spec.js";import{buildAgentProcessEnv as m}from"../agent-env.js";import{buildPlatformInstructions as c}from"./platform-instructions.js";function x(i){return i?.trim()||"default"}const y=new Set(["low","medium","high","xhigh","max"]),a=new Map;function w(i){const t=i?.trim();if(t){if(y.has(t))return t;throw new Error(`Unsupported Claude reasoning effort "${t}". Supported values: low, medium, high, xhigh, max.`)}}function $(){a.clear()}function C(i){const t=i.trim();return t?!t.includes("claude.ai connectors are disabled because"):!1}function k(){const i=p("claude");if(!i)return!1;const t=`${i.kind}:${i.path}:${i.command}:${i.args.join("\0")}`;if(a.has(t))return a.get(t)??!1;try{const e=E(i,["--help"],{encoding:"utf-8",stdio:["ignore","pipe","pipe"],timeout:3e3,env:m()}),s=`${e.stdout??""}
2
+ ${e.stderr??""}`,o=/(?:^|\s)--effort(?:\s|,|<)/.test(s);return a.set(t,o),o}catch{return a.set(t,!1),!1}}class v extends g{options;type="claude";process=null;agentSessionId=null;sessionId=null;workDir=null;seq=0;runId="";hasEmittedText=!1;terminalState="open";constructor(t={}){super(),this.options=t}externalChannel=null;shennianSessionId=null;extraEnv={};configure(t){this.shennianSessionId=t.sessionId??null,this.externalChannel=t.externalChannel??null,this.extraEnv=t.env??{}}async start(t,e,s){this.sessionId=t,this.workDir=e,this.seq=0,s&&(this.agentSessionId=s)}async send(t,e,s,o,u){await this.killProcess(),this.runId=h(),this.resetRunState();const n=["-p",t,"--output-format","stream-json","--verbose"],r=this.options.systemPrompt??c(this.workDir??process.cwd(),this.externalChannel,this.shennianSessionId??void 0,u);r&&n.push("--append-system-prompt",r),this.options.hidden&&n.push("--name","Shennian Manager Substrate"),process.getuid?.()!==0&&n.push("--dangerously-skip-permissions"),n.push("--model",x(e));const l=w(s);l&&k()&&n.push("--effort",l),this.agentSessionId&&n.push("--resume",this.agentSessionId),this.spawnAndParse(n)}async resume(t){await this.killProcess(),this.agentSessionId=t,this.runId=h(),this.resetRunState();const e=["--resume",t,"--output-format","stream-json","--verbose"],s=this.options.systemPrompt??c(this.workDir??process.cwd(),this.externalChannel,this.shennianSessionId??void 0);s&&e.push("--append-system-prompt",s),this.options.hidden&&e.push("--name","Shennian Manager Substrate"),process.getuid?.()!==0&&e.push("--dangerously-skip-permissions"),this.spawnAndParse(e)}async stop(){await this.killProcess()}spawnAndParse(t){const e=p("claude");if(!e){this.emit("error",new Error('Command "claude" not found. Is Claude Code CLI installed?'));return}const s=I(e,t,{cwd:this.workDir??void 0,stdio:["ignore","pipe","pipe"],env:m(this.extraEnv)});this.process=s,f({input:s.stdout}).on("line",n=>{if(n.trim())try{const r=JSON.parse(n);this.handleStreamEvent(r)}catch{}});let u="";s.stderr?.on("data",n=>{const r=n.toString();if(u+=r,this.terminalState==="open")for(const l of r.split(`
3
+ `)){const d=l.trim();C(d)&&this.emitEvent({state:"delta",text:d+`
4
4
  `})}}),s.on("close",n=>{this.process===s&&(this.process=null,this.handleProcessClose(n,u))}),s.on("error",n=>{this.process===s&&(this.process=null,n.code==="ENOENT"?this.emit("error",new Error('Command "claude" not found. Is Claude Code CLI installed?')):this.emit("error",n))})}handleStreamEvent(t){if(t.type==="system"&&t.subtype==="init"&&t.session_id){this.agentSessionId=t.session_id,this.emitEvent({state:"start",agentSessionId:this.agentSessionId});return}if(t.type==="system"&&t.subtype==="api_retry"&&t.error==="authentication_failed"){this.emitErrorIfOpen({state:"error",message:'Claude authentication failed. Run "claude auth login" and retry.'});return}if(t.type==="assistant"){if(t.message?.content){for(const e of t.message.content)if(e.type==="text"&&e.text){const s=this.hasEmittedText?`
5
5
 
6
6
  `:"";this.emitEvent({state:"delta",text:s+e.text}),this.hasEmittedText=!0}else e.type==="thinking"&&e.thinking?this.emitEvent({state:"delta",text:e.thinking,thinking:!0}):e.type==="tool_use"&&this.emitEvent({state:"tool-call",name:e.name,args:e.input});return}if(t.subtype==="text"&&t.text){const e=this.hasEmittedText?`
7
7
 
8
- `:"";this.emitEvent({state:"delta",text:e+t.text}),this.hasEmittedText=!0}else t.subtype==="thinking"&&t.thinking?this.emitEvent({state:"delta",text:t.thinking,thinking:!0}):t.subtype==="tool_use"&&this.emitEvent({state:"tool-call",name:t.name,args:t.input});return}if(t.type==="result")if(t.subtype==="tool_result"){const e=typeof t.content=="string"?t.content:JSON.stringify(t.content);this.emitEvent({state:"tool-result",name:t.name,result:e})}else if(t.subtype==="success"){t.session_id&&(this.agentSessionId=t.session_id);const e=t.usage??t.message?.usage;this.emitFinalIfOpen({state:"final",agentSessionId:this.agentSessionId??void 0,usage:e?{inputTokens:e.input_tokens,outputTokens:e.output_tokens}:void 0})}else t.subtype==="error"&&this.emitErrorIfOpen({state:"error",message:t.error??t.result??"unknown error"})}handleProcessClose(t,e){if(t!==0&&t!==null){const s=e.trim()||`claude exited with code ${t}`;this.emitErrorIfOpen({state:"error",message:s});return}this.emitFinalIfOpen({state:"final",agentSessionId:this.agentSessionId??void 0})}emitFinalIfOpen(t){this.terminalState==="open"&&(this.terminalState="final",this.emitEvent(t))}emitErrorIfOpen(t){this.terminalState==="open"&&(this.terminalState="error",this.emitEvent(t))}resetRunState(){this.seq=0,this.hasEmittedText=!1,this.terminalState="open"}emitEvent(t){const e={...t,runId:this.runId,seq:this.seq++};this.emit("agentEvent",e)}async killProcess(){const t=this.process;t&&(this.process=null,t.kill("SIGTERM"),await new Promise(e=>{t.on("close",e),setTimeout(()=>{t.kill("SIGKILL"),e()},3e3)}))}}I("claude",()=>new k);export{k as ClaudeAdapter,y as normalizeClaudeModelId,w as normalizeClaudeReasoningEffort,R as resetClaudeCapabilityCacheForTests,C as supportsClaudeReasoningEffort};
8
+ `:"";this.emitEvent({state:"delta",text:e+t.text}),this.hasEmittedText=!0}else t.subtype==="thinking"&&t.thinking?this.emitEvent({state:"delta",text:t.thinking,thinking:!0}):t.subtype==="tool_use"&&this.emitEvent({state:"tool-call",name:t.name,args:t.input});return}if(t.type==="result")if(t.subtype==="tool_result"){const e=typeof t.content=="string"?t.content:JSON.stringify(t.content);this.emitEvent({state:"tool-result",name:t.name,result:e})}else if(t.subtype==="success"){t.session_id&&(this.agentSessionId=t.session_id);const e=t.usage??t.message?.usage;this.emitFinalIfOpen({state:"final",agentSessionId:this.agentSessionId??void 0,usage:e?{inputTokens:e.input_tokens,outputTokens:e.output_tokens}:void 0})}else t.subtype==="error"&&this.emitErrorIfOpen({state:"error",message:t.error??t.result??"unknown error"})}handleProcessClose(t,e){if(t!==0&&t!==null){const s=e.trim()||`claude exited with code ${t}`;this.emitErrorIfOpen({state:"error",message:s});return}this.emitFinalIfOpen({state:"final",agentSessionId:this.agentSessionId??void 0})}emitFinalIfOpen(t){this.terminalState==="open"&&(this.terminalState="final",this.emitEvent(t))}emitErrorIfOpen(t){this.terminalState==="open"&&(this.terminalState="error",this.emitEvent(t))}resetRunState(){this.seq=0,this.hasEmittedText=!1,this.terminalState="open"}emitEvent(t){const e={...t,runId:this.runId,seq:this.seq++};this.emit("agentEvent",e)}async killProcess(){const t=this.process;t&&(this.process=null,t.kill("SIGTERM"),await new Promise(e=>{t.on("close",e),setTimeout(()=>{t.kill("SIGKILL"),e()},3e3)}))}}S("claude",()=>new v);export{v as ClaudeAdapter,x as normalizeClaudeModelId,w as normalizeClaudeReasoningEffort,$ as resetClaudeCapabilityCacheForTests,C as shouldForwardClaudeStderrLine,k as supportsClaudeReasoningEffort};
@@ -5,6 +5,14 @@ export { isCodexUnsupportedEffortError, isMissingCodexRolloutError, normalizeCod
5
5
  export declare function isCodexActiveWriterError(error: unknown): boolean;
6
6
  export declare function isCodexPaginatedHistoryUnsupportedError(error: unknown): boolean;
7
7
  export declare function isCodexExcludeTurnsUnsupportedError(error: unknown): boolean;
8
+ export declare function isCodexProjectApiUnsupportedError(error: unknown): boolean;
9
+ type CodexProject = {
10
+ id?: string;
11
+ roots?: Array<{
12
+ path?: string;
13
+ }>;
14
+ };
15
+ export declare function selectCodexProjectIdForWorkDir(projects: CodexProject[], workDir: string): string | undefined;
8
16
  export declare function buildCodexQueuedText(text: string, attachments?: ChatAttachmentMeta[]): string;
9
17
  export declare function buildCodexQueueArgs(threadId: string, text: string, attachments?: ChatAttachmentMeta[]): string[];
10
18
  export declare class CodexAdapter extends AgentAdapter {
@@ -58,6 +66,7 @@ export declare class CodexAdapter extends AgentAdapter {
58
66
  private spawnAppServer;
59
67
  private ensureAppServer;
60
68
  private initializeAppServer;
69
+ private resolveCodexProjectId;
61
70
  private startTurnWithRecovery;
62
71
  private startTurn;
63
72
  private interruptActiveTurn;
@@ -1,16 +1,16 @@
1
- import{spawnSync as C}from"node:child_process";import{createInterface as I}from"node:readline";import{randomUUID as _}from"node:crypto";import A from"node:path";import{AgentAdapter as R,registerAgent as P}from"./adapter.js";import{resolveBuiltinCommand as g,spawnAgentCommand as v}from"./command-spec.js";import{buildAgentProcessEnv as x}from"../agent-env.js";import{ensurePlatformInstructionsFile as q}from"./platform-instructions.js";import{CODEX_APP_SERVER_CLIENT_INFO as O,extractAppServerErrorMessage as $,formatCodexErrorMessage as b,formatTransientCodexStatus as L,isCodexCollabAgentItem as F,isCodexCollabAgentToolName as f,isCodexLegacyCollabAgentItem as M,isCodexUnsupportedEffortError as U,isMissingCodexRolloutError as N,isTransientCodexErrorMessage as D,looksLikeCodexInteractiveAuthPrompt as W,looksLikeFatalCodexStderr as z,makeThreadTitle as B,normalizeCodexModelId as S,normalizeCodexReasoningEffort as G,normalizeCodexStderr as T,normalizeTerminalText as J,safeStringify as w,stripGitDirectiveArtifacts as H}from"./codex-utils.js";import{isCodexUnsupportedEffortError as me,isMissingCodexRolloutError as fe,normalizeCodexModelId as ge,normalizeCodexReasoningEffort as ve}from"./codex-utils.js";function V(a){const e=a instanceof Error?a.message:String(a);return/already has an active writer|thread-store conflict/i.test(e)}function Q(a){const e=a instanceof Error?a.message:String(a);return/paginated(?:_threads|\s+(?:thread\s+)?history)?.*(?:not supported|unsupported|unavailable)/i.test(e)||/(?:not supported|unsupported|unavailable).*paginated(?:_threads|\s+(?:thread\s+)?history)?/i.test(e)}function K(a){const e=a instanceof Error?a.message:String(a);return/unknown field [`'"]?excludeTurns/i.test(e)||/excludeTurns.*(?:not supported|unsupported|unavailable)/i.test(e)}function X(a,e){const t=e?.flatMap(r=>Y(r)).filter(r=>!/^https?:\/\//i.test(r.path)).filter(r=>r.path);return{type:"text",text:j(a,e),text_elements:[],...t?.length?{local_images:t}:{}}}function Y(a){if(a.kind==="image")return a.previewData?[{path:a.path,data:a.previewData,mimeType:a.mimeType}]:[{path:a.path}];if(a.mimeType.startsWith("video/")){const e=Z(a.path);return e?[{path:e}]:[]}return[]}function Z(a){if(!a||/^https?:\/\//i.test(a))return null;const e=A.extname(a),s=`${e?a.slice(0,-e.length):a}.preview.png`;return C("ffmpeg",["-y","-ss","0","-i",a,"-frames:v","1","-vf","scale=320:-1",s],{stdio:"ignore",timeout:15e3}).status===0?s:null}function j(a,e){const t=e?.filter(i=>i.kind!=="image").filter(i=>i.path&&!/^https?:\/\//i.test(i.path));if(!t?.length||t.every(i=>a.includes(i.path)))return a;const r=t.map((i,n)=>{const o=i.name||`attachment-${n+1}`,l=i.mimeType?` (${i.mimeType})`:"";return`[${k(o)}${l}](<${i.path}>)`});return[a,"",...r].join(`
2
- `)}function ee(a,e){const t=e?.filter(r=>r.path&&!/^https?:\/\//i.test(r.path)&&!a.includes(r.path));if(!t?.length)return a;const s=t.map((r,i)=>{const n=k(r.name||`attachment-${i+1}`);return r.kind==="image"?`![${n}](<${r.path}>)`:`[${n}](<${r.path}>)`});return[a,"",...s].join(`
3
- `)}function te(a,e,t){return["queue","--thread",a,"--message",ee(e,t)]}function k(a){return a.replace(/[[\]\\]/g,"\\$&")}class se extends R{options;type="codex";process=null;workDir=null;seq=0;runId="";pendingUsage;agentSessionId=null;hasEmittedText=!1;textTail="";terminalState="open";forceCloseTimer=null;stderrBuf="";rpcSeq=1;pendingRequests=new Map;appServerReady=null;appServerStartupError=null;activeTurnId=null;namedThread=!1;deltaItemIds=new Set;lastTransientStatus="";agentSystemPrompt="";constructor(e={}){super(),this.options=e}externalChannel=null;shennianSessionId=null;extraEnv={};configure(e){this.shennianSessionId=e.sessionId??null,this.externalChannel=e.externalChannel??null,this.extraEnv=e.env??{}}async start(e,t,s){this.workDir=t,this.seq=0,s&&(this.agentSessionId=s)}async send(e,t,s,r,i){this.activeTurnId&&(await this.interruptActiveTurn().catch(()=>{}),await this.killProcess());const n=S(t),o=G(s),l=i?.trim()??"";l!==this.agentSystemPrompt&&(this.agentSystemPrompt=l,this.process&&await this.killProcess()),this.runId=_(),this.seq=0,this.resetRunState();try{await this.ensureAppServer(n)}catch(c){if(!this.agentSessionId||!V(c))throw c;return{deliveryMode:"external_owner_queue",queuedSubmissionId:await this.enqueueForExternalOwner(this.agentSessionId,e,r)}}const u=this.agentSessionId;if(!u)throw new Error("codex app-server did not create a thread");this.emitEvent({state:"start",agentSessionId:u}),this.namedThread||(await this.sendRpc("thread/name/set",{threadId:u,name:B(e)}).catch(()=>{}),this.namedThread=!0);const p=await this.startTurnWithRecovery(u,e,n,o,r);return this.activeTurnId=p.turn?.id??null,{deliveryMode:"direct"}}async enqueueForExternalOwner(e,t,s){const r=g("codex");if(!r)throw new Error('Command "codex" not found. Is OpenAI Codex CLI installed?');const i=te(e,t,s),n=v(r,i,{cwd:this.workDir??void 0,stdio:["ignore","pipe","pipe"],env:x({NO_COLOR:"1",...this.extraEnv})});return await new Promise((o,l)=>{let u="",p="",c=!1;const h=(d,m)=>{c||(c=!0,clearTimeout(y),d?l(d):o(m))},y=setTimeout(()=>{n.kill("SIGTERM"),h(new Error("Codex Desktop message queue timed out"))},6e4);y.unref?.(),n.stdout?.on("data",d=>{u+=d.toString()}),n.stderr?.on("data",d=>{p+=d.toString()}),n.once("error",d=>h(d)),n.once("close",d=>{const m=`${u}
4
- ${p}`.trim();if(d!==0){h(new Error(m||`codex queue exited with code ${d??"unknown"}`));return}const E=m.match(/Queued message\s+([^\s]+)\s+for thread/i)?.[1];h(void 0,E)})})}async resume(e){await this.killProcess(),this.agentSessionId=e,this.namedThread=!0,this.resetTextState()}async setTitle(e,t,s){const r=e.trim(),i=t.replace(/\s+/g," ").trim();!r||!i||(this.agentSessionId=r,s?.trim()&&(this.workDir=s),await this.ensureAppServer(),await this.sendRpc("thread/name/set",{threadId:r,name:i}),this.namedThread=!0)}async stop(){await this.interruptActiveTurn().catch(()=>{}),await this.killProcess()}async getStatus(){const e=this.agentSessionId,t=this.activeTurnId;if(!e||!this.process)return{active:!1,runId:t,runPhase:null,canStop:!1};let s=null;try{s=(await this.sendRpc("thread/read",{threadId:e,includeTurns:!1},5e3)).thread?.status??null}catch{}const r=s?.type==="active"||!!t;return{active:r,runId:t,runPhase:r?this.mapThreadStatusToRunPhase(s):null,canStop:!!(r&&t&&this.process)}}spawnCodex(e){const t=g("codex");if(!t){this.emit("error",new Error('Command "codex" not found. Is OpenAI Codex CLI installed?'));return}const s=v(t,e,{cwd:this.workDir??void 0,stdio:["ignore","pipe","pipe"],env:x(this.extraEnv)});this.process=s,I({input:s.stdout}).on("line",n=>{if(n.trim())try{const o=JSON.parse(n);this.handleStreamEvent(o)}catch{this.emitEvent({state:"delta",text:n})}});let i="";s.stderr?.on("data",n=>{i+=n.toString()}),s.on("close",n=>{this.process===s&&(this.process=null,this.clearForceCloseTimer(),this.handleProcessClose(n,i))}),s.on("error",n=>{this.process===s&&(this.process=null,n.code==="ENOENT"?this.emit("error",new Error('Command "codex" not found. Is OpenAI Codex CLI installed?')):this.emit("error",n))})}spawnAppServer(){const e=g("codex");if(!e){this.emit("error",new Error('Command "codex" not found. Is OpenAI Codex CLI installed?'));return}const t=["app-server","--listen","stdio://"],s=this.options.modelInstructionsFile??q(this.workDir??process.cwd(),this.externalChannel,this.shennianSessionId??void 0,this.agentSystemPrompt);s&&t.push("-c",`model_instructions_file=${JSON.stringify(s)}`);const r=v(e,t,{cwd:this.workDir??void 0,stdio:["pipe","pipe","pipe"],env:x({NO_COLOR:"1",...this.extraEnv})});this.process=r,this.stderrBuf="",this.appServerStartupError=null,I({input:r.stdout}).on("line",n=>{if(!n.trim())return;let o;try{o=JSON.parse(n)}catch{this.handleAppServerNonJsonStdout(n);return}this.handleAppServerMessage(o)}),r.stderr?.on("data",n=>{this.stderrBuf+=n.toString()}),r.on("close",n=>{if(this.process!==r)return;const o=this.activeTurnId!=null;this.process=null,this.appServerReady=null,this.activeTurnId=null,this.rejectAllPending(new Error(T(this.stderrBuf)||`codex app-server exited with code ${n}`)),this.terminalState==="open"&&o&&this.handleProcessClose(n,this.stderrBuf)}),r.on("error",n=>{this.process===r&&(this.process=null,this.rejectAllPending(n),n.code==="ENOENT"?this.emit("error",new Error('Command "codex" not found. Is OpenAI Codex CLI installed?')):this.emit("error",n))})}async ensureAppServer(e){return this.appServerReady?this.appServerReady:(this.spawnAppServer(),this.appServerReady=this.initializeAppServer(e).catch(async t=>{throw this.appServerReady=null,await this.killProcess(),t}),this.appServerReady)}async initializeAppServer(e){const t=S(e);if(await this.sendRpc("initialize",{clientInfo:O,capabilities:{experimentalApi:!0}}),this.agentSessionId){const n={threadId:this.agentSessionId,cwd:this.workDir,approvalPolicy:"never",sandbox:"danger-full-access",persistExtendedHistory:!0,...this.options.hidden?{threadSource:"subagent"}:{},...t?{model:t}:{}};let o;try{o=await this.sendRpc("thread/resume",{...n,excludeTurns:!0})}catch(l){if(!K(l))throw l;o=await this.sendRpc("thread/resume",n)}this.agentSessionId=o.thread?.id??this.agentSessionId,this.namedThread=!!o.thread?.name;return}const s={cwd:this.workDir,approvalPolicy:"never",sandbox:"danger-full-access",serviceName:"Shennian",threadSource:this.options.hidden?"subagent":"user",experimentalRawEvents:!1,persistExtendedHistory:!0,...t?{model:t}:{}};let r;if(this.options.hidden)r=await this.sendRpc("thread/start",s);else try{r=await this.sendRpc("thread/start",{...s,historyMode:"paginated"})}catch(n){throw Q(n)?new Error("\u5F53\u524D Codex CLI \u4E0D\u652F\u6301\u521B\u5EFA\u53EF\u5728 Codex Desktop \u4E2D\u7EE7\u7EED\u7684\u65B0\u4F1A\u8BDD\uFF0C\u8BF7\u624B\u52A8\u5347\u7EA7 Codex CLI \u540E\u91CD\u8BD5\u3002\u795E\u5FF5\u4E0D\u4F1A\u81EA\u52A8\u5347\u7EA7\uFF0C\u4E5F\u4E0D\u4F1A\u964D\u7EA7\u521B\u5EFA\u4E0D\u53EF\u89C1\u7684 legacy \u4F1A\u8BDD\u3002",{cause:n}):n}const i=r.thread?.id;if(!i)throw new Error("codex app-server thread/start returned no thread id");this.agentSessionId=i,this.namedThread=!!r.thread?.name}async startTurnWithRecovery(e,t,s,r,i){try{return await this.startTurn(e,t,s,r,i)}catch(n){if(!N(n))throw n;return await this.killProcess(),await this.ensureAppServer(s),await this.startTurn(e,t,s,r,i)}}async startTurn(e,t,s,r,i){try{return await this.sendRpc("turn/start",{threadId:e,input:[X(t,i)],approvalPolicy:"never",sandboxPolicy:{type:"dangerFullAccess"},...s?{model:s}:{},...r?{effort:r}:{}})}catch(n){throw r&&U(n)?new Error(`Codex app-server does not accept reasoning effort "${r}" for this turn. Refresh models or upgrade Codex CLI, then retry.`,{cause:n}):n}}async interruptActiveTurn(){const e=this.agentSessionId,t=this.activeTurnId;!e||!t||!this.process||(await this.sendRpc("turn/interrupt",{threadId:e,turnId:t},5e3),this.activeTurnId=null)}sendRpc(e,t,s=6e4){if(this.appServerStartupError)return Promise.reject(this.appServerStartupError);const r=this.process;if(!r?.stdin)return Promise.reject(new Error("codex app-server is not running"));const i=this.rpcSeq++,n=JSON.stringify({id:i,method:e,params:t});return new Promise((o,l)=>{const u=setTimeout(()=>{this.pendingRequests.delete(i),l(new Error(`codex app-server request timed out: ${e}`))},s);this.pendingRequests.set(i,{resolve:o,reject:l,timer:u}),r.stdin.write(`${n}
5
- `,p=>{p&&(clearTimeout(u),this.pendingRequests.delete(i),l(p))})})}handleAppServerMessage(e){if(e.id!=null){const s=this.pendingRequests.get(e.id);s&&(clearTimeout(s.timer),this.pendingRequests.delete(e.id),e.error?s.reject(new Error(e.error.message||`codex app-server error ${e.error.code??""}`.trim())):s.resolve(e.result))}if(!e.method)return;const t=e.params??{};switch(e.method){case"turn/started":{const s=typeof t.turn=="object"&&t.turn!==null?t.turn:null,r=typeof s?.id=="string"?s.id:null;r&&(this.activeTurnId=r);break}case"thread/status/changed":{const s=typeof t.threadId=="string"?t.threadId:"";if(s&&this.agentSessionId&&s!==this.agentSessionId)break;const r=typeof t.status=="object"&&t.status!==null?t.status:null;r?.type==="active"&&this.emitEvent({state:"heartbeat",runPhase:this.mapThreadStatusToRunPhase(r)});break}case"item/agentMessage/delta":{const s=typeof t.itemId=="string"?t.itemId:"";s&&this.deltaItemIds.add(s),typeof t.delta=="string"&&this.emitText(t.delta);break}case"item/completed":this.handleAppServerCompletedItem(t);break;case"thread/tokenUsage/updated":this.handleAppServerUsage(t);break;case"turn/completed":{const s=typeof t.turn=="object"&&t.turn!==null?t.turn:null,r=typeof s?.id=="string"?s.id:null;(!this.activeTurnId||r===this.activeTurnId)&&(this.activeTurnId=null,this.emitFinalIfOpen());break}case"error":{const s=$(t);if(D(s)){this.emitTransientStatus(s);return}this.emitErrorIfOpen(s);break}}}mapThreadStatusToRunPhase(e){const t=e?.type==="active"&&Array.isArray(e.activeFlags)?e.activeFlags:[];return t.includes("waitingOnApproval")?"waiting_approval":t.includes("waitingOnUserInput")?"waiting_user_input":"thinking"}handleAppServerNonJsonStdout(e){const t=J(e);W(t)&&this.failAppServerStartup(new Error("Codex \u672A\u767B\u5F55\u3002\u8BF7\u5148\u5728\u8FD9\u53F0\u7535\u8111\u7684\u7EC8\u7AEF\u8FD0\u884C `codex`\uFF0C\u6309\u63D0\u793A\u5B8C\u6210 ChatGPT \u767B\u5F55\u6216\u914D\u7F6E API key\uFF0C\u7136\u540E\u518D\u56DE\u5230\u795E\u5FF5\u53D1\u9001\u6D88\u606F\u3002"))}failAppServerStartup(e){this.appServerStartupError||(this.appServerStartupError=e,this.rejectAllPending(e),this.process?.kill("SIGTERM"))}handleAppServerCompletedItem(e){const t=typeof e.item=="object"&&e.item!==null?e.item:null;if(!(!t||typeof t.type!="string")&&!F(t))switch(t.type){case"agentMessage":{const s=typeof t.id=="string"?t.id:"";if(s&&this.deltaItemIds.has(s))return;typeof t.text=="string"&&this.emitText(t.text,"block");break}case"commandExecution":{const s=typeof t.command=="string"?t.command:"",r=typeof t.aggregatedOutput=="string"?t.aggregatedOutput:"";s&&this.emitEvent({state:"tool-call",name:"command_execution",args:{command:s}}),this.emitEvent({state:"tool-result",name:"command_execution",result:[r,t.exitCode!=null?`
6
- (exit ${t.exitCode})`:""].join("").trim()});break}case"mcpToolCall":case"dynamicToolCall":{const s=typeof t.tool=="string"?t.tool:t.type;if(f(s))return;const r=typeof t.arguments=="object"&&t.arguments!==null?t.arguments:{};this.emitEvent({state:"tool-call",name:s,args:r}),this.emitEvent({state:"tool-result",name:s,result:w(t.error??t.result??t.contentItems??"")});break}case"fileChange":break;case"approvalRequest":case"approval_request":case"permissionRequest":case"permission_request":case"userInputRequired":case"user_input_required":{this.emitApprovalPending(t);break}}}handleAppServerUsage(e){const t=typeof e.tokenUsage=="object"&&e.tokenUsage!==null?e.tokenUsage:null,s=typeof t?.last=="object"&&t.last!==null?t.last:null,r=typeof t?.total=="object"&&t.total!==null?t.total:null,i=s??r;if(!i)return;const n=typeof i.inputTokens=="number"?i.inputTokens:void 0,o=typeof i.outputTokens=="number"?i.outputTokens:void 0;(n!=null||o!=null)&&(this.pendingUsage={inputTokens:n??0,outputTokens:o??0})}handleCodexItemLine(e){const t=e.item;if(t?.type&&!M(t))switch(t.type){case"agent_message":case"reasoning":{e.type==="item.completed"&&t.text&&this.emitText(t.text,"block");break}case"command_execution":{if(e.type==="item.started"&&t.command&&this.emitEvent({state:"tool-call",name:"command_execution",args:{command:t.command}}),e.type==="item.completed"){const s=[t.aggregated_output??"",t.exit_code!=null?`
7
- (exit ${t.exit_code})`:""];this.emitEvent({state:"tool-result",name:"command_execution",result:s.join("").trim()||String(t.status??"")})}break}case"mcp_tool_call":{if(t.tool&&f(t.tool))return;if(e.type==="item.started"&&this.emitEvent({state:"tool-call",name:t.tool??"mcp_tool",args:{server:t.server,...t.arguments&&typeof t.arguments=="object"?t.arguments:{}}}),e.type==="item.completed"){const s=t.error&&typeof t.error=="object"?t.error.message:void 0;this.emitEvent({state:"tool-result",name:t.tool??"mcp_tool",result:s??w(t.result)})}break}case"file_change":break;case"web_search":{e.type==="item.completed"&&t.query&&this.emitText(`[web_search] ${t.query}`,"block");break}case"todo_list":{if(e.type==="item.completed"&&t.items?.length){const s=t.items.map(r=>`- [${r.completed?"x":" "}] ${r.text}`).join(`
8
- `);this.emitText(s,"block")}break}case"approval_request":case"approvalRequest":case"permission_request":case"permissionRequest":case"user_input_required":case"userInputRequired":{(e.type==="item.started"||e.type==="item.completed")&&this.emitApprovalPending(t);break}case"error":{e.type==="item.completed"&&t.message&&this.emitEvent({state:"error",message:t.message});break}default:break}}handleStreamEvent(e){const t=e.type??e.event;switch(t?.startsWith("item.")&&this.handleCodexItemLine(e),t){case"turn.completed":{const r=e.usage;if(r){const i=r.input_tokens??r.prompt_tokens,n=r.output_tokens??r.completion_tokens;(i!=null||n!=null)&&(this.pendingUsage={inputTokens:i??0,outputTokens:n??0})}this.emitFinalIfOpen();return}case"turn.failed":{const r=typeof e.error=="object"&&e.error?.message?e.error.message:typeof e.error=="string"?e.error:"turn failed";this.emitErrorIfOpen(r);return}case"error":{const r=typeof e.message=="string"?e.message:"";if(r.includes("Reconnecting"))return;this.emitErrorIfOpen(r||(typeof e.error=="string"?e.error:"stream error"));return}case"thread.started":{const r=e.thread_id??e.session_id??e.id;typeof r=="string"&&r&&(this.agentSessionId=r,this.emitEvent({state:"start",agentSessionId:this.agentSessionId}));return}case"turn.started":return}if(t?.startsWith("item."))return;switch(e.type??e.event){case"message":case"text":case"content":{const r=e.content??e.text;r&&this.emitText(r);break}case"function_call":case"tool_call":{if(this.isApprovalLikeName(e.name)){this.emitApprovalPending(e);break}if(e.name&&f(e.name))return;this.emitEvent({state:"tool-call",name:e.name,args:e.arguments??e.input});break}case"function_call_output":case"tool_result":{if(e.name&&f(e.name))return;this.emitEvent({state:"tool-result",name:e.name,result:e.output??e.result});break}case"approval_request":case"approvalRequest":case"permission_request":case"permissionRequest":case"user_input_required":case"userInputRequired":{this.emitApprovalPending(e);break}case"error":break;case"done":case"completed":{e.usage&&(this.pendingUsage={inputTokens:e.usage.prompt_tokens??e.usage.input_tokens??0,outputTokens:e.usage.completion_tokens??e.usage.output_tokens??0}),this.emitFinalIfOpen();break}}}emitEvent(e){if(this.terminalState!=="open"&&e.state!=="final"&&e.state!=="error"&&e.state!=="aborted")return;const t={...e,runId:this.runId,seq:this.seq++};this.emit("agentEvent",t)}isApprovalLikeName(e){return typeof e!="string"?!1:/approval|permission|confirm|user[_-]?input|request[_-]?user/i.test(e)}emitApprovalPending(e){const t=typeof e.command=="string"?e.command:"",s=typeof e.title=="string"&&e.title.trim()?e.title:"\u9700\u8981\u7528\u6237\u786E\u8BA4",r=typeof e.prompt=="string"?e.prompt:typeof e.reason=="string"?e.reason:typeof e.message=="string"?e.message:t?`Codex \u8BF7\u6C42\u6267\u884C\u547D\u4EE4\uFF1A${t}`:"Codex \u6B63\u5728\u7B49\u5F85\u7528\u6237\u786E\u8BA4\u540E\u7EE7\u7EED\u3002";this.emitEvent({state:"approval-pending",approval:{title:s,content:r,source:"codex",actionHint:"\u5F53\u524D\u795E\u5FF5\u6682\u4E0D\u652F\u6301\u8FDC\u7A0B\u786E\u8BA4\uFF0C\u8BF7\u56DE\u5230\u8FD0\u884C Codex \u7684\u7535\u8111/\u7EC8\u7AEF\u5B8C\u6210\u786E\u8BA4\u3002"}})}async killProcess(){const e=this.process;if(this.appServerReady=null,this.activeTurnId=null,!e){this.rejectAllPending(new Error("codex app-server stopped"));return}this.process=null,this.clearForceCloseTimer(),e.kill("SIGTERM"),await new Promise(t=>{e.on("close",t),setTimeout(()=>{e.kill("SIGKILL"),t()},3e3)}),this.rejectAllPending(new Error("codex app-server stopped"))}rejectAllPending(e){for(const[t,s]of this.pendingRequests.entries())clearTimeout(s.timer),s.reject(e),this.pendingRequests.delete(t)}handleProcessClose(e,t){const s=T(t);if(e!==0&&e!==null){const r=s||`codex exited with code ${e}`;this.emitErrorIfOpen(r)}else z(s)?this.emitErrorIfOpen(s):this.emitFinalIfOpen();this.pendingUsage=void 0}emitFinalIfOpen(){this.terminalState==="open"&&(this.terminalState="final",this.emitEvent({state:"final",usage:this.pendingUsage,agentSessionId:this.agentSessionId??void 0}),this.killProcess().catch(()=>{}))}emitErrorIfOpen(e){this.terminalState==="open"&&(this.terminalState="error",this.emitEvent({state:"error",message:b(e)}),this.scheduleProcessClose())}resetRunState(){this.pendingUsage=void 0,this.terminalState="open",this.deltaItemIds.clear(),this.lastTransientStatus="",this.clearForceCloseTimer(),this.resetTextState()}emitTransientStatus(e){const t=L(e);!t||t===this.lastTransientStatus||this.terminalState!=="open"||(this.lastTransientStatus=t,this.emitEvent({state:"delta",text:`${t}
9
- `,thinking:!0}))}resetTextState(){this.hasEmittedText=!1,this.textTail=""}emitText(e,t="delta"){if(!e)return;let s=H(e);s&&(t==="block"&&this.hasEmittedText&&!s.startsWith(`
1
+ import{spawnSync as _}from"node:child_process";import{createInterface as S}from"node:readline";import{createHash as A,randomUUID as R}from"node:crypto";import p from"node:path";import{AgentAdapter as P,registerAgent as q}from"./adapter.js";import{resolveBuiltinCommand as v,spawnAgentCommand as x}from"./command-spec.js";import{buildAgentProcessEnv as I}from"../agent-env.js";import{ensurePlatformInstructionsFile as O}from"./platform-instructions.js";import{CODEX_APP_SERVER_CLIENT_INFO as b,extractAppServerErrorMessage as $,formatCodexErrorMessage as L,formatTransientCodexStatus as D,isCodexCollabAgentItem as F,isCodexCollabAgentToolName as g,isCodexLegacyCollabAgentItem as M,isCodexUnsupportedEffortError as U,isMissingCodexRolloutError as N,isTransientCodexErrorMessage as W,looksLikeCodexInteractiveAuthPrompt as z,looksLikeFatalCodexStderr as B,makeThreadTitle as G,normalizeCodexModelId as T,normalizeCodexReasoningEffort as H,normalizeCodexStderr as w,normalizeTerminalText as J,safeStringify as k,stripGitDirectiveArtifacts as V}from"./codex-utils.js";import{isCodexUnsupportedEffortError as ve,isMissingCodexRolloutError as xe,normalizeCodexModelId as Ie,normalizeCodexReasoningEffort as ye}from"./codex-utils.js";function K(o){const e=o instanceof Error?o.message:String(o);return/already has an active writer|thread-store conflict/i.test(e)}function Q(o){const e=o instanceof Error?o.message:String(o);return/paginated(?:_threads|\s+(?:thread\s+)?history)?.*(?:not supported|unsupported|unavailable)/i.test(e)||/(?:not supported|unsupported|unavailable).*paginated(?:_threads|\s+(?:thread\s+)?history)?/i.test(e)}function X(o){const e=o instanceof Error?o.message:String(o);return/unknown field [`'"]?excludeTurns/i.test(e)||/excludeTurns.*(?:not supported|unsupported|unavailable)/i.test(e)}function Y(o){const e=o instanceof Error?o.message:String(o);return/(?:method not found|unknown method).*(?:project\/list|project\/create)/i.test(e)||/(?:project\/list|project\/create).*(?:not supported|unsupported|unavailable)/i.test(e)}function Z(o,e){const t=p.resolve(e);let s;for(const r of o)if(r.id)for(const i of r.roots??[]){if(!i.path)continue;const n=p.resolve(i.path),a=p.relative(n,t);!(a===""||!a.startsWith("..")&&!p.isAbsolute(a))||n.length<=(s?.rootLength??-1)||(s={id:r.id,rootLength:n.length})}return s?.id}function j(o,e){const t=e?.flatMap(r=>ee(r)).filter(r=>!/^https?:\/\//i.test(r.path)).filter(r=>r.path);return{type:"text",text:se(o,e),text_elements:[],...t?.length?{local_images:t}:{}}}function ee(o){if(o.kind==="image")return o.previewData?[{path:o.path,data:o.previewData,mimeType:o.mimeType}]:[{path:o.path}];if(o.mimeType.startsWith("video/")){const e=te(o.path);return e?[{path:e}]:[]}return[]}function te(o){if(!o||/^https?:\/\//i.test(o))return null;const e=p.extname(o),s=`${e?o.slice(0,-e.length):o}.preview.png`;return _("ffmpeg",["-y","-ss","0","-i",o,"-frames:v","1","-vf","scale=320:-1",s],{stdio:"ignore",timeout:15e3}).status===0?s:null}function se(o,e){const t=e?.filter(i=>i.kind!=="image").filter(i=>i.path&&!/^https?:\/\//i.test(i.path));if(!t?.length||t.every(i=>o.includes(i.path)))return o;const r=t.map((i,n)=>{const a=i.name||`attachment-${n+1}`,d=i.mimeType?` (${i.mimeType})`:"";return`[${C(a)}${d}](<${i.path}>)`});return[o,"",...r].join(`
2
+ `)}function re(o,e){const t=e?.filter(r=>r.path&&!/^https?:\/\//i.test(r.path)&&!o.includes(r.path));if(!t?.length)return o;const s=t.map((r,i)=>{const n=C(r.name||`attachment-${i+1}`);return r.kind==="image"?`![${n}](<${r.path}>)`:`[${n}](<${r.path}>)`});return[o,"",...s].join(`
3
+ `)}function ne(o,e,t){return["queue","--thread",o,"--message",re(e,t)]}function C(o){return o.replace(/[[\]\\]/g,"\\$&")}class ie extends P{options;type="codex";process=null;workDir=null;seq=0;runId="";pendingUsage;agentSessionId=null;hasEmittedText=!1;textTail="";terminalState="open";forceCloseTimer=null;stderrBuf="";rpcSeq=1;pendingRequests=new Map;appServerReady=null;appServerStartupError=null;activeTurnId=null;namedThread=!1;deltaItemIds=new Set;lastTransientStatus="";agentSystemPrompt="";constructor(e={}){super(),this.options=e}externalChannel=null;shennianSessionId=null;extraEnv={};configure(e){this.shennianSessionId=e.sessionId??null,this.externalChannel=e.externalChannel??null,this.extraEnv=e.env??{}}async start(e,t,s){this.workDir=t,this.seq=0,s&&(this.agentSessionId=s)}async send(e,t,s,r,i){this.activeTurnId&&(await this.interruptActiveTurn().catch(()=>{}),await this.killProcess());const n=T(t),a=H(s),d=i?.trim()??"";d!==this.agentSystemPrompt&&(this.agentSystemPrompt=d,this.process&&await this.killProcess()),this.runId=R(),this.seq=0,this.resetRunState();try{await this.ensureAppServer(n)}catch(h){if(!this.agentSessionId||!K(h))throw h;return{deliveryMode:"external_owner_queue",queuedSubmissionId:await this.enqueueForExternalOwner(this.agentSessionId,e,r)}}const l=this.agentSessionId;if(!l)throw new Error("codex app-server did not create a thread");this.emitEvent({state:"start",agentSessionId:l}),this.namedThread||(await this.sendRpc("thread/name/set",{threadId:l,name:G(e)}).catch(()=>{}),this.namedThread=!0);const u=await this.startTurnWithRecovery(l,e,n,a,r);return this.activeTurnId=u.turn?.id??null,{deliveryMode:"direct"}}async enqueueForExternalOwner(e,t,s){const r=v("codex");if(!r)throw new Error('Command "codex" not found. Is OpenAI Codex CLI installed?');const i=ne(e,t,s),n=x(r,i,{cwd:this.workDir??void 0,stdio:["ignore","pipe","pipe"],env:I({NO_COLOR:"1",...this.extraEnv})});return await new Promise((a,d)=>{let l="",u="",h=!1;const m=(c,f)=>{h||(h=!0,clearTimeout(y),c?d(c):a(f))},y=setTimeout(()=>{n.kill("SIGTERM"),m(new Error("Codex Desktop message queue timed out"))},6e4);y.unref?.(),n.stdout?.on("data",c=>{l+=c.toString()}),n.stderr?.on("data",c=>{u+=c.toString()}),n.once("error",c=>m(c)),n.once("close",c=>{const f=`${l}
4
+ ${u}`.trim();if(c!==0){m(new Error(f||`codex queue exited with code ${c??"unknown"}`));return}const E=f.match(/Queued message\s+([^\s]+)\s+for thread/i)?.[1];m(void 0,E)})})}async resume(e){await this.killProcess(),this.agentSessionId=e,this.namedThread=!0,this.resetTextState()}async setTitle(e,t,s){const r=e.trim(),i=t.replace(/\s+/g," ").trim();!r||!i||(this.agentSessionId=r,s?.trim()&&(this.workDir=s),await this.ensureAppServer(),await this.sendRpc("thread/name/set",{threadId:r,name:i}),this.namedThread=!0)}async stop(){await this.interruptActiveTurn().catch(()=>{}),await this.killProcess()}async getStatus(){const e=this.agentSessionId,t=this.activeTurnId;if(!e||!this.process)return{active:!1,runId:t,runPhase:null,canStop:!1};let s=null;try{s=(await this.sendRpc("thread/read",{threadId:e,includeTurns:!1},5e3)).thread?.status??null}catch{}const r=s?.type==="active"||!!t;return{active:r,runId:t,runPhase:r?this.mapThreadStatusToRunPhase(s):null,canStop:!!(r&&t&&this.process)}}spawnCodex(e){const t=v("codex");if(!t){this.emit("error",new Error('Command "codex" not found. Is OpenAI Codex CLI installed?'));return}const s=x(t,e,{cwd:this.workDir??void 0,stdio:["ignore","pipe","pipe"],env:I(this.extraEnv)});this.process=s,S({input:s.stdout}).on("line",n=>{if(n.trim())try{const a=JSON.parse(n);this.handleStreamEvent(a)}catch{this.emitEvent({state:"delta",text:n})}});let i="";s.stderr?.on("data",n=>{i+=n.toString()}),s.on("close",n=>{this.process===s&&(this.process=null,this.clearForceCloseTimer(),this.handleProcessClose(n,i))}),s.on("error",n=>{this.process===s&&(this.process=null,n.code==="ENOENT"?this.emit("error",new Error('Command "codex" not found. Is OpenAI Codex CLI installed?')):this.emit("error",n))})}spawnAppServer(){const e=v("codex");if(!e){this.emit("error",new Error('Command "codex" not found. Is OpenAI Codex CLI installed?'));return}const t=["app-server","--listen","stdio://"],s=this.options.modelInstructionsFile??O(this.workDir??process.cwd(),this.externalChannel,this.shennianSessionId??void 0,this.agentSystemPrompt);s&&t.push("-c",`model_instructions_file=${JSON.stringify(s)}`);const r=x(e,t,{cwd:this.workDir??void 0,stdio:["pipe","pipe","pipe"],env:I({NO_COLOR:"1",...this.extraEnv})});this.process=r,this.stderrBuf="",this.appServerStartupError=null,S({input:r.stdout}).on("line",n=>{if(!n.trim())return;let a;try{a=JSON.parse(n)}catch{this.handleAppServerNonJsonStdout(n);return}this.handleAppServerMessage(a)}),r.stderr?.on("data",n=>{this.stderrBuf+=n.toString()}),r.on("close",n=>{if(this.process!==r)return;const a=this.activeTurnId!=null;this.process=null,this.appServerReady=null,this.activeTurnId=null,this.rejectAllPending(new Error(w(this.stderrBuf)||`codex app-server exited with code ${n}`)),this.terminalState==="open"&&a&&this.handleProcessClose(n,this.stderrBuf)}),r.on("error",n=>{this.process===r&&(this.process=null,this.rejectAllPending(n),n.code==="ENOENT"?this.emit("error",new Error('Command "codex" not found. Is OpenAI Codex CLI installed?')):this.emit("error",n))})}async ensureAppServer(e){return this.appServerReady?this.appServerReady:(this.spawnAppServer(),this.appServerReady=this.initializeAppServer(e).catch(async t=>{throw this.appServerReady=null,await this.killProcess(),t}),this.appServerReady)}async initializeAppServer(e){const t=T(e);if(await this.sendRpc("initialize",{clientInfo:b,capabilities:{experimentalApi:!0}}),this.agentSessionId){const a={threadId:this.agentSessionId,cwd:this.workDir,approvalPolicy:"never",sandbox:"danger-full-access",persistExtendedHistory:!0,...this.options.hidden?{threadSource:"subagent"}:{},...t?{model:t}:{}};let d;try{d=await this.sendRpc("thread/resume",{...a,excludeTurns:!0})}catch(l){if(!X(l))throw l;d=await this.sendRpc("thread/resume",a)}this.agentSessionId=d.thread?.id??this.agentSessionId,this.namedThread=!!d.thread?.name;return}const s=this.options.hidden?void 0:await this.resolveCodexProjectId(),r={cwd:this.workDir,approvalPolicy:"never",sandbox:"danger-full-access",serviceName:"Shennian",threadSource:this.options.hidden?"subagent":"user",experimentalRawEvents:!1,persistExtendedHistory:!0,...s?{projectId:s}:{},...t?{model:t}:{}};let i;if(this.options.hidden)i=await this.sendRpc("thread/start",r);else try{i=await this.sendRpc("thread/start",{...r,historyMode:"paginated"})}catch(a){throw Q(a)?new Error("\u5F53\u524D Codex CLI \u4E0D\u652F\u6301\u521B\u5EFA\u53EF\u5728 Codex Desktop \u4E2D\u7EE7\u7EED\u7684\u65B0\u4F1A\u8BDD\uFF0C\u8BF7\u624B\u52A8\u5347\u7EA7 Codex CLI \u540E\u91CD\u8BD5\u3002\u795E\u5FF5\u4E0D\u4F1A\u81EA\u52A8\u5347\u7EA7\uFF0C\u4E5F\u4E0D\u4F1A\u964D\u7EA7\u521B\u5EFA\u4E0D\u53EF\u89C1\u7684 legacy \u4F1A\u8BDD\u3002",{cause:a}):a}const n=i.thread?.id;if(!n)throw new Error("codex app-server thread/start returned no thread id");this.agentSessionId=n,this.namedThread=!!i.thread?.name}async resolveCodexProjectId(){const e=this.workDir?.trim();if(e)try{const t=[];let s;do{const l=await this.sendRpc("project/list",{limit:100,...s?{cursor:s}:{}});t.push(...l.data??[]),s=l.nextCursor}while(s);const r=Z(t,e);if(r)return r;const i=p.resolve(e),n=`shennian-project-${A("sha256").update(i).digest("hex")}`,d=(await this.sendRpc("project/create",{idempotencyKey:n,name:p.basename(i)||i,roots:[{path:i}],metadata:{createdBy:"Shennian"}})).project?.id;if(!d)throw new Error("codex app-server project/create returned no project id");return d}catch(t){throw Y(t)?new Error("\u5F53\u524D Codex CLI \u4E0D\u652F\u6301\u521B\u5EFA\u53EF\u5F52\u5C5E\u5230 Codex Desktop \u9879\u76EE\u7684\u65B0\u4F1A\u8BDD\uFF0C\u8BF7\u624B\u52A8\u5347\u7EA7 Codex CLI \u540E\u91CD\u8BD5\u3002\u795E\u5FF5\u4E0D\u4F1A\u81EA\u52A8\u5347\u7EA7\uFF0C\u4E5F\u4E0D\u4F1A\u76F4\u63A5\u4FEE\u6539 Codex Desktop \u7684\u79C1\u6709\u4FA7\u680F\u7D22\u5F15\u3002",{cause:t}):t}}async startTurnWithRecovery(e,t,s,r,i){try{return await this.startTurn(e,t,s,r,i)}catch(n){if(!N(n))throw n;return await this.killProcess(),await this.ensureAppServer(s),await this.startTurn(e,t,s,r,i)}}async startTurn(e,t,s,r,i){try{return await this.sendRpc("turn/start",{threadId:e,input:[j(t,i)],approvalPolicy:"never",sandboxPolicy:{type:"dangerFullAccess"},...s?{model:s}:{},...r?{effort:r}:{}})}catch(n){throw r&&U(n)?new Error(`Codex app-server does not accept reasoning effort "${r}" for this turn. Refresh models or upgrade Codex CLI, then retry.`,{cause:n}):n}}async interruptActiveTurn(){const e=this.agentSessionId,t=this.activeTurnId;!e||!t||!this.process||(await this.sendRpc("turn/interrupt",{threadId:e,turnId:t},5e3),this.activeTurnId=null)}sendRpc(e,t,s=6e4){if(this.appServerStartupError)return Promise.reject(this.appServerStartupError);const r=this.process;if(!r?.stdin)return Promise.reject(new Error("codex app-server is not running"));const i=this.rpcSeq++,n=JSON.stringify({id:i,method:e,params:t});return new Promise((a,d)=>{const l=setTimeout(()=>{this.pendingRequests.delete(i),d(new Error(`codex app-server request timed out: ${e}`))},s);this.pendingRequests.set(i,{resolve:a,reject:d,timer:l}),r.stdin.write(`${n}
5
+ `,u=>{u&&(clearTimeout(l),this.pendingRequests.delete(i),d(u))})})}handleAppServerMessage(e){if(e.id!=null){const s=this.pendingRequests.get(e.id);s&&(clearTimeout(s.timer),this.pendingRequests.delete(e.id),e.error?s.reject(new Error(e.error.message||`codex app-server error ${e.error.code??""}`.trim())):s.resolve(e.result))}if(!e.method)return;const t=e.params??{};switch(e.method){case"turn/started":{const s=typeof t.turn=="object"&&t.turn!==null?t.turn:null,r=typeof s?.id=="string"?s.id:null;r&&(this.activeTurnId=r);break}case"thread/status/changed":{const s=typeof t.threadId=="string"?t.threadId:"";if(s&&this.agentSessionId&&s!==this.agentSessionId)break;const r=typeof t.status=="object"&&t.status!==null?t.status:null;r?.type==="active"&&this.emitEvent({state:"heartbeat",runPhase:this.mapThreadStatusToRunPhase(r)});break}case"item/agentMessage/delta":{const s=typeof t.itemId=="string"?t.itemId:"";s&&this.deltaItemIds.add(s),typeof t.delta=="string"&&this.emitText(t.delta);break}case"item/completed":this.handleAppServerCompletedItem(t);break;case"thread/tokenUsage/updated":this.handleAppServerUsage(t);break;case"turn/completed":{const s=typeof t.turn=="object"&&t.turn!==null?t.turn:null,r=typeof s?.id=="string"?s.id:null;(!this.activeTurnId||r===this.activeTurnId)&&(this.activeTurnId=null,this.emitFinalIfOpen());break}case"error":{const s=$(t);if(W(s)){this.emitTransientStatus(s);return}this.emitErrorIfOpen(s);break}}}mapThreadStatusToRunPhase(e){const t=e?.type==="active"&&Array.isArray(e.activeFlags)?e.activeFlags:[];return t.includes("waitingOnApproval")?"waiting_approval":t.includes("waitingOnUserInput")?"waiting_user_input":"thinking"}handleAppServerNonJsonStdout(e){const t=J(e);z(t)&&this.failAppServerStartup(new Error("Codex \u672A\u767B\u5F55\u3002\u8BF7\u5148\u5728\u8FD9\u53F0\u7535\u8111\u7684\u7EC8\u7AEF\u8FD0\u884C `codex`\uFF0C\u6309\u63D0\u793A\u5B8C\u6210 ChatGPT \u767B\u5F55\u6216\u914D\u7F6E API key\uFF0C\u7136\u540E\u518D\u56DE\u5230\u795E\u5FF5\u53D1\u9001\u6D88\u606F\u3002"))}failAppServerStartup(e){this.appServerStartupError||(this.appServerStartupError=e,this.rejectAllPending(e),this.process?.kill("SIGTERM"))}handleAppServerCompletedItem(e){const t=typeof e.item=="object"&&e.item!==null?e.item:null;if(!(!t||typeof t.type!="string")&&!F(t))switch(t.type){case"agentMessage":{const s=typeof t.id=="string"?t.id:"";if(s&&this.deltaItemIds.has(s))return;typeof t.text=="string"&&this.emitText(t.text,"block");break}case"commandExecution":{const s=typeof t.command=="string"?t.command:"",r=typeof t.aggregatedOutput=="string"?t.aggregatedOutput:"";s&&this.emitEvent({state:"tool-call",name:"command_execution",args:{command:s}}),this.emitEvent({state:"tool-result",name:"command_execution",result:[r,t.exitCode!=null?`
6
+ (exit ${t.exitCode})`:""].join("").trim()});break}case"mcpToolCall":case"dynamicToolCall":{const s=typeof t.tool=="string"?t.tool:t.type;if(g(s))return;const r=typeof t.arguments=="object"&&t.arguments!==null?t.arguments:{};this.emitEvent({state:"tool-call",name:s,args:r}),this.emitEvent({state:"tool-result",name:s,result:k(t.error??t.result??t.contentItems??"")});break}case"fileChange":break;case"approvalRequest":case"approval_request":case"permissionRequest":case"permission_request":case"userInputRequired":case"user_input_required":{this.emitApprovalPending(t);break}}}handleAppServerUsage(e){const t=typeof e.tokenUsage=="object"&&e.tokenUsage!==null?e.tokenUsage:null,s=typeof t?.last=="object"&&t.last!==null?t.last:null,r=typeof t?.total=="object"&&t.total!==null?t.total:null,i=s??r;if(!i)return;const n=typeof i.inputTokens=="number"?i.inputTokens:void 0,a=typeof i.outputTokens=="number"?i.outputTokens:void 0;(n!=null||a!=null)&&(this.pendingUsage={inputTokens:n??0,outputTokens:a??0})}handleCodexItemLine(e){const t=e.item;if(t?.type&&!M(t))switch(t.type){case"agent_message":case"reasoning":{e.type==="item.completed"&&t.text&&this.emitText(t.text,"block");break}case"command_execution":{if(e.type==="item.started"&&t.command&&this.emitEvent({state:"tool-call",name:"command_execution",args:{command:t.command}}),e.type==="item.completed"){const s=[t.aggregated_output??"",t.exit_code!=null?`
7
+ (exit ${t.exit_code})`:""];this.emitEvent({state:"tool-result",name:"command_execution",result:s.join("").trim()||String(t.status??"")})}break}case"mcp_tool_call":{if(t.tool&&g(t.tool))return;if(e.type==="item.started"&&this.emitEvent({state:"tool-call",name:t.tool??"mcp_tool",args:{server:t.server,...t.arguments&&typeof t.arguments=="object"?t.arguments:{}}}),e.type==="item.completed"){const s=t.error&&typeof t.error=="object"?t.error.message:void 0;this.emitEvent({state:"tool-result",name:t.tool??"mcp_tool",result:s??k(t.result)})}break}case"file_change":break;case"web_search":{e.type==="item.completed"&&t.query&&this.emitText(`[web_search] ${t.query}`,"block");break}case"todo_list":{if(e.type==="item.completed"&&t.items?.length){const s=t.items.map(r=>`- [${r.completed?"x":" "}] ${r.text}`).join(`
8
+ `);this.emitText(s,"block")}break}case"approval_request":case"approvalRequest":case"permission_request":case"permissionRequest":case"user_input_required":case"userInputRequired":{(e.type==="item.started"||e.type==="item.completed")&&this.emitApprovalPending(t);break}case"error":{e.type==="item.completed"&&t.message&&this.emitEvent({state:"error",message:t.message});break}default:break}}handleStreamEvent(e){const t=e.type??e.event;switch(t?.startsWith("item.")&&this.handleCodexItemLine(e),t){case"turn.completed":{const r=e.usage;if(r){const i=r.input_tokens??r.prompt_tokens,n=r.output_tokens??r.completion_tokens;(i!=null||n!=null)&&(this.pendingUsage={inputTokens:i??0,outputTokens:n??0})}this.emitFinalIfOpen();return}case"turn.failed":{const r=typeof e.error=="object"&&e.error?.message?e.error.message:typeof e.error=="string"?e.error:"turn failed";this.emitErrorIfOpen(r);return}case"error":{const r=typeof e.message=="string"?e.message:"";if(r.includes("Reconnecting"))return;this.emitErrorIfOpen(r||(typeof e.error=="string"?e.error:"stream error"));return}case"thread.started":{const r=e.thread_id??e.session_id??e.id;typeof r=="string"&&r&&(this.agentSessionId=r,this.emitEvent({state:"start",agentSessionId:this.agentSessionId}));return}case"turn.started":return}if(t?.startsWith("item."))return;switch(e.type??e.event){case"message":case"text":case"content":{const r=e.content??e.text;r&&this.emitText(r);break}case"function_call":case"tool_call":{if(this.isApprovalLikeName(e.name)){this.emitApprovalPending(e);break}if(e.name&&g(e.name))return;this.emitEvent({state:"tool-call",name:e.name,args:e.arguments??e.input});break}case"function_call_output":case"tool_result":{if(e.name&&g(e.name))return;this.emitEvent({state:"tool-result",name:e.name,result:e.output??e.result});break}case"approval_request":case"approvalRequest":case"permission_request":case"permissionRequest":case"user_input_required":case"userInputRequired":{this.emitApprovalPending(e);break}case"error":break;case"done":case"completed":{e.usage&&(this.pendingUsage={inputTokens:e.usage.prompt_tokens??e.usage.input_tokens??0,outputTokens:e.usage.completion_tokens??e.usage.output_tokens??0}),this.emitFinalIfOpen();break}}}emitEvent(e){if(this.terminalState!=="open"&&e.state!=="final"&&e.state!=="error"&&e.state!=="aborted")return;const t={...e,runId:this.runId,seq:this.seq++};this.emit("agentEvent",t)}isApprovalLikeName(e){return typeof e!="string"?!1:/approval|permission|confirm|user[_-]?input|request[_-]?user/i.test(e)}emitApprovalPending(e){const t=typeof e.command=="string"?e.command:"",s=typeof e.title=="string"&&e.title.trim()?e.title:"\u9700\u8981\u7528\u6237\u786E\u8BA4",r=typeof e.prompt=="string"?e.prompt:typeof e.reason=="string"?e.reason:typeof e.message=="string"?e.message:t?`Codex \u8BF7\u6C42\u6267\u884C\u547D\u4EE4\uFF1A${t}`:"Codex \u6B63\u5728\u7B49\u5F85\u7528\u6237\u786E\u8BA4\u540E\u7EE7\u7EED\u3002";this.emitEvent({state:"approval-pending",approval:{title:s,content:r,source:"codex",actionHint:"\u5F53\u524D\u795E\u5FF5\u6682\u4E0D\u652F\u6301\u8FDC\u7A0B\u786E\u8BA4\uFF0C\u8BF7\u56DE\u5230\u8FD0\u884C Codex \u7684\u7535\u8111/\u7EC8\u7AEF\u5B8C\u6210\u786E\u8BA4\u3002"}})}async killProcess(){const e=this.process;if(this.appServerReady=null,this.activeTurnId=null,!e){this.rejectAllPending(new Error("codex app-server stopped"));return}this.process=null,this.clearForceCloseTimer(),e.kill("SIGTERM"),await new Promise(t=>{e.on("close",t),setTimeout(()=>{e.kill("SIGKILL"),t()},3e3)}),this.rejectAllPending(new Error("codex app-server stopped"))}rejectAllPending(e){for(const[t,s]of this.pendingRequests.entries())clearTimeout(s.timer),s.reject(e),this.pendingRequests.delete(t)}handleProcessClose(e,t){const s=w(t);if(e!==0&&e!==null){const r=s||`codex exited with code ${e}`;this.emitErrorIfOpen(r)}else B(s)?this.emitErrorIfOpen(s):this.emitFinalIfOpen();this.pendingUsage=void 0}emitFinalIfOpen(){this.terminalState==="open"&&(this.terminalState="final",this.emitEvent({state:"final",usage:this.pendingUsage,agentSessionId:this.agentSessionId??void 0}),this.killProcess().catch(()=>{}))}emitErrorIfOpen(e){this.terminalState==="open"&&(this.terminalState="error",this.emitEvent({state:"error",message:L(e)}),this.scheduleProcessClose())}resetRunState(){this.pendingUsage=void 0,this.terminalState="open",this.deltaItemIds.clear(),this.lastTransientStatus="",this.clearForceCloseTimer(),this.resetTextState()}emitTransientStatus(e){const t=D(e);!t||t===this.lastTransientStatus||this.terminalState!=="open"||(this.lastTransientStatus=t,this.emitEvent({state:"delta",text:`${t}
9
+ `,thinking:!0}))}resetTextState(){this.hasEmittedText=!1,this.textTail=""}emitText(e,t="delta"){if(!e)return;let s=V(e);s&&(t==="block"&&this.hasEmittedText&&!s.startsWith(`
10
10
  `)&&(this.textTail.endsWith(`
11
11
  `)?s=`
12
12
  ${s}`:this.textTail.endsWith(`
13
13
 
14
14
  `)||(s=`
15
15
 
16
- ${s}`)),this.hasEmittedText=!0,this.textTail=(this.textTail+s).slice(-4),this.emitEvent({state:"delta",text:s}))}scheduleProcessClose(){const e=this.process;!e||this.forceCloseTimer||(this.forceCloseTimer=setTimeout(()=>{this.forceCloseTimer=null,this.process===e&&(e.kill("SIGTERM"),setTimeout(()=>{this.process===e&&e.kill("SIGKILL")},1e3))},250))}clearForceCloseTimer(){this.forceCloseTimer&&(clearTimeout(this.forceCloseTimer),this.forceCloseTimer=null)}}P("codex",()=>new se);export{se as CodexAdapter,te as buildCodexQueueArgs,ee as buildCodexQueuedText,V as isCodexActiveWriterError,K as isCodexExcludeTurnsUnsupportedError,Q as isCodexPaginatedHistoryUnsupportedError,me as isCodexUnsupportedEffortError,fe as isMissingCodexRolloutError,ge as normalizeCodexModelId,ve as normalizeCodexReasoningEffort};
16
+ ${s}`)),this.hasEmittedText=!0,this.textTail=(this.textTail+s).slice(-4),this.emitEvent({state:"delta",text:s}))}scheduleProcessClose(){const e=this.process;!e||this.forceCloseTimer||(this.forceCloseTimer=setTimeout(()=>{this.forceCloseTimer=null,this.process===e&&(e.kill("SIGTERM"),setTimeout(()=>{this.process===e&&e.kill("SIGKILL")},1e3))},250))}clearForceCloseTimer(){this.forceCloseTimer&&(clearTimeout(this.forceCloseTimer),this.forceCloseTimer=null)}}q("codex",()=>new ie);export{ie as CodexAdapter,ne as buildCodexQueueArgs,re as buildCodexQueuedText,K as isCodexActiveWriterError,X as isCodexExcludeTurnsUnsupportedError,Q as isCodexPaginatedHistoryUnsupportedError,Y as isCodexProjectApiUnsupportedError,ve as isCodexUnsupportedEffortError,xe as isMissingCodexRolloutError,Ie as normalizeCodexModelId,ye as normalizeCodexReasoningEffort,Z as selectCodexProjectIdForWorkDir};
@@ -74,6 +74,12 @@ export declare function startDaemonProcess(opts?: {
74
74
  export declare function shouldReplaceRunningDaemon(status: Pick<DaemonStatus, 'pid' | 'running' | 'adopted'>, record?: {
75
75
  version?: string;
76
76
  } | null, currentVersion?: string): boolean;
77
+ export declare function isAutoStartServicePresent(platform?: Platform): boolean;
78
+ export declare function canReuseRunningDaemonForStart(status: Pick<DaemonStatus, 'pid' | 'running' | 'stale' | 'remoteAccessDisabled' | 'adopted'>, opts: {
79
+ api?: string;
80
+ }, servicePresent: boolean, record?: {
81
+ version?: string;
82
+ } | null, currentVersion?: string): boolean;
77
83
  /**
78
84
  * Install the platform-native auto-start service.
79
85
  * Returns true if the service was immediately started (so caller can skip startDaemonProcess).
@@ -1,13 +1,13 @@
1
- import a from"chalk";import o from"node:fs";import s from"node:path";import u from"node:os";import{execSync as c,spawn as ue}from"node:child_process";import{randomUUID as de}from"node:crypto";import{fileURLToPath as le}from"node:url";import{getShennianDir as pe,loadConfig as U,resolveShennianPath as h,saveConfig as me}from"../config/index.js";import{buildAugmentedPath as fe}from"../env-path.js";import{buildWindowsLauncherCommand as Se,buildWindowsScheduledTaskCommands as he,buildWindowsScheduledTaskXml as ge,buildWindowsStartupVbs as we,escapeXml as N,isWindowsCmdScript as ye,quoteCmdArg as j,quoteSystemdArg as ve,suspendWindowsScheduledTask as Ee}from"./daemon-windows.js";import{waitForDaemonReadyStatus as b}from"./daemon-start-wait.js";import{findRunningWindowsDaemonProcessIds as Pe,isShennianRunServiceCommand as Ie}from"./daemon-process.js";import{getCurrentVersion as ke}from"../upgrade/engine.js";import{buildWindowsLauncherCommand as En,buildWindowsScheduledTaskCommands as Pn,buildWindowsScheduledTaskSuspendCommands as In,buildWindowsScheduledTaskXml as kn,buildWindowsStartupVbs as An,suspendWindowsScheduledTask as Dn}from"./daemon-windows.js";import{isShennianRunServiceCommand as bn}from"./daemon-process.js";const Ae=s.dirname(le(import.meta.url)),f=pe(),g=h("daemon.pid"),d=h("daemon.log"),R=h("remote-access.disabled"),k=h("daemon-launcher.json"),W=s.resolve(Ae,"../../bin/shennian.js"),F=process.execPath,B=new Set(["PATH","HOME","USERPROFILE","HOMEDRIVE","HOMEPATH","USERNAME","USERDOMAIN","COMPUTERNAME","USER","LOGNAME","SHELL","TMPDIR","LANG","LC_ALL","LC_CTYPE","SSH_AUTH_SOCK","XDG_CONFIG_HOME","XDG_DATA_HOME","TEMP","TMP","APPDATA","LOCALAPPDATA","SHENNIAN_DESKTOP_SERVER_URL","SHENNIAN_WECHAT_CHANNEL_HELPER_DIR","SHENNIAN_HOME","SHENNIAN_NATIVE_FUSION_DISABLED","SHENNIAN_WECHAT_DELIVERABILITY_TRACE"]);function De(e){return B.has(e)}function l(){const e=u.platform();return e==="darwin"||e==="linux"||e==="win32"?e:"linux"}function T(e){const n=e.replace(/\\/g,"/").toLowerCase(),t=u.tmpdir().replace(/\\/g,"/").toLowerCase();return n.includes("/_npx/")||n.includes("/npm-cache/_npx/")||n.includes("/pnpm/dlx/")||n.startsWith(t.endsWith("/")?t:`${t}/`)}function Ne(e){return o.existsSync(e.scriptPath)&&!T(e.scriptPath)?{command:e.nodeExec,args:[e.scriptPath,"run-service"],mode:"direct"}:e.shennianCommandPath&&!T(e.shennianCommandPath)?{command:e.shennianCommandPath,args:["run-service"],mode:"global-shim"}:e.npxPath?{command:e.npxPath,args:["--yes","shennian","run-service"],mode:"npx"}:{command:e.nodeExec,args:[e.scriptPath,"run-service"],mode:"direct"}}function be(e){const n=e.trim();if(!n)return null;if(n.startsWith("{"))try{const r=JSON.parse(n),i=Number(r.pid);return!Number.isInteger(i)||i<=0?null:{pid:i,...typeof r.instanceId=="string"?{instanceId:r.instanceId}:{},...typeof r.version=="string"?{version:r.version}:{},...r.launcher==="desktop-managed"||r.launcher==="global-cli"||r.launcher==="unknown"?{launcher:r.launcher}:{},...typeof r.startedAt=="string"?{startedAt:r.startedAt}:{},...r.adopted===!0?{adopted:!0}:{}}}catch{return null}const t=parseInt(n,10);return isNaN(t)?null:{pid:t}}function A(){try{return be(o.readFileSync(g,"utf-8"))}catch{return null}}function v(e){try{return process.kill(e,0),!0}catch{return!1}}function V(e,n=3e3){const t=Date.now();for(;Date.now()-t<n;){if(!v(e))return!0;Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,100)}return!v(e)}function Re(){return o.existsSync(R)}function Te(){try{o.unlinkSync(R)}catch{}}function J(){return"global-cli"}function Le(){return`${process.pid}-${Date.now()}-${de()}`}function Oe(e,n,t={}){o.mkdirSync(f,{recursive:!0}),o.writeFileSync(g,JSON.stringify({pid:e,instanceId:n,...t.version?{version:t.version}:{},launcher:t.launcher??J(),...t.adopted?{adopted:!0}:{},startedAt:new Date().toISOString()},null,2))}function gn(e,n){const t=A();if(!(t?.pid!==e||t.instanceId!==n))try{o.unlinkSync(g)}catch{}}function G(e,n=J(),t){o.mkdirSync(f,{recursive:!0}),o.writeFileSync(k,JSON.stringify({pid:e,launcher:n,...t?{instanceId:t}:{},updatedAt:new Date().toISOString()},null,2))}function xe(e){if(e)try{if(JSON.parse(o.readFileSync(k,"utf-8")).instanceId!==e)return}catch{return}try{o.unlinkSync(k)}catch{}}function He(e){e&&G(e)}function _e(e,n){if(!e||!n)return"unknown";try{const t=JSON.parse(o.readFileSync(k,"utf-8"));if(t.pid!==e)return"unknown";if(t.launcher==="desktop-managed"||t.launcher==="global-cli")return t.launcher}catch{}return L(e)}function L(e){if(l()==="win32")return"unknown";try{const n=c(`ps -p ${e} -o command=`,{encoding:"utf-8",stdio:["ignore","pipe","ignore"],timeout:1e3,windowsHide:!0}).replace(/\\/g,"/");if(n.includes("/node_modules/shennian/")||n.includes("/bin/shennian")||n.includes(" shennian run-service")||n.includes(" shennian.js run-service"))return"global-cli"}catch{}return"unknown"}function K(e=process.pid){if(l()==="win32")return Pe(e);try{return c("ps -axo pid=,command=",{encoding:"utf-8",stdio:["ignore","pipe","ignore"],timeout:1e3,windowsHide:!0}).split(/\r?\n/).map(t=>{const r=t.trim().match(/^(\d+)\s+(.+)$/);if(!r)return null;const i=Number(r[1]),m=r[2];return!Number.isInteger(i)||i===e?null:Ie(m)?i:null}).filter(t=>typeof t=="number")}catch{return[]}}function $e(e=process.env,n=f,t=u.tmpdir(),r=s.join(u.homedir(),".shennian")){if(e.SHENNIAN_E2E_ISOLATED_DAEMON!=="1"||!e.SHENNIAN_HOME?.trim()||s.resolve(n)===s.resolve(r))return!1;const i=s.relative(s.resolve(t),s.resolve(n));return i!==""&&i!==".."&&!i.startsWith(`..${s.sep}`)&&!s.isAbsolute(i)||s.resolve(n)===s.resolve(e.SHENNIAN_HOME)}function Ce(e=process.env,n=f){return!$e(e,n)}function S(e={}){let n=A(),t=n?.pid??null,r=t!==null&&v(t),i=t!==null&&!r,m=n?.adopted===!0;const y=U();if(i&&e.cleanupStale){try{o.unlinkSync(g)}catch{}n=null,t=null,r=!1,i=!1}if(!r&&Ce()){const w=K()[0];if(w&&(t=w,r=!0,i=!1,m=!0,e.cleanupStale)){const M=Le();Oe(w,M,{launcher:L(w),adopted:!0}),G(w,L(w)),n={pid:w,instanceId:M,adopted:!0}}}return{running:r,pid:t,stale:i,remoteAccessDisabled:Re(),launcher:_e(t,r),platform:l(),shennianDir:f,pidFile:g,logFile:d,...n?.instanceId?{instanceId:n.instanceId}:{},...m?{adopted:!0}:{},...y.machineId?{machineId:y.machineId}:{},paired:!!(y.machineToken&&y.machineId),...y.serverUrl?{serverUrl:y.serverUrl}:{}}}function E(e){process.stdout.write(`${JSON.stringify(e,null,2)}
2
- `)}function X(e){const n=e?.trim();if(!n)return;const t=U();t.serverUrl=n,me(t)}function q(e){return e?.trim()||process.env.SHENNIAN_DESKTOP_SERVER_URL?.trim()||void 0}const O="com.shennian.agent",p=s.join(u.homedir(),"Library/LaunchAgents",`${O}.plist`),Me=s.join(u.homedir(),"AppData","Roaming","Microsoft","Windows","Start Menu","Programs","Startup"),x=h("autostart.cmd"),H=h("autostart.vbs"),_=h("autostart.xml"),Ue=s.join(Me,"ShennianAgent.vbs");function Y(e){const n=l()==="win32"?`where ${e}`:`command -v ${e}`;try{return c(n,{stdio:["ignore","pipe","pipe"],encoding:"utf-8",windowsHide:!0}).split(/\r?\n/).map(i=>i.trim()).find(Boolean)??null}catch{return null}}function je(){const e=l()==="win32"?"npx.cmd":"npx",n=s.join(s.dirname(F),e);return o.existsSync(n)?n:Y("npx")}function P(){return Ne({nodeExec:F,scriptPath:W,shennianCommandPath:Y("shennian"),npxPath:je()})}function z(){try{c(`schtasks /delete /tn "${I}" /f`,{stdio:"pipe",windowsHide:!0})}catch{}}function Q(){try{o.unlinkSync(Ue)}catch{}}function We(){const e=process.env.USERDOMAIN?.trim(),n=process.env.COMPUTERNAME?.trim(),t=process.env.USERNAME?.trim()||u.userInfo().username;return e&&e.toUpperCase()!=="WORKGROUP"?`${e}\\${t}`:n?`${n}\\${t}`:t}function Fe(e){if(l()==="win32"&&ye(e.command)){const n=[e.command,...e.args].map(j).join(" ");return{command:process.env.ComSpec||"cmd.exe",args:["/d","/s","/c",`"${n}"`],windowsVerbatimArguments:!0}}return{command:e.command,args:e.args}}function Be(e,n,t=process.env){return{detached:!0,stdio:["ignore",n,n],env:t,windowsHide:!0,...e.windowsVerbatimArguments?{windowsVerbatimArguments:!0}:{}}}function Ve(){const e=P();o.writeFileSync(x,Se(e,d)),o.writeFileSync(H,we(x)),Q(),z();const n=ge({userId:We(),command:s.join(process.env.SystemRoot||"C:\\Windows","System32","wscript.exe"),arguments:[j(H)]});o.writeFileSync(_,n,"utf8");const t=he({taskName:I,xmlPath:_});try{return c(t.create,{stdio:"pipe",windowsHide:!0}),c(t.run,{stdio:"pipe",windowsHide:!0}),!0}catch{return!1}}function Je(){Ee(I,e=>{c(e,{stdio:"pipe",windowsHide:!0})})}function Z(){const e={};for(const n of B)process.env[n]&&(e[n]=process.env[n]);if(e.HOME??=u.homedir(),e.PATH=fe({pathValue:e.PATH,env:e}),e.USER??=u.userInfo().username,l()==="win32"){const n=process.env.TEMP||process.env.TMP||u.tmpdir(),t=u.homedir();e.USERPROFILE??=t;const r=s.win32.parse(t);e.HOMEDRIVE??=r.root.replace(/[\\/]$/,"")||process.env.HOMEDRIVE||"",e.HOMEPATH??=t.slice((e.HOMEDRIVE||"").length)||process.env.HOMEPATH||"",e.USERNAME??=process.env.USERNAME||u.userInfo().username,e.COMPUTERNAME??=process.env.COMPUTERNAME||"",e.TMPDIR??=n,e.TEMP??=n,e.TMP??=n}else e.TMPDIR??="/tmp";return e}function Ge(){const e=Z(),n=P(),t=Object.entries(e).map(([i,m])=>` <key>${N(i)}</key>
3
- <string>${N(m)}</string>`).join(`
4
- `),r=[n.command,...n.args].map(i=>` <string>${N(i)}</string>`).join(`
1
+ import c from"chalk";import i from"node:fs";import s from"node:path";import u from"node:os";import{execSync as a,spawn as de}from"node:child_process";import{randomUUID as le}from"node:crypto";import{fileURLToPath as pe}from"node:url";import{getShennianDir as me,loadConfig as j,resolveShennianPath as h,saveConfig as fe}from"../config/index.js";import{buildAugmentedPath as Se}from"../env-path.js";import{buildWindowsLauncherCommand as he,buildWindowsScheduledTaskCommands as ge,buildWindowsScheduledTaskXml as we,buildWindowsStartupVbs as ye,escapeXml as T,isWindowsCmdScript as ve,quoteCmdArg as W,quoteSystemdArg as Ee,suspendWindowsScheduledTask as Pe}from"./daemon-windows.js";import{waitForDaemonReadyStatus as L}from"./daemon-start-wait.js";import{findRunningWindowsDaemonProcessIds as Ae,isShennianRunServiceCommand as Ie}from"./daemon-process.js";import{getCurrentVersion as F}from"../upgrade/engine.js";import{buildWindowsLauncherCommand as An,buildWindowsScheduledTaskCommands as In,buildWindowsScheduledTaskSuspendCommands as kn,buildWindowsScheduledTaskXml as Dn,buildWindowsStartupVbs as Nn,suspendWindowsScheduledTask as bn}from"./daemon-windows.js";import{isShennianRunServiceCommand as Tn}from"./daemon-process.js";const ke=s.dirname(pe(import.meta.url)),S=me(),g=h("daemon.pid"),d=h("daemon.log"),x=h("remote-access.disabled"),D=h("daemon-launcher.json"),B=s.resolve(ke,"../../bin/shennian.js"),V=process.execPath,J=new Set(["PATH","HOME","USERPROFILE","HOMEDRIVE","HOMEPATH","USERNAME","USERDOMAIN","COMPUTERNAME","USER","LOGNAME","SHELL","TMPDIR","LANG","LC_ALL","LC_CTYPE","SSH_AUTH_SOCK","XDG_CONFIG_HOME","XDG_DATA_HOME","TEMP","TMP","APPDATA","LOCALAPPDATA","SHENNIAN_DESKTOP_SERVER_URL","SHENNIAN_WECHAT_CHANNEL_HELPER_DIR","SHENNIAN_HOME","SHENNIAN_NATIVE_FUSION_DISABLED","SHENNIAN_WECHAT_DELIVERABILITY_TRACE"]);function De(e){return J.has(e)}function l(){const e=u.platform();return e==="darwin"||e==="linux"||e==="win32"?e:"linux"}function O(e){const n=e.replace(/\\/g,"/").toLowerCase(),t=u.tmpdir().replace(/\\/g,"/").toLowerCase();return n.includes("/_npx/")||n.includes("/npm-cache/_npx/")||n.includes("/pnpm/dlx/")||n.startsWith(t.endsWith("/")?t:`${t}/`)}function Ne(e){return i.existsSync(e.scriptPath)&&!O(e.scriptPath)?{command:e.nodeExec,args:[e.scriptPath,"run-service"],mode:"direct"}:e.shennianCommandPath&&!O(e.shennianCommandPath)?{command:e.shennianCommandPath,args:["run-service"],mode:"global-shim"}:e.npxPath?{command:e.npxPath,args:["--yes","shennian","run-service"],mode:"npx"}:{command:e.nodeExec,args:[e.scriptPath,"run-service"],mode:"direct"}}function be(e){const n=e.trim();if(!n)return null;if(n.startsWith("{"))try{const r=JSON.parse(n),o=Number(r.pid);return!Number.isInteger(o)||o<=0?null:{pid:o,...typeof r.instanceId=="string"?{instanceId:r.instanceId}:{},...typeof r.version=="string"?{version:r.version}:{},...r.launcher==="desktop-managed"||r.launcher==="global-cli"||r.launcher==="unknown"?{launcher:r.launcher}:{},...typeof r.startedAt=="string"?{startedAt:r.startedAt}:{},...r.adopted===!0?{adopted:!0}:{}}}catch{return null}const t=parseInt(n,10);return isNaN(t)?null:{pid:t}}function P(){try{return be(i.readFileSync(g,"utf-8"))}catch{return null}}function E(e){try{return process.kill(e,0),!0}catch{return!1}}function G(e,n=3e3){const t=Date.now();for(;Date.now()-t<n;){if(!E(e))return!0;Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,100)}return!E(e)}function Re(){return i.existsSync(x)}function Te(){try{i.unlinkSync(x)}catch{}}function K(){return"global-cli"}function Le(){return`${process.pid}-${Date.now()}-${le()}`}function xe(e,n,t={}){i.mkdirSync(S,{recursive:!0}),i.writeFileSync(g,JSON.stringify({pid:e,instanceId:n,...t.version?{version:t.version}:{},launcher:t.launcher??K(),...t.adopted?{adopted:!0}:{},startedAt:new Date().toISOString()},null,2))}function yn(e,n){const t=P();if(!(t?.pid!==e||t.instanceId!==n))try{i.unlinkSync(g)}catch{}}function X(e,n=K(),t){i.mkdirSync(S,{recursive:!0}),i.writeFileSync(D,JSON.stringify({pid:e,launcher:n,...t?{instanceId:t}:{},updatedAt:new Date().toISOString()},null,2))}function Oe(e){if(e)try{if(JSON.parse(i.readFileSync(D,"utf-8")).instanceId!==e)return}catch{return}try{i.unlinkSync(D)}catch{}}function He(e){e&&X(e)}function _e(e,n){if(!e||!n)return"unknown";try{const t=JSON.parse(i.readFileSync(D,"utf-8"));if(t.pid!==e)return"unknown";if(t.launcher==="desktop-managed"||t.launcher==="global-cli")return t.launcher}catch{}return H(e)}function H(e){if(l()==="win32")return"unknown";try{const n=a(`ps -p ${e} -o command=`,{encoding:"utf-8",stdio:["ignore","pipe","ignore"],timeout:1e3,windowsHide:!0}).replace(/\\/g,"/");if(n.includes("/node_modules/shennian/")||n.includes("/bin/shennian")||n.includes(" shennian run-service")||n.includes(" shennian.js run-service"))return"global-cli"}catch{}return"unknown"}function q(e=process.pid){if(l()==="win32")return Ae(e);try{return a("ps -axo pid=,command=",{encoding:"utf-8",stdio:["ignore","pipe","ignore"],timeout:1e3,windowsHide:!0}).split(/\r?\n/).map(t=>{const r=t.trim().match(/^(\d+)\s+(.+)$/);if(!r)return null;const o=Number(r[1]),f=r[2];return!Number.isInteger(o)||o===e?null:Ie(f)?o:null}).filter(t=>typeof t=="number")}catch{return[]}}function $e(e=process.env,n=S,t=u.tmpdir(),r=s.join(u.homedir(),".shennian")){if(e.SHENNIAN_E2E_ISOLATED_DAEMON!=="1"||!e.SHENNIAN_HOME?.trim()||s.resolve(n)===s.resolve(r))return!1;const o=s.relative(s.resolve(t),s.resolve(n));return o!==""&&o!==".."&&!o.startsWith(`..${s.sep}`)&&!s.isAbsolute(o)||s.resolve(n)===s.resolve(e.SHENNIAN_HOME)}function Ce(e=process.env,n=S){return!$e(e,n)}function m(e={}){let n=P(),t=n?.pid??null,r=t!==null&&E(t),o=t!==null&&!r,f=n?.adopted===!0;const v=j();if(o&&e.cleanupStale){try{i.unlinkSync(g)}catch{}n=null,t=null,r=!1,o=!1}if(!r&&Ce()){const w=q()[0];if(w&&(t=w,r=!0,o=!1,f=!0,e.cleanupStale)){const U=Le();xe(w,U,{launcher:H(w),adopted:!0}),X(w,H(w)),n={pid:w,instanceId:U,adopted:!0}}}return{running:r,pid:t,stale:o,remoteAccessDisabled:Re(),launcher:_e(t,r),platform:l(),shennianDir:S,pidFile:g,logFile:d,...n?.instanceId?{instanceId:n.instanceId}:{},...f?{adopted:!0}:{},...v.machineId?{machineId:v.machineId}:{},paired:!!(v.machineToken&&v.machineId),...v.serverUrl?{serverUrl:v.serverUrl}:{}}}function y(e){process.stdout.write(`${JSON.stringify(e,null,2)}
2
+ `)}function Y(e){const n=e?.trim();if(!n)return;const t=j();t.serverUrl=n,fe(t)}function z(e){return e?.trim()||process.env.SHENNIAN_DESKTOP_SERVER_URL?.trim()||void 0}const _="com.shennian.agent",p=s.join(u.homedir(),"Library/LaunchAgents",`${_}.plist`),Me=s.join(u.homedir(),"AppData","Roaming","Microsoft","Windows","Start Menu","Programs","Startup"),N=h("autostart.cmd"),b=h("autostart.vbs"),R=h("autostart.xml"),Ue=s.join(Me,"ShennianAgent.vbs");function Q(e){const n=l()==="win32"?`where ${e}`:`command -v ${e}`;try{return a(n,{stdio:["ignore","pipe","pipe"],encoding:"utf-8",windowsHide:!0}).split(/\r?\n/).map(o=>o.trim()).find(Boolean)??null}catch{return null}}function je(){const e=l()==="win32"?"npx.cmd":"npx",n=s.join(s.dirname(V),e);return i.existsSync(n)?n:Q("npx")}function A(){return Ne({nodeExec:V,scriptPath:B,shennianCommandPath:Q("shennian"),npxPath:je()})}function Z(){try{a(`schtasks /delete /tn "${k}" /f`,{stdio:"pipe",windowsHide:!0})}catch{}}function ee(){try{i.unlinkSync(Ue)}catch{}}function We(){const e=process.env.USERDOMAIN?.trim(),n=process.env.COMPUTERNAME?.trim(),t=process.env.USERNAME?.trim()||u.userInfo().username;return e&&e.toUpperCase()!=="WORKGROUP"?`${e}\\${t}`:n?`${n}\\${t}`:t}function Fe(e){if(l()==="win32"&&ve(e.command)){const n=[e.command,...e.args].map(W).join(" ");return{command:process.env.ComSpec||"cmd.exe",args:["/d","/s","/c",`"${n}"`],windowsVerbatimArguments:!0}}return{command:e.command,args:e.args}}function Be(e,n,t=process.env){return{detached:!0,stdio:["ignore",n,n],env:t,windowsHide:!0,...e.windowsVerbatimArguments?{windowsVerbatimArguments:!0}:{}}}function Ve(){const e=A();i.writeFileSync(N,he(e,d)),i.writeFileSync(b,ye(N)),ee(),Z();const n=we({userId:We(),command:s.join(process.env.SystemRoot||"C:\\Windows","System32","wscript.exe"),arguments:[W(b)]});i.writeFileSync(R,n,"utf8");const t=ge({taskName:k,xmlPath:R});try{return a(t.create,{stdio:"pipe",windowsHide:!0}),a(t.run,{stdio:"pipe",windowsHide:!0}),!0}catch{return!1}}function Je(){Pe(k,e=>{a(e,{stdio:"pipe",windowsHide:!0})})}function ne(){const e={};for(const n of J)process.env[n]&&(e[n]=process.env[n]);if(e.HOME??=u.homedir(),e.PATH=Se({pathValue:e.PATH,env:e}),e.USER??=u.userInfo().username,l()==="win32"){const n=process.env.TEMP||process.env.TMP||u.tmpdir(),t=u.homedir();e.USERPROFILE??=t;const r=s.win32.parse(t);e.HOMEDRIVE??=r.root.replace(/[\\/]$/,"")||process.env.HOMEDRIVE||"",e.HOMEPATH??=t.slice((e.HOMEDRIVE||"").length)||process.env.HOMEPATH||"",e.USERNAME??=process.env.USERNAME||u.userInfo().username,e.COMPUTERNAME??=process.env.COMPUTERNAME||"",e.TMPDIR??=n,e.TEMP??=n,e.TMP??=n}else e.TMPDIR??="/tmp";return e}function Ge(){const e=ne(),n=A(),t=Object.entries(e).map(([o,f])=>` <key>${T(o)}</key>
3
+ <string>${T(f)}</string>`).join(`
4
+ `),r=[n.command,...n.args].map(o=>` <string>${T(o)}</string>`).join(`
5
5
  `);return`<?xml version="1.0" encoding="UTF-8"?>
6
6
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
7
7
  <plist version="1.0">
8
8
  <dict>
9
9
  <key>Label</key>
10
- <string>${O}</string>
10
+ <string>${_}</string>
11
11
  <key>ProgramArguments</key>
12
12
  <array>
13
13
  ${r}
@@ -27,8 +27,8 @@ ${t}
27
27
  <key>StandardErrorPath</key>
28
28
  <string>${d}</string>
29
29
  </dict>
30
- </plist>`}const D=s.join(u.homedir(),".config/systemd/user/shennian.service");function Ke(){const e=Z(),n=P(),t=Object.entries(e).map(([i,m])=>`Environment=${i}=${m}`).join(`
31
- `),r=[n.command,...n.args].map(ve).join(" ");return`[Unit]
30
+ </plist>`}const I=s.join(u.homedir(),".config/systemd/user/shennian.service");function Ke(){const e=ne(),n=A(),t=Object.entries(e).map(([o,f])=>`Environment=${o}=${f}`).join(`
31
+ `),r=[n.command,...n.args].map(Ee).join(" ");return`[Unit]
32
32
  Description=Shennian Agent Daemon
33
33
  After=network.target
34
34
 
@@ -41,6 +41,6 @@ StandardOutput=append:${d}
41
41
  StandardError=append:${d}
42
42
 
43
43
  [Install]
44
- WantedBy=default.target`}const I="ShennianAgent";function Xe(e){c(e,{stdio:"pipe",windowsHide:!0})}function qe(){const e=process.getuid?.();if(typeof e=="number")return e;const n=u.userInfo();return typeof n.uid=="number"?n.uid:n.username}function ee(e=qe()){return`gui/${e}/${O}`}function Ye(e=Xe){try{return e(`launchctl unload "${p}" 2>/dev/null; launchctl load -w "${p}"`),e(`launchctl kickstart -k "${ee()}"`),!0}catch{try{return e(`launchctl load -w "${p}"`),e(`launchctl kickstart -k "${ee()}"`),!0}catch{return!1}}}function ze(){o.mkdirSync(f,{recursive:!0});const e={};for(const[n,t]of Object.entries(process.env))t!==void 0&&De(n)&&(e[n]=t);o.writeFileSync(h("env.json"),JSON.stringify(e,null,2))}function ne(e={}){o.mkdirSync(f,{recursive:!0});const n=S({cleanupStale:!0});if(n.pid!==null&&n.running&&!te(n)){e.quiet||console.log(a.green(`\u2713 Background service already running (PID ${n.pid})`));return}re(n);const t=o.openSync(d,"a"),r=Fe(P()),i=ue(r.command,r.args,Be(r,t));i.unref(),o.closeSync(t),He(i.pid),e.quiet||(console.log(a.green(`\u2713 Background service started (PID ${i.pid})`)),console.log(a.gray(` Logs: ${d}`)))}function te(e,n=A(),t=ke()){return e.pid===null||!e.running?!1:e.adopted===!0||n?.version!==t}function re(e=S({cleanupStale:!0})){if(!(!te(e)||e.pid===null))try{process.kill(e.pid,"SIGTERM"),V(e.pid,5e3)||(process.kill(e.pid,"SIGKILL"),V(e.pid,2e3));try{A()?.pid===e.pid&&o.unlinkSync(g)}catch{}}catch{}}function oe(){o.mkdirSync(f,{recursive:!0});const e=l();switch(P().mode==="direct"&&T(W)&&console.warn(a.yellow("\u26A0 Warning: Current CLI path is temporary (npx). Auto-start may not work after reboot.\n Run `npm install -g shennian@latest` for reliable auto-start.")),e){case"darwin":{const t=s.dirname(p);return o.mkdirSync(t,{recursive:!0}),o.writeFileSync(p,Ge()),Ye()}case"linux":{const t=s.dirname(D);o.mkdirSync(t,{recursive:!0}),o.writeFileSync(D,Ke());try{c("systemctl --user daemon-reload && systemctl --user enable shennian",{stdio:"pipe",windowsHide:!0})}catch{return!1}try{c(`loginctl enable-linger ${u.userInfo().username}`,{stdio:"pipe",windowsHide:!0})}catch{}try{return c("systemctl --user restart shennian",{stdio:"pipe",windowsHide:!0}),!0}catch{return!1}}case"win32":return Ve()}}function Qe(){const n=S({cleanupStale:!0}).pid;if(n===null)return{};if(!v(n)){try{o.unlinkSync(g)}catch{}return{stalePid:n}}try{return process.kill(n,"SIGTERM"),{stoppedPid:n}}catch(t){return{error:t instanceof Error?t.message:String(t)}}}async function ie(e=5e3){const n=Qe();if(!n.stoppedPid)return n;if(!await C(n.stoppedPid,e))try{process.kill(n.stoppedPid,"SIGKILL"),await C(n.stoppedPid,2e3)}catch(r){return{...n,error:r instanceof Error?r.message:String(r)}}try{o.unlinkSync(g)}catch{}xe();for(const r of K())if(r!==n.stoppedPid)try{process.kill(r,"SIGTERM"),await C(r,2e3)}catch{}return n}function Ze(e={}){X(q(e.api)),Te(),re(),oe()||ne({quiet:e.json}),e.json?E(b(()=>S({cleanupStale:!0}))):console.log(a.green("\u2713 Background service started"))}async function en(e={}){switch(o.mkdirSync(f,{recursive:!0}),o.writeFileSync(R,new Date().toISOString()),l()){case"darwin":{if(o.existsSync(p))try{c(`launchctl unload -w "${p}"`,{stdio:"pipe",windowsHide:!0})}catch{}break}case"linux":{try{c("systemctl --user disable --now shennian",{stdio:"pipe",windowsHide:!0})}catch{}break}case"win32":{try{c(`schtasks /change /tn "${I}" /disable`,{stdio:"pipe",windowsHide:!0})}catch{}try{c(`schtasks /end /tn "${I}"`,{stdio:"pipe",windowsHide:!0})}catch{}break}}const t=await ie(),r=S({cleanupStale:!0});if(e.json){E(t.error?{...r,error:t.error}:r);return}t.error?console.error(a.red(`\u2717 Failed to stop: ${t.error}`)):console.log(a.green("\u2713 Background service stopped"))}function se(e){ze(),Ze(e)}async function $(e={}){await en(e)}async function C(e,n=5e3){const t=Date.now();for(;Date.now()-t<n;){if(!v(e))return!0;await new Promise(r=>setTimeout(r,100))}return!v(e)}async function nn(e={}){X(q(e.api));const n=l();n==="win32"&&Je();const t=await ie();if(t.error){e.json?E({...S(),error:t.error}):console.error(a.red(`\u2717 Failed to stop: ${t.error}`));return}if(t.stoppedPid!==void 0?e.json||console.log(a.green(`\u2713 Background service stopped (PID ${t.stoppedPid})`)):t.stalePid!==void 0?e.json||console.log(a.yellow(`\u26A0 Service process no longer exists, cleaned up stale PID ${t.stalePid}`)):e.json||console.log(a.gray("\u25CF Background service not running, starting it now")),(n==="linux"||n==="win32")&&oe()){e.json&&E(b(()=>S({cleanupStale:!0})));return}ne({quiet:e.json}),e.json&&E(b(()=>S({cleanupStale:!0})))}function ae(e={}){const n=S({cleanupStale:!0});if(e.json){E(n);return}if(n.pid===null){console.log(a.gray("\u25CF Background service not running"));return}n.running?(console.log(a.green(`\u25CF Background service running (PID ${n.pid})`)),console.log(a.gray(` Logs: ${d}`))):console.log(a.yellow(`\u25CF Background service stopped (PID ${n.pid} is stale)`))}function ce(e){if(!o.existsSync(d)){console.log(a.gray("No logs yet"));return}try{const n=c(u.platform()==="win32"?`powershell Get-Content -Tail ${e.lines} "${d}"`:`tail -n ${e.lines} "${d}"`,{encoding:"utf-8",windowsHide:!0});process.stdout.write(n)}catch{const n=o.readFileSync(d,"utf-8").split(`
44
+ WantedBy=default.target`}const k="ShennianAgent";function Xe(e){a(e,{stdio:"pipe",windowsHide:!0})}function qe(){const e=process.getuid?.();if(typeof e=="number")return e;const n=u.userInfo();return typeof n.uid=="number"?n.uid:n.username}function te(e=qe()){return`gui/${e}/${_}`}function Ye(e=Xe){try{return e(`launchctl unload "${p}" 2>/dev/null; launchctl load -w "${p}"`),e(`launchctl kickstart -k "${te()}"`),!0}catch{try{return e(`launchctl load -w "${p}"`),e(`launchctl kickstart -k "${te()}"`),!0}catch{return!1}}}function ze(){i.mkdirSync(S,{recursive:!0});const e={};for(const[n,t]of Object.entries(process.env))t!==void 0&&De(n)&&(e[n]=t);i.writeFileSync(h("env.json"),JSON.stringify(e,null,2))}function re(e={}){i.mkdirSync(S,{recursive:!0});const n=m({cleanupStale:!0});if(n.pid!==null&&n.running&&!$(n)){e.quiet||console.log(c.green(`\u2713 Background service already running (PID ${n.pid})`));return}ie(n);const t=i.openSync(d,"a"),r=Fe(A()),o=de(r.command,r.args,Be(r,t));o.unref(),i.closeSync(t),He(o.pid),e.quiet||(console.log(c.green(`\u2713 Background service started (PID ${o.pid})`)),console.log(c.gray(` Logs: ${d}`)))}function $(e,n=P(),t=F()){return e.pid===null||!e.running?!1:e.adopted===!0||n?.version!==t}function Qe(e=l()){switch(e){case"darwin":return i.existsSync(p);case"linux":return i.existsSync(I);case"win32":return i.existsSync(N)&&i.existsSync(b)&&i.existsSync(R)}}function Ze(e,n,t,r=P(),o=F()){return!n.api?.trim()&&e.pid!==null&&e.running&&!e.stale&&!e.remoteAccessDisabled&&t&&!$(e,r,o)}function ie(e=m({cleanupStale:!0})){if(!(!$(e)||e.pid===null))try{process.kill(e.pid,"SIGTERM"),G(e.pid,5e3)||(process.kill(e.pid,"SIGKILL"),G(e.pid,2e3));try{P()?.pid===e.pid&&i.unlinkSync(g)}catch{}}catch{}}function oe(){i.mkdirSync(S,{recursive:!0});const e=l();switch(A().mode==="direct"&&O(B)&&console.warn(c.yellow("\u26A0 Warning: Current CLI path is temporary (npx). Auto-start may not work after reboot.\n Run `npm install -g shennian@latest` for reliable auto-start.")),e){case"darwin":{const t=s.dirname(p);return i.mkdirSync(t,{recursive:!0}),i.writeFileSync(p,Ge()),Ye()}case"linux":{const t=s.dirname(I);i.mkdirSync(t,{recursive:!0}),i.writeFileSync(I,Ke());try{a("systemctl --user daemon-reload && systemctl --user enable shennian",{stdio:"pipe",windowsHide:!0})}catch{return!1}try{a(`loginctl enable-linger ${u.userInfo().username}`,{stdio:"pipe",windowsHide:!0})}catch{}try{return a("systemctl --user restart shennian",{stdio:"pipe",windowsHide:!0}),!0}catch{return!1}}case"win32":return Ve()}}function en(){const n=m({cleanupStale:!0}).pid;if(n===null)return{};if(!E(n)){try{i.unlinkSync(g)}catch{}return{stalePid:n}}try{return process.kill(n,"SIGTERM"),{stoppedPid:n}}catch(t){return{error:t instanceof Error?t.message:String(t)}}}async function se(e=5e3){const n=en();if(!n.stoppedPid)return n;if(!await M(n.stoppedPid,e))try{process.kill(n.stoppedPid,"SIGKILL"),await M(n.stoppedPid,2e3)}catch(r){return{...n,error:r instanceof Error?r.message:String(r)}}try{i.unlinkSync(g)}catch{}Oe();for(const r of q())if(r!==n.stoppedPid)try{process.kill(r,"SIGTERM"),await M(r,2e3)}catch{}return n}function nn(e={}){Y(z(e.api)),Te(),ie(),oe()||re({quiet:e.json}),e.json?y(L(()=>m({cleanupStale:!0}))):console.log(c.green("\u2713 Background service started"))}async function tn(e={}){switch(i.mkdirSync(S,{recursive:!0}),i.writeFileSync(x,new Date().toISOString()),l()){case"darwin":{if(i.existsSync(p))try{a(`launchctl unload -w "${p}"`,{stdio:"pipe",windowsHide:!0})}catch{}break}case"linux":{try{a("systemctl --user disable --now shennian",{stdio:"pipe",windowsHide:!0})}catch{}break}case"win32":{try{a(`schtasks /change /tn "${k}" /disable`,{stdio:"pipe",windowsHide:!0})}catch{}try{a(`schtasks /end /tn "${k}"`,{stdio:"pipe",windowsHide:!0})}catch{}break}}const t=await se(),r=m({cleanupStale:!0});if(e.json){y(t.error?{...r,error:t.error}:r);return}t.error?console.error(c.red(`\u2717 Failed to stop: ${t.error}`)):console.log(c.green("\u2713 Background service stopped"))}function ce(e){const n=m({cleanupStale:!0});if(Ze(n,e,Qe())){e.json?y(n):console.log(c.green(`\u2713 Background service already running (PID ${n.pid})`));return}ze(),nn(e)}async function C(e={}){await tn(e)}async function M(e,n=5e3){const t=Date.now();for(;Date.now()-t<n;){if(!E(e))return!0;await new Promise(r=>setTimeout(r,100))}return!E(e)}async function rn(e={}){Y(z(e.api));const n=l();n==="win32"&&Je();const t=await se();if(t.error){e.json?y({...m(),error:t.error}):console.error(c.red(`\u2717 Failed to stop: ${t.error}`));return}if(t.stoppedPid!==void 0?e.json||console.log(c.green(`\u2713 Background service stopped (PID ${t.stoppedPid})`)):t.stalePid!==void 0?e.json||console.log(c.yellow(`\u26A0 Service process no longer exists, cleaned up stale PID ${t.stalePid}`)):e.json||console.log(c.gray("\u25CF Background service not running, starting it now")),(n==="linux"||n==="win32")&&oe()){e.json&&y(L(()=>m({cleanupStale:!0})));return}re({quiet:e.json}),e.json&&y(L(()=>m({cleanupStale:!0})))}function ae(e={}){const n=m({cleanupStale:!0});if(e.json){y(n);return}if(n.pid===null){console.log(c.gray("\u25CF Background service not running"));return}n.running?(console.log(c.green(`\u25CF Background service running (PID ${n.pid})`)),console.log(c.gray(` Logs: ${d}`))):console.log(c.yellow(`\u25CF Background service stopped (PID ${n.pid} is stale)`))}function ue(e){if(!i.existsSync(d)){console.log(c.gray("No logs yet"));return}try{const n=a(u.platform()==="win32"?`powershell Get-Content -Tail ${e.lines} "${d}"`:`tail -n ${e.lines} "${d}"`,{encoding:"utf-8",windowsHide:!0});process.stdout.write(n)}catch{const n=i.readFileSync(d,"utf-8").split(`
45
45
  `).slice(-e.lines);console.log(n.join(`
46
- `))}}async function tn(){switch(await $(),l()){case"darwin":{if(o.existsSync(p)){try{c(`launchctl unload -w "${p}"`,{stdio:"pipe",windowsHide:!0})}catch{}o.unlinkSync(p),console.log(a.green("\u2713 Auto-start uninstalled (launchd)"))}break}case"linux":{try{c("systemctl --user disable --now shennian",{stdio:"pipe",windowsHide:!0})}catch{}if(o.existsSync(D)){o.unlinkSync(D);try{c("systemctl --user daemon-reload",{stdio:"pipe",windowsHide:!0})}catch{}}console.log(a.green("\u2713 Auto-start uninstalled (systemd)"));break}case"win32":{z();try{o.unlinkSync(x)}catch{}try{o.unlinkSync(H)}catch{}try{o.unlinkSync(_)}catch{}Q(),console.log(a.green("\u2713 Auto-start uninstalled (Task Scheduler)"));break}}}function wn(e){e.command("start").description("Start the background service").option("--json","Print machine-readable status JSON").option("--api <url>","Server URL override").action(r=>se(r)),e.command("stop").description("Stop the background service").option("--json","Print machine-readable status JSON").action(r=>$(r)),e.command("status").description("Show background service status").option("--json","Print machine-readable status JSON").action(r=>ae(r)),e.command("logs").description("Show recent logs").option("-n, --lines <n>","Number of lines","50").action(r=>ce({lines:parseInt(r.lines,10)}));const n=e.command("daemon",{hidden:!0}).description("Deprecated: use top-level start/stop/status/logs"),t=(r,i)=>{i||console.error(a.yellow(`\u26A0 Deprecated command. Use \`shennian ${r}\` instead.`))};n.command("start").description("Start the background service").option("--json","Print machine-readable status JSON").option("--api <url>","Server URL override").action(r=>{t("start",r.json),se(r)}),n.command("stop").description("Stop the background service").option("--json","Print machine-readable status JSON").action(async r=>{t("stop",r.json),await $(r)}),n.command("restart").description("Restart the background service").option("--json","Print machine-readable status JSON").option("--api <url>","Server URL override").action(r=>{t("stop && shennian start",r.json),nn(r)}),n.command("status").description("Show background service status").option("--json","Print machine-readable status JSON").action(r=>{t("status",r.json),ae(r)}),n.command("logs").description("Show recent logs").option("-n, --lines <n>","Number of lines","50").action(r=>{t("logs"),ce({lines:parseInt(r.lines,10)})}),n.command("uninstall").description("Uninstall auto-start service").action(tn)}export{Be as buildDaemonSpawnOptions,En as buildWindowsLauncherCommand,Pn as buildWindowsScheduledTaskCommands,In as buildWindowsScheduledTaskSuspendCommands,kn as buildWindowsScheduledTaskXml,An as buildWindowsStartupVbs,Z as captureEnvForService,xe as clearDaemonLauncher,gn as clearDaemonPidIfOwner,Te as clearRemoteAccessDisabled,Le as createDaemonInstanceId,K as findRunningDaemonProcessIds,J as getCurrentProcessDaemonLauncher,S as getDaemonStatus,ee as getLaunchdServiceTarget,oe as installService,T as isEphemeralCliPath,$e as isIsolatedDaemonE2eInstance,Re as isRemoteAccessDisabled,De as isSafeSnapshotEnvKey,bn as isShennianRunServiceCommand,He as recordStartedDaemon,wn as registerDaemonCommand,Ye as reloadLaunchdAgent,Ne as resolveServiceLaunchSpec,ze as saveEnvSnapshot,Ce as shouldDiscoverGlobalDaemonProcesses,te as shouldReplaceRunningDaemon,ne as startDaemonProcess,Dn as suspendWindowsScheduledTask,G as writeDaemonLauncher,Oe as writeDaemonPid};
46
+ `))}}async function on(){switch(await C(),l()){case"darwin":{if(i.existsSync(p)){try{a(`launchctl unload -w "${p}"`,{stdio:"pipe",windowsHide:!0})}catch{}i.unlinkSync(p),console.log(c.green("\u2713 Auto-start uninstalled (launchd)"))}break}case"linux":{try{a("systemctl --user disable --now shennian",{stdio:"pipe",windowsHide:!0})}catch{}if(i.existsSync(I)){i.unlinkSync(I);try{a("systemctl --user daemon-reload",{stdio:"pipe",windowsHide:!0})}catch{}}console.log(c.green("\u2713 Auto-start uninstalled (systemd)"));break}case"win32":{Z();try{i.unlinkSync(N)}catch{}try{i.unlinkSync(b)}catch{}try{i.unlinkSync(R)}catch{}ee(),console.log(c.green("\u2713 Auto-start uninstalled (Task Scheduler)"));break}}}function vn(e){e.command("start").description("Start the background service").option("--json","Print machine-readable status JSON").option("--api <url>","Server URL override").action(r=>ce(r)),e.command("stop").description("Stop the background service").option("--json","Print machine-readable status JSON").action(r=>C(r)),e.command("status").description("Show background service status").option("--json","Print machine-readable status JSON").action(r=>ae(r)),e.command("logs").description("Show recent logs").option("-n, --lines <n>","Number of lines","50").action(r=>ue({lines:parseInt(r.lines,10)}));const n=e.command("daemon",{hidden:!0}).description("Deprecated: use top-level start/stop/status/logs"),t=(r,o)=>{o||console.error(c.yellow(`\u26A0 Deprecated command. Use \`shennian ${r}\` instead.`))};n.command("start").description("Start the background service").option("--json","Print machine-readable status JSON").option("--api <url>","Server URL override").action(r=>{t("start",r.json),ce(r)}),n.command("stop").description("Stop the background service").option("--json","Print machine-readable status JSON").action(async r=>{t("stop",r.json),await C(r)}),n.command("restart").description("Restart the background service").option("--json","Print machine-readable status JSON").option("--api <url>","Server URL override").action(r=>{t("stop && shennian start",r.json),rn(r)}),n.command("status").description("Show background service status").option("--json","Print machine-readable status JSON").action(r=>{t("status",r.json),ae(r)}),n.command("logs").description("Show recent logs").option("-n, --lines <n>","Number of lines","50").action(r=>{t("logs"),ue({lines:parseInt(r.lines,10)})}),n.command("uninstall").description("Uninstall auto-start service").action(on)}export{Be as buildDaemonSpawnOptions,An as buildWindowsLauncherCommand,In as buildWindowsScheduledTaskCommands,kn as buildWindowsScheduledTaskSuspendCommands,Dn as buildWindowsScheduledTaskXml,Nn as buildWindowsStartupVbs,Ze as canReuseRunningDaemonForStart,ne as captureEnvForService,Oe as clearDaemonLauncher,yn as clearDaemonPidIfOwner,Te as clearRemoteAccessDisabled,Le as createDaemonInstanceId,q as findRunningDaemonProcessIds,K as getCurrentProcessDaemonLauncher,m as getDaemonStatus,te as getLaunchdServiceTarget,oe as installService,Qe as isAutoStartServicePresent,O as isEphemeralCliPath,$e as isIsolatedDaemonE2eInstance,Re as isRemoteAccessDisabled,De as isSafeSnapshotEnvKey,Tn as isShennianRunServiceCommand,He as recordStartedDaemon,vn as registerDaemonCommand,Ye as reloadLaunchdAgent,Ne as resolveServiceLaunchSpec,ze as saveEnvSnapshot,Ce as shouldDiscoverGlobalDaemonProcesses,$ as shouldReplaceRunningDaemon,re as startDaemonProcess,bn as suspendWindowsScheduledTask,X as writeDaemonLauncher,xe as writeDaemonPid};
@@ -1,6 +1,7 @@
1
1
  import type { Command } from 'commander';
2
2
  import { PublicAttachmentCache } from '../room/public-attachment-cache.js';
3
3
  import { PublicRoomClient } from '../room/public-room-client.js';
4
+ import { type RoomInviteQr } from '../room/invite-qr.js';
4
5
  type RoomCommandIo = {
5
6
  write: (text: string) => void;
6
7
  fail: () => void;
@@ -9,6 +10,7 @@ type RoomCommandOptions = {
9
10
  client?: PublicRoomClient;
10
11
  cache?: PublicAttachmentCache;
11
12
  io?: RoomCommandIo;
13
+ createInviteQr?: (inviteUrl: string) => Promise<RoomInviteQr>;
12
14
  };
13
15
  export declare function registerRoomCommand(program: Command, options?: RoomCommandOptions): void;
14
16
  export {};
@@ -1,3 +1,3 @@
1
- import g from"node:crypto";import x from"node:fs";import R from"node:path";import{PublicAttachmentCache as L}from"../room/public-attachment-cache.js";import{PublicRoomClient as j,PublicRoomClientError as U}from"../room/public-room-client.js";import{callDaemonIpc as z}from"../daemon-ipc/client.js";import{DaemonIpcError as q}from"../daemon-ipc/protocol.js";import{MANAGED_ROOM_CONTEXT_ENV as D}from"../room/managed-context.js";const M="https://shennian.net/install.md",$={write:t=>{process.stdout.write(t)},fail:()=>{process.exitCode=1}};function K(t,e={}){const a=e.client??new j,r=e.cache??new L({client:a}),s=e.io??$,d=t.command("room").description("Use one explicitly referenced Shennian Room");d.command("create").description("Create a Room as the paired user").requiredOption("--name <name>","Room name").action(async o=>p("room.create",s,async()=>{b("create");const n=o.name.trim();(!n||n.length>80)&&i("invalid_room_name","Room name must contain 1 to 80 characters.");const c=await a.create(n),m=u(c.room,"invalid_room_response");return typeof m.inviteUrl!="string"&&i("invalid_room_response","Shennian returned an invalid Room invitation."),{...c,sharing:{inviteUrl:m.inviteUrl,installGuideUrl:M}}})),d.command("join <invite-url>").description("Join or request access through an official Room invitation").action(async o=>p("room.join",s,async()=>(b("join"),a.join(k(o))))),d.command("status [room-ref]").description("Show the managed Room, or one explicitly referenced Room").action(async o=>p("room.status",s,()=>{const n=_();return n?(o&&i("managed_room_ref_forbidden","Do not pass a Room reference in managed Room context."),y(n,"status",{})):a.status(w(o))})),d.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(o,n)=>{await p("room.read",s,async()=>{n.before&&n.after&&i("ambiguous_sequence_cursor","--before and --after are mutually exclusive."),n.before&&!S(n.before)&&i("invalid_sequence","--before sequence is invalid."),n.after&&!S(n.after)&&i("invalid_sequence","--after sequence is invalid.");const c=Number(n.limit);(!Number.isInteger(c)||c<1||c>50)&&i("invalid_limit","--limit must be between 1 and 50.");const m=_();if(m)return o&&i("managed_room_ref_forbidden","Do not pass a Room reference in managed Room context."),y(m,"read",{...n.before?{before:n.before}:{},...n.after?{after:n.after}:{},limit:c});const l=w(o),f=await a.read(l,{before:n.before,after:n.after,limit:c});return T(f,l,r)})}),d.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",C,[]).action(async(o,n)=>{await p("room.send",s,async()=>{const c=n.text?.trim()??"";!c&&n.file.length===0&&i("room_message_empty","Provide --text or at least one --file."),n.file.length>10&&i("room_attachments_too_many","A message can contain at most 10 files.");const m=_();if(m)return o&&i("managed_room_ref_forbidden","Do not pass a Room reference in managed Room context."),y(m,"send",{clientMessageId:`managed:${g.randomUUID()}`,text:c,files:n.file});const l=w(o),f=[];for(const I of n.file)f.push(await N(a,l,I));return a.send(l,{clientMessageId:`cli:${g.randomUUID()}`,...c?{text:c}:{},...f.length>0?{attachmentIds:f}:{}})})}),d.command("attachment").description("Download Room attachments through Shennian").command("get [room-ref] [attachment-id]").description("Download and verify one Room attachment").action(async(o,n)=>{await p("room.attachment.get",s,async()=>{const c=_(),m=c?o:n;return(!m||!/^ratt_[A-Za-z0-9_-]{1,191}$/.test(m))&&i("invalid_attachment_ref","attachment-id is invalid."),c?y(c,"attachment.get",{attachmentId:m}):r.materialize(w(o),m)})}),d.command("leave <room-ref>").description("Leave one explicitly referenced Room").action(async o=>p("room.leave",s,()=>(b("leave"),a.leave(A(o))))),d.command("list",{hidden:!0}).action(()=>h(s,"command_removed","Room listing is not available to Agents.",null)),d.command("search",{hidden:!0}).allowUnknownOption(!0).action(()=>h(s,"command_removed","Room search is not available to Agents.",null)),d.command("agent",{hidden:!0}).allowUnknownOption(!0).action(()=>h(s,"command_removed","Managed Agents are created in the Shennian App or Web interface.",null))}async function p(t,e,a){try{const r=await a();e.write(`${JSON.stringify({ok:!0,command:t,result:r})}
2
- `)}catch(r){if(r instanceof U){h(e,r.code,r.message,r.nextAction);return}if(r instanceof q){h(e,r.code,r.message,null);return}h(e,"room_command_failed","Shennian Room command failed.",null)}}function _(){return process.env[D]?.trim()||null}async function y(t,e,a){return z({method:"managed-room.execute",params:{context:t,action:e,...a},timeoutMs:e==="send"||e==="attachment.get"?18e4:15e3})}function b(t){_()&&i("managed_room_action_forbidden",`${t} is not available in managed Room context.`)}function w(t){return t||i("room_ref_required","Use an official Room URL returned by Shennian."),A(t)}function A(t){let e;try{e=new URL(t)}catch{i("invalid_room_ref","Use an official Room URL returned by Shennian.")}const a=e.pathname.match(/^\/spaces\/([A-Za-z0-9_-]{1,191})$/);return(e.protocol!=="https:"||e.hostname!=="app.shennian.net"&&e.hostname!=="shennian.net"||e.port||e.username||e.password||e.search||e.hash||!a)&&i("invalid_room_ref","Use an official Room URL returned by Shennian."),a[1]}function k(t){let e;try{e=new URL(t)}catch{i("invalid_invite_url","Use an official Shennian Room invitation URL.")}const a=e.pathname.match(/^\/spaces\/join\/([A-Za-z0-9_-]{32,256})$/);return(e.protocol!=="https:"||e.hostname!=="shennian.net"||e.port||e.username||e.password||e.search||e.hash||!a)&&i("invalid_invite_url","Use an official Shennian Room invitation URL."),a[1]}function S(t){return/^(0|[1-9]\d{0,19})$/.test(t)}function C(t,e){return[...e,t]}async function N(t,e,a){const r=R.resolve(a);let s;try{s=await x.promises.lstat(r)}catch{i("file_not_found",`File does not exist: ${a}`)}(!s.isFile()||s.isSymbolicLink())&&i("invalid_file",`Not a regular file: ${a}`),(s.size<1||s.size>100*1024*1024)&&i("invalid_file_size","Each file must be between 1 byte and 100 MB.");const d=await x.promises.readFile(r),v=g.createHash("sha256").update(d).digest("hex"),o=O(r),n=await t.initializeAttachment(e,{clientUploadId:`cli:${g.randomUUID()}`,name:R.basename(r),mediaKind:o.startsWith("image/")?"image":o.startsWith("audio/")?"audio":"file",mimeType:o,byteSize:s.size,sha256:v}),c=u(n.attachment,"invalid_attachment_response"),m=u(n.upload,"invalid_attachment_response");(typeof c.id!="string"||typeof m.url!="string"||m.method!=="PUT")&&i("invalid_attachment_response","Shennian returned an invalid attachment response.");const l=u(m.headers,"invalid_attachment_response");return Object.values(l).some(f=>typeof f!="string")&&i("invalid_attachment_response","Shennian returned invalid upload headers."),E(m.url),await t.upload(m.url,l,d),await t.completeAttachment(e,c.id),c.id}function O(t){const e=R.extname(t).toLowerCase();return{".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".webp":"image/webp",".gif":"image/gif",".heic":"image/heic",".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"}[e]??"application/octet-stream"}function T(t,e,a){return Array.isArray(t.messages)||i("invalid_server_response","Shennian returned invalid messages."),{messages:t.messages.map(r=>{const s=u(r,"invalid_server_response"),d=Array.isArray(s.attachments)?s.attachments:[];return{...s,attachments:d.map(v=>{const o=u(v,"invalid_server_response");return(typeof o.attachmentId!="string"||typeof o.name!="string"||typeof o.mimeType!="string"||typeof o.byteSize!="string"||typeof o.sha256!="string")&&i("invalid_server_response","Shennian returned invalid attachment metadata."),{...o,...a.lookup(e,o)}})}})}}function u(t,e){return(!t||typeof t!="object"||Array.isArray(t))&&i(e,"Invalid structured response."),t}function E(t){let e;try{e=new URL(t)}catch{i("invalid_attachment_response","Invalid upload URL.")}const a=e.protocol==="http:"&&(e.hostname==="localhost"||e.hostname==="127.0.0.1");(e.protocol!=="https:"&&!a||e.username||e.password||e.hash)&&i("invalid_attachment_response","Invalid upload URL.")}function i(t,e){throw new U(t,e)}function h(t,e,a,r){t.write(`${JSON.stringify({ok:!1,error:e,message:a,nextAction:r})}
3
- `),t.fail()}export{K as registerRoomCommand};
1
+ import y from"node:crypto";import I from"node:fs";import x from"node:path";import{PublicAttachmentCache as j}from"../room/public-attachment-cache.js";import{PublicRoomClient as k,PublicRoomClientError as A}from"../room/public-room-client.js";import{callDaemonIpc as q}from"../daemon-ipc/client.js";import{DaemonIpcError as D}from"../daemon-ipc/protocol.js";import{MANAGED_ROOM_CONTEXT_ENV as M}from"../room/managed-context.js";import{createRoomInviteQr as $}from"../room/invite-qr.js";const T="https://shennian.net/install.md",O={write:e=>{process.stdout.write(e)},fail:()=>{process.exitCode=1}};function X(e,t={}){const a=t.client??new k,r=t.cache??new j({client:a}),i=t.io??O,p=t.createInviteQr??$,c=e.command("room").description("Use one explicitly referenced Shennian Room");c.command("create").description("Create a Room as the paired user").requiredOption("--name <name>","Room name").action(async m=>f("room.create",i,async()=>{w("create");const n=m.name.trim();(!n||n.length>80)&&o("invalid_room_name","Room name must contain 1 to 80 characters.");const s=await a.create(n),d=h(s.room,"invalid_room_response");typeof d.inviteUrl!="string"&&o("invalid_room_response","Shennian returned an invalid Room invitation.");const u=await p(d.inviteUrl).catch(()=>({imagePath:null,mimeType:"image/png",payload:d.inviteUrl}));return{...s,sharing:{inviteUrl:d.inviteUrl,inviteQr:u,installGuideUrl:T}}})),c.command("join <invite-url>").description("Join or request access through an official Room invitation").action(async m=>f("room.join",i,async()=>{w("join");const n=U(m,"invite");return a.join(n.roomId,n.inviteToken)})),c.command("open <room-url>").description("Open one Room where the paired user is already a member").action(async m=>f("room.open",i,async()=>{w("open");const n=U(m,"stable");return a.open(n.roomId)})),c.command("status [room-ref]").description("Show the managed Room, or one explicitly referenced Room").action(async m=>f("room.status",i,()=>{const n=g();return n?(m&&o("managed_room_ref_forbidden","Do not pass a Room reference in managed Room context."),b(n,"status",{})):a.status(R(m))})),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(m,n)=>{await f("room.read",i,async()=>{n.before&&n.after&&o("ambiguous_sequence_cursor","--before and --after are mutually exclusive."),n.before&&!L(n.before)&&o("invalid_sequence","--before sequence is invalid."),n.after&&!L(n.after)&&o("invalid_sequence","--after sequence is invalid.");const s=Number(n.limit);(!Number.isInteger(s)||s<1||s>50)&&o("invalid_limit","--limit must be between 1 and 50.");const d=g();if(d)return m&&o("managed_room_ref_forbidden","Do not pass a Room reference in managed Room context."),b(d,"read",{...n.before?{before:n.before}:{},...n.after?{after:n.after}:{},limit:s});const u=R(m),_=await a.read(u,{before:n.before,after:n.after,limit:s});return P(_,u,r)})}),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",C,[]).action(async(m,n)=>{await f("room.send",i,async()=>{const s=n.text?.trim()??"";!s&&n.file.length===0&&o("room_message_empty","Provide --text or at least one --file."),n.file.length>10&&o("room_attachments_too_many","A message can contain at most 10 files.");const d=g();if(d)return m&&o("managed_room_ref_forbidden","Do not pass a Room reference in managed Room context."),b(d,"send",{clientMessageId:`managed:${y.randomUUID()}`,text:s,files:n.file});const u=R(m),_=[];for(const z of n.file)_.push(await N(a,u,z));return a.send(u,{clientMessageId:`cli:${y.randomUUID()}`,...s?{text:s}:{},..._.length>0?{attachmentIds:_}:{}})})}),c.command("attachment").description("Download Room attachments through Shennian").command("get [room-ref] [attachment-id]").description("Download and verify one Room attachment").action(async(m,n)=>{await f("room.attachment.get",i,async()=>{const s=g(),d=s?m:n;return(!d||!/^ratt_[A-Za-z0-9_-]{1,191}$/.test(d))&&o("invalid_attachment_ref","attachment-id is invalid."),s?b(s,"attachment.get",{attachmentId:d}):r.materialize(R(m),d)})}),c.command("leave <room-ref>").description("Leave one explicitly referenced Room").action(async m=>f("room.leave",i,()=>(w("leave"),a.leave(S(m))))),c.command("list",{hidden:!0}).action(()=>v(i,"command_removed","Room listing is not available to Agents.",null)),c.command("search",{hidden:!0}).allowUnknownOption(!0).action(()=>v(i,"command_removed","Room search is not available to Agents.",null)),c.command("agent",{hidden:!0}).allowUnknownOption(!0).action(()=>v(i,"command_removed","Managed Agents are created in the Shennian mobile App.",null))}async function f(e,t,a){try{const r=await a();t.write(`${JSON.stringify({ok:!0,command:e,result:r})}
2
+ `)}catch(r){if(r instanceof A){v(t,r.code,r.message,r.nextAction);return}if(r instanceof D){v(t,r.code,r.message,null);return}v(t,"room_command_failed","Shennian Room command failed.",null)}}function g(){return process.env[M]?.trim()||null}async function b(e,t,a){return q({method:"managed-room.execute",params:{context:e,action:t,...a},timeoutMs:t==="send"||t==="attachment.get"?18e4:15e3})}function w(e){g()&&o("managed_room_action_forbidden",`${e} is not available in managed Room context.`)}function R(e){return e||o("room_ref_required","Use an official Room URL returned by Shennian."),S(e)}function S(e){return U(e,"stable").roomId}function U(e,t){let a;const r=t==="invite"?"invalid_invite_url":"invalid_room_ref",i=t==="invite"?"Use an official Shennian Room invitation URL.":"Use an official Room URL returned by Shennian.";try{a=new URL(e)}catch{o(r,i)}const p=a.pathname.match(/^\/spaces\/([A-Za-z0-9_-]{1,191})$/),c=a.searchParams.get("invite");return(a.protocol!=="https:"||a.hostname!=="app.shennian.net"||a.port||a.username||a.password||a.hash||!p||a.searchParams.has("access")||[...a.searchParams.keys()].some(l=>l!=="invite")||t==="stable"&&c!=null||t==="invite"&&(c==null||!/^[A-Za-z0-9_-]{32,256}$/.test(c)))&&o(r,i),{roomId:p[1],...c?{inviteToken:c}:{}}}function L(e){return/^(0|[1-9]\d{0,19})$/.test(e)}function C(e,t){return[...t,e]}async function N(e,t,a){const r=x.resolve(a);let i;try{i=await I.promises.lstat(r)}catch{o("file_not_found",`File does not exist: ${a}`)}(!i.isFile()||i.isSymbolicLink())&&o("invalid_file",`Not a regular file: ${a}`),(i.size<1||i.size>100*1024*1024)&&o("invalid_file_size","Each file must be between 1 byte and 100 MB.");const p=await I.promises.readFile(r),c=y.createHash("sha256").update(p).digest("hex"),l=E(r),m=await e.initializeAttachment(t,{clientUploadId:`cli:${y.randomUUID()}`,name:x.basename(r),mediaKind:l.startsWith("image/")?"image":l.startsWith("audio/")?"audio":"file",mimeType:l,byteSize:i.size,sha256:c}),n=h(m.attachment,"invalid_attachment_response"),s=h(m.upload,"invalid_attachment_response");(typeof n.id!="string"||typeof s.url!="string"||s.method!=="PUT")&&o("invalid_attachment_response","Shennian returned an invalid attachment response.");const d=h(s.headers,"invalid_attachment_response");return Object.values(d).some(u=>typeof u!="string")&&o("invalid_attachment_response","Shennian returned invalid upload headers."),F(s.url),await e.upload(s.url,d,p),await e.completeAttachment(t,n.id),n.id}function E(e){const t=x.extname(e).toLowerCase();return{".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".webp":"image/webp",".gif":"image/gif",".heic":"image/heic",".mp3":"audio/mpeg",".wav":"audio/wav",".m4a":"audio/mp4",".mp4":"video/mp4",".mov":"video/quicktime",".pdf":"application/pdf",".txt":"text/plain",".md":"text/markdown",".json":"application/json",".csv":"text/csv",".zip":"application/zip",".docx":"application/vnd.openxmlformats-officedocument.wordprocessingml.document",".xlsx":"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}[t]??"application/octet-stream"}function P(e,t,a){return Array.isArray(e.messages)||o("invalid_server_response","Shennian returned invalid messages."),{messages:e.messages.map(r=>{const i=h(r,"invalid_server_response"),p=Array.isArray(i.attachments)?i.attachments:[];return{...i,attachments:p.map(c=>{const l=h(c,"invalid_server_response");return(typeof l.attachmentId!="string"||typeof l.name!="string"||typeof l.mimeType!="string"||typeof l.byteSize!="string"||typeof l.sha256!="string")&&o("invalid_server_response","Shennian returned invalid attachment metadata."),{...l,...a.lookup(t,l)}})}})}}function h(e,t){return(!e||typeof e!="object"||Array.isArray(e))&&o(t,"Invalid structured response."),e}function F(e){let t;try{t=new URL(e)}catch{o("invalid_attachment_response","Invalid upload URL.")}const a=t.protocol==="http:"&&(t.hostname==="localhost"||t.hostname==="127.0.0.1");(t.protocol!=="https:"&&!a||t.username||t.password||t.hash)&&o("invalid_attachment_response","Invalid upload URL.")}function o(e,t){throw new A(e,t)}function v(e,t,a,r){e.write(`${JSON.stringify({ok:!1,error:t,message:a,nextAction:r})}
3
+ `),e.fail()}export{X as registerRoomCommand};
package/dist/src/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import{Command as oe}from"commander";import v from"chalk";import ne from"node:os";import $ from"node:fs";import{loadConfig as d,saveConfig as te,getConfigPath as re,getShennianDir as ie,resolveShennianPath as B,normalizeAutoUpgradePolicy as se}from"./config/index.js";import{CliRelayClient as ae}from"./relay/client.js";import{registerPairCommand as ce,runSmartStart as me}from"./commands/pair.js";import{clearDaemonPidIfOwner as F,createDaemonInstanceId as le,clearDaemonLauncher as G,findRunningDaemonProcessIds as de,isRemoteAccessDisabled as pe,isIsolatedDaemonE2eInstance as ge,registerDaemonCommand as ue,writeDaemonPid as J,writeDaemonLauncher as j}from"./commands/daemon.js";import{registerAgentCommand as he}from"./commands/agent.js";import{registerRuntimeCommand as fe}from"./commands/runtime.js";import{registerManagerCommand as ve}from"./commands/manager.js";import{registerUpgradeCommand as Se}from"./commands/upgrade.js";import{registerToolsCommand as we}from"./commands/tools.js";import{registerIntegrationCommand as Ie}from"./commands/integration.js";import{registerRoomCommand as Re}from"./commands/room.js";import{SessionManager as ye}from"./session/manager.js";import{SERVERS as A,regionToUrl as ke,urlToRegion as Ce}from"./region.js";import{getCurrentVersion as K,handleStartupCrashCheck as De}from"./upgrade/engine.js";import{AutoUpgradeScheduler as Te}from"./upgrade/scheduler.js";import{detectAgents as Ue}from"./agents/detect.js";import{augmentProcessPath as z}from"./env-path.js";z();const W=K();import{getCachedAgentInfos as Ee,resolveAgentInfos as $e}from"./agents/model-registry.js";import{initCliLogReporter as Ae,reportLog as S}from"./log-reporter.js";import{isNativeFusionEnabled as be}from"./native-fusion/config.js";import{NativeSessionFusionService as Oe}from"./native-fusion/service.js";import{startDaemonLogRetention as Ne}from"./daemon-log.js";import{startDaemonIpcServer as Pe}from"./daemon-ipc/server.js";import{RoomSessionRegistry as Me}from"./room/session-registry.js";import{HttpRoomBindingClient as Le}from"./room/room-binding-client.js";import{TrustedManagedRoomBinder as _e}from"./room/managed-room-binding.js";import{RoomHostSessionObservationStore as xe}from"./room/host-session-observations.js";import{RoomMirrorSyncService as Ve}from"./room/mirror-sync-service.js";import{RoomActivationService as He}from"./room/activation-service.js";import{RoomActivationAudit as Be}from"./room/activation-audit.js";import{RoomAttachmentCache as Fe}from"./room/attachment-cache.js";import{RoomAttachmentUploader as Ge}from"./room/attachment-upload.js";import{ManagedRoomContextAuthority as Je}from"./room/managed-context.js";import{createManagedRoomIpcHandlers as je}from"./room/managed-room-ipc.js";const q=ie(),Ke=B("daemon.pid");function ze(){try{const o=$.readFileSync(Ke,"utf-8").trim();if(o.startsWith("{")){const r=JSON.parse(o),s=Number(r.pid);return Number.isInteger(s)&&s>0?s:null}const n=parseInt(o,10);return Number.isInteger(n)&&n>0?n:null}catch{return null}}function We(o){return o.replace(/^https:\/\//,"wss://").replace(/^http:\/\//,"ws://")}function b(){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 qe(o){const n=[`trigger=${o.trigger??"unknown"}`,`phase=${o.phase}`,`code=${o.code??"unknown"}`,`attempt=${o.reconnectAttempt}`];return o.reason&&n.push(`reason=${o.reason}`),o.error&&n.push(`error=${o.error}`),n.join(" ")}async function Q(o,n=5e3){const r=Date.now();for(;Date.now()-r<n;){try{process.kill(o,0)}catch{return!0}await new Promise(s=>setTimeout(s,100))}try{return process.kill(o,0),!1}catch{return!0}}const t=new oe;t.name("shennian").description("Shennian \u2014 AI Agent Control Plane").version(W),t.enablePositionalOptions(),Ie(t),Re(t),t.option("--api <url>","Server URL override").option("--name <name>","Machine name",ne.hostname()).action(async o=>{const n=d(),r=o.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??n.serverUrl??void 0;await me(r,o.name)}),t.command("run-service",{hidden:!0}).description("(internal) Connect to relay server, called by the background service").option("--api <url>","Server URL override").action(async o=>{const n=B("env.json");try{const e=JSON.parse($.readFileSync(n,"utf-8"));for(const[i,c]of Object.entries(e))process.env[i]||(process.env[i]=c);z()}catch{}const r=Ne();pe()&&(console.log(`[${new Date().toISOString()}] remote access disabled, service start skipped`),clearInterval(r),process.exit(0));const s=!!(process.env.INVOCATION_ID||process.env.JOURNAL_STREAM||process.env.SHENNIAN_DESKTOP_SERVER_URL),O=async(e,i)=>{console.log(`[${new Date().toISOString()}] ${i} (PID ${e})`),process.kill(e,"SIGTERM"),await Q(e)||(process.kill(e,"SIGKILL"),await Q(e,2e3))};try{const e=ze();if(e&&e!==process.pid)try{process.kill(e,0),s?await O(e,"managed start taking over from existing daemon"):(console.log(`[${new Date().toISOString()}] daemon already running (PID ${e}), skipping duplicate start`),process.exit(0))}catch{}}catch{}const D=ge(process.env,q)?[]:de(process.pid);if(D.length>0)if(s)for(const e of D)try{await O(e,"managed start taking over from orphan daemon")}catch{}else console.log(`[${new Date().toISOString()}] daemon already running (PID ${D[0]}), skipping duplicate start`),process.exit(0);const m=le();J(process.pid,m,{version:W}),j(process.pid,void 0,m),process.on("exit",()=>{F(process.pid,m),G(m)}),await De()&&(console.log(`[${new Date().toISOString()}] Rolled back to previous version, restarting...`),process.exit(0));const a=d(),g=o.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??a.serverUrl??A.cn.url,p=K(),T=Ue(),u=T.map(e=>e.type),U=Ee(T),w=new Me,I=new xe,N=new Je(w),h=new Le(g),Y=new Fe({actionClient:h}),Z=new Ge({actionClient:h});let P=null;const E=await Pe({handlers:{...je({authority:N,actionClient:h,attachmentCache:Y,attachmentUploader:Z,observations:I,getMachineCredentials:()=>({machineToken:d().machineToken})})}});let M=!1;const R=()=>{M||(M=!0,E.close().finally(()=>process.exit(0)))};if(process.once("SIGINT",R),process.once("SIGTERM",R),!a.machineToken){console.error('Shennian CLI is not paired. Run "shennian pair --browser" or "shennian pair --token" first.'),await E.close(),process.exitCode=1;return}process.removeListener("SIGINT",R),process.removeListener("SIGTERM",R);const L=`${We(g)}/relay/machine`,_=a.machineToken;if(!_)return;a.machineId&&Ae(g,a.machineId),console.log(`[${new Date().toISOString()}] Connecting to ${L}... (v${p}) agents: ${u.join(",")}`),S({level:"info",wsEvent:"daemon.start",metadata:{version:p,agents:u}});let y=null,k=null;const x=se(a.autoUpgrade),l=new ae({serverUrl:L,machineToken:_,cliVersion:p,agentList:u,autoUpgradePolicy:x,onConnected:()=>{console.log(`[${new Date().toISOString()}] \u2713 Connected`),S({level:"info",wsEvent:"daemon.connected"}),U.some(e=>e.models.length>0)&&l.sendEvent({type:"event",event:"machine.agents",payload:{agentList:u,agents:U}}),$e(T,{serverUrl:a.serverUrl??g,authToken:a.machineToken??a.accessToken}).then(e=>{JSON.stringify(e)!==JSON.stringify(U)&&l.sendEvent({type:"event",event:"machine.agents",payload:{agentList:u,agents:e}})}).catch(()=>{}),import("./upgrade/engine.js").then(({readUpgradeAttempt:e,clearUpgradeAttempt:i})=>{const c=e();i(),c&&(console.log(`[${new Date().toISOString()}] [upgrade] Reporting success: ${c.from} \u2192 ${c.to}`),l.sendEvent({type:"event",event:"upgrade",payload:{state:"success",machineId:"self",from:c.from,to:c.to}}))}).catch(()=>{}),C.start(),y?.handleConnected(),k?.start()},onDisconnected:e=>{k?.stop(),console.log(`[${new Date().toISOString()}] \u26A0 Disconnected, reconnecting... ${qe(e)}`),S({level:"warn",wsEvent:"daemon.disconnected",metadata:{code:e.code,reason:e.reason,error:e.error,phase:e.phase,trigger:e.trigger,reconnectAttempt:e.reconnectAttempt}})},onReq:e=>{console.log(`[${new Date().toISOString()}] [req] ${e.method}`),S({level:"info",type:"ws",wsEvent:e.method,wsDirection:"in",traceId:e.traceId}),f.handleReq(e)}});y=be()?new Oe(l):null,k=new Ve({registry:w,observations:I,actionClient:h,getMachineCredentials:()=>({machineToken:d().machineToken}),onMessages:async(e,i,c,ee)=>{await P?.processMessages(e,i,c,ee)}});const C=new Te({currentVersion:p,machineId:a.machineId??"unknown-machine",serverUrl:a.serverUrl??g,getIdleState:()=>f.getUpgradeIdleState(),startUpgrade:async e=>{const{handleUpgradeStart:i}=await import("./commands/upgrade.js");await i(l,"auto-upgrade",e,{currentVersion:p})}});C.setPolicy(x);const f=new ye(l,y,p,C,new _e({registry:w,observations:I,bindingClient:h,getMachineCredentials:()=>{const e=d();return{machineId:e.machineId,machineToken:e.machineToken}},hostProcessIdentity:()=>`daemon:${m}`}));P=new He({registry:w,observations:I,activate:e=>f.activateRoomSession(e),issueManagedRoomContext:e=>{const i=d().machineToken;if(!i)throw new Error("machine_not_authorized");return N.issue({registryKey:e,machineCredential:i})},abort:e=>f.abortRoomSession(e),audit:new Be}),$.mkdirSync(q,{recursive:!0}),J(process.pid,m,{version:p}),j(process.pid,void 0,m),l.connect(),process.stdin.resume();let V=!1;const H=async()=>{if(!V){V=!0,console.log(v.gray(`
3
- Disconnecting...`)),S({level:"info",wsEvent:"daemon.stop"}),clearInterval(r);try{await f.cleanup(),C.stop(),y?.stop(),k?.stop(),l.disconnect(),await E.close(),F(process.pid,m),G(m)}finally{process.exit(0)}}};process.on("SIGINT",()=>{H()}),process.on("SIGTERM",()=>{H()})}),t.command("workbuddy-session-hook",{hidden:!0}).description("(retired) WorkBuddy Room Hook").allowUnknownOption(!0).action(()=>b()),t.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(()=>b()),t.command("room-tool-server",{hidden:!0}).description("(internal) Run the official Shennian Room MCP tool").allowUnknownOption(!0).action(()=>b());const X=t.command("config").description("View or modify configuration");X.command("show",{isDefault:!0}).description("Show current configuration").action(()=>{console.log(`Config file: ${re()}`);const n={...d()};if(n.machineToken&&(n.machineToken=n.machineToken.slice(0,12)+"..."),n.serverUrl){const r=Ce(n.serverUrl);console.log(`Region: ${r} (${A[r].label})`)}console.log(JSON.stringify(n,null,2))}),X.command("set").description("Update a config value").argument("<key>",'Config key (e.g. "server")').argument("<value>",'Config value (e.g. "cn" or "global")').action((o,n)=>{const r=d();if(o==="server"){n!=="cn"&&n!=="global"&&(console.error(v.red('\u2717 Value must be "cn" or "global"')),process.exit(1));const s=n;r.serverUrl=ke(s),te(r),console.log(v.green(`\u2713 Server set to ${A[s].label}`)),console.log(v.yellow(" Restart the background service for this to take effect: shennian stop && shennian start"));return}console.error(v.red(`\u2717 Unknown config key: ${o}. Supported: server`)),process.exit(1)}),ce(t),ue(t),he(t),fe(t),ve(t),Se(t),we(t),t.parse();
1
+ import{Command as oe}from"commander";import v from"chalk";import ne from"node:os";import $ from"node:fs";import{loadConfig as d,saveConfig as te,getConfigPath as re,getShennianDir as ie,resolveShennianPath as B,normalizeAutoUpgradePolicy as se}from"./config/index.js";import{CliRelayClient as ae}from"./relay/client.js";import{registerPairCommand as ce,runSmartStart as me}from"./commands/pair.js";import{clearDaemonPidIfOwner as F,createDaemonInstanceId as le,clearDaemonLauncher as G,findRunningDaemonProcessIds as de,isRemoteAccessDisabled as pe,isIsolatedDaemonE2eInstance as ge,registerDaemonCommand as ue,writeDaemonPid as J,writeDaemonLauncher as j}from"./commands/daemon.js";import{registerRuntimeCommand as he}from"./commands/runtime.js";import{registerManagerCommand as fe}from"./commands/manager.js";import{registerUpgradeCommand as ve}from"./commands/upgrade.js";import{registerToolsCommand as Se}from"./commands/tools.js";import{registerIntegrationCommand as we}from"./commands/integration.js";import{registerRoomCommand as Ie}from"./commands/room.js";import{SessionManager as Re}from"./session/manager.js";import{SERVERS as A,regionToUrl as ye,urlToRegion as ke}from"./region.js";import{getCurrentVersion as K,handleStartupCrashCheck as Ce}from"./upgrade/engine.js";import{AutoUpgradeScheduler as De}from"./upgrade/scheduler.js";import{detectAgents as Te}from"./agents/detect.js";import{augmentProcessPath as z}from"./env-path.js";z();const W=K();import{getCachedAgentInfos as Ue,resolveAgentInfos as Ee}from"./agents/model-registry.js";import{initCliLogReporter as $e,reportLog as S}from"./log-reporter.js";import{isNativeFusionEnabled as Ae}from"./native-fusion/config.js";import{NativeSessionFusionService as be}from"./native-fusion/service.js";import{startDaemonLogRetention as Oe}from"./daemon-log.js";import{startDaemonIpcServer as Ne}from"./daemon-ipc/server.js";import{RoomSessionRegistry as Pe}from"./room/session-registry.js";import{HttpRoomBindingClient as Me}from"./room/room-binding-client.js";import{TrustedManagedRoomBinder as Le}from"./room/managed-room-binding.js";import{RoomHostSessionObservationStore as _e}from"./room/host-session-observations.js";import{RoomMirrorSyncService as xe}from"./room/mirror-sync-service.js";import{RoomActivationService as Ve}from"./room/activation-service.js";import{RoomActivationAudit as He}from"./room/activation-audit.js";import{RoomAttachmentCache as Be}from"./room/attachment-cache.js";import{RoomAttachmentUploader as Fe}from"./room/attachment-upload.js";import{ManagedRoomContextAuthority as Ge}from"./room/managed-context.js";import{createManagedRoomIpcHandlers as Je}from"./room/managed-room-ipc.js";const q=ie(),je=B("daemon.pid");function Ke(){try{const o=$.readFileSync(je,"utf-8").trim();if(o.startsWith("{")){const r=JSON.parse(o),s=Number(r.pid);return Number.isInteger(s)&&s>0?s:null}const n=parseInt(o,10);return Number.isInteger(n)&&n>0?n:null}catch{return null}}function ze(o){return o.replace(/^https:\/\//,"wss://").replace(/^http:\/\//,"ws://")}function b(){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 We(o){const n=[`trigger=${o.trigger??"unknown"}`,`phase=${o.phase}`,`code=${o.code??"unknown"}`,`attempt=${o.reconnectAttempt}`];return o.reason&&n.push(`reason=${o.reason}`),o.error&&n.push(`error=${o.error}`),n.join(" ")}async function Q(o,n=5e3){const r=Date.now();for(;Date.now()-r<n;){try{process.kill(o,0)}catch{return!0}await new Promise(s=>setTimeout(s,100))}try{return process.kill(o,0),!1}catch{return!0}}const t=new oe;t.name("shennian").description("Shennian \u2014 AI Agent Control Plane").version(W),t.enablePositionalOptions(),we(t),Ie(t),t.option("--api <url>","Server URL override").option("--name <name>","Machine name",ne.hostname()).action(async o=>{const n=d(),r=o.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??n.serverUrl??void 0;await me(r,o.name)}),t.command("run-service",{hidden:!0}).description("(internal) Connect to relay server, called by the background service").option("--api <url>","Server URL override").action(async o=>{const n=B("env.json");try{const e=JSON.parse($.readFileSync(n,"utf-8"));for(const[i,c]of Object.entries(e))process.env[i]||(process.env[i]=c);z()}catch{}const r=Oe();pe()&&(console.log(`[${new Date().toISOString()}] remote access disabled, service start skipped`),clearInterval(r),process.exit(0));const s=!!(process.env.INVOCATION_ID||process.env.JOURNAL_STREAM||process.env.SHENNIAN_DESKTOP_SERVER_URL),O=async(e,i)=>{console.log(`[${new Date().toISOString()}] ${i} (PID ${e})`),process.kill(e,"SIGTERM"),await Q(e)||(process.kill(e,"SIGKILL"),await Q(e,2e3))};try{const e=Ke();if(e&&e!==process.pid)try{process.kill(e,0),s?await O(e,"managed start taking over from existing daemon"):(console.log(`[${new Date().toISOString()}] daemon already running (PID ${e}), skipping duplicate start`),process.exit(0))}catch{}}catch{}const D=ge(process.env,q)?[]:de(process.pid);if(D.length>0)if(s)for(const e of D)try{await O(e,"managed start taking over from orphan daemon")}catch{}else console.log(`[${new Date().toISOString()}] daemon already running (PID ${D[0]}), skipping duplicate start`),process.exit(0);const m=le();J(process.pid,m,{version:W}),j(process.pid,void 0,m),process.on("exit",()=>{F(process.pid,m),G(m)}),await Ce()&&(console.log(`[${new Date().toISOString()}] Rolled back to previous version, restarting...`),process.exit(0));const a=d(),g=o.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??a.serverUrl??A.cn.url,p=K(),T=Te(),u=T.map(e=>e.type),U=Ue(T),w=new Pe,I=new _e,N=new Ge(w),h=new Me(g),Y=new Be({actionClient:h}),Z=new Fe({actionClient:h});let P=null;const E=await Ne({handlers:{...Je({authority:N,actionClient:h,attachmentCache:Y,attachmentUploader:Z,observations:I,getMachineCredentials:()=>({machineToken:d().machineToken})})}});let M=!1;const R=()=>{M||(M=!0,E.close().finally(()=>process.exit(0)))};if(process.once("SIGINT",R),process.once("SIGTERM",R),!a.machineToken){console.error('Shennian CLI is not paired. Run "shennian pair --browser" or "shennian pair --token" first.'),await E.close(),process.exitCode=1;return}process.removeListener("SIGINT",R),process.removeListener("SIGTERM",R);const L=`${ze(g)}/relay/machine`,_=a.machineToken;if(!_)return;a.machineId&&$e(g,a.machineId),console.log(`[${new Date().toISOString()}] Connecting to ${L}... (v${p}) agents: ${u.join(",")}`),S({level:"info",wsEvent:"daemon.start",metadata:{version:p,agents:u}});let y=null,k=null;const x=se(a.autoUpgrade),l=new ae({serverUrl:L,machineToken:_,cliVersion:p,agentList:u,autoUpgradePolicy:x,onConnected:()=>{console.log(`[${new Date().toISOString()}] \u2713 Connected`),S({level:"info",wsEvent:"daemon.connected"}),U.some(e=>e.models.length>0)&&l.sendEvent({type:"event",event:"machine.agents",payload:{agentList:u,agents:U}}),Ee(T,{serverUrl:a.serverUrl??g,authToken:a.machineToken??a.accessToken}).then(e=>{JSON.stringify(e)!==JSON.stringify(U)&&l.sendEvent({type:"event",event:"machine.agents",payload:{agentList:u,agents:e}})}).catch(()=>{}),import("./upgrade/engine.js").then(({readUpgradeAttempt:e,clearUpgradeAttempt:i})=>{const c=e();i(),c&&(console.log(`[${new Date().toISOString()}] [upgrade] Reporting success: ${c.from} \u2192 ${c.to}`),l.sendEvent({type:"event",event:"upgrade",payload:{state:"success",machineId:"self",from:c.from,to:c.to}}))}).catch(()=>{}),C.start(),y?.handleConnected(),k?.start()},onDisconnected:e=>{k?.stop(),console.log(`[${new Date().toISOString()}] \u26A0 Disconnected, reconnecting... ${We(e)}`),S({level:"warn",wsEvent:"daemon.disconnected",metadata:{code:e.code,reason:e.reason,error:e.error,phase:e.phase,trigger:e.trigger,reconnectAttempt:e.reconnectAttempt}})},onReq:e=>{console.log(`[${new Date().toISOString()}] [req] ${e.method}`),S({level:"info",type:"ws",wsEvent:e.method,wsDirection:"in",traceId:e.traceId}),f.handleReq(e)}});y=Ae()?new be(l):null,k=new xe({registry:w,observations:I,actionClient:h,getMachineCredentials:()=>({machineToken:d().machineToken}),onMessages:async(e,i,c,ee)=>{await P?.processMessages(e,i,c,ee)}});const C=new De({currentVersion:p,machineId:a.machineId??"unknown-machine",serverUrl:a.serverUrl??g,getIdleState:()=>f.getUpgradeIdleState(),startUpgrade:async e=>{const{handleUpgradeStart:i}=await import("./commands/upgrade.js");await i(l,"auto-upgrade",e,{currentVersion:p})}});C.setPolicy(x);const f=new Re(l,y,p,C,new Le({registry:w,observations:I,bindingClient:h,getMachineCredentials:()=>{const e=d();return{machineId:e.machineId,machineToken:e.machineToken}},hostProcessIdentity:()=>`daemon:${m}`}));P=new Ve({registry:w,observations:I,activate:e=>f.activateRoomSession(e),issueManagedRoomContext:e=>{const i=d().machineToken;if(!i)throw new Error("machine_not_authorized");return N.issue({registryKey:e,machineCredential:i})},abort:e=>f.abortRoomSession(e),audit:new He}),$.mkdirSync(q,{recursive:!0}),J(process.pid,m,{version:p}),j(process.pid,void 0,m),l.connect(),process.stdin.resume();let V=!1;const H=async()=>{if(!V){V=!0,console.log(v.gray(`
3
+ Disconnecting...`)),S({level:"info",wsEvent:"daemon.stop"}),clearInterval(r);try{await f.cleanup(),C.stop(),y?.stop(),k?.stop(),l.disconnect(),await E.close(),F(process.pid,m),G(m)}finally{process.exit(0)}}};process.on("SIGINT",()=>{H()}),process.on("SIGTERM",()=>{H()})}),t.command("workbuddy-session-hook",{hidden:!0}).description("(retired) WorkBuddy Room Hook").allowUnknownOption(!0).action(()=>b()),t.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(()=>b()),t.command("room-tool-server",{hidden:!0}).description("(internal) Run the official Shennian Room MCP tool").allowUnknownOption(!0).action(()=>b());const X=t.command("config").description("View or modify configuration");X.command("show",{isDefault:!0}).description("Show current configuration").action(()=>{console.log(`Config file: ${re()}`);const n={...d()};if(n.machineToken&&(n.machineToken=n.machineToken.slice(0,12)+"..."),n.serverUrl){const r=ke(n.serverUrl);console.log(`Region: ${r} (${A[r].label})`)}console.log(JSON.stringify(n,null,2))}),X.command("set").description("Update a config value").argument("<key>",'Config key (e.g. "server")').argument("<value>",'Config value (e.g. "cn" or "global")').action((o,n)=>{const r=d();if(o==="server"){n!=="cn"&&n!=="global"&&(console.error(v.red('\u2717 Value must be "cn" or "global"')),process.exit(1));const s=n;r.serverUrl=ye(s),te(r),console.log(v.green(`\u2713 Server set to ${A[s].label}`)),console.log(v.yellow(" Restart the background service for this to take effect: shennian stop && shennian start"));return}console.error(v.red(`\u2717 Unknown config key: ${o}. Supported: server`)),process.exit(1)}),ce(t),ue(t),he(t),fe(t),ve(t),Se(t),t.parse();
@@ -0,0 +1,6 @@
1
+ export type RoomInviteQr = {
2
+ imagePath: string;
3
+ mimeType: 'image/png';
4
+ payload: string;
5
+ };
6
+ export declare function createRoomInviteQr(inviteUrl: string, rootDirectory?: string): Promise<RoomInviteQr>;
@@ -0,0 +1 @@
1
+ import i from"node:crypto";import t from"node:fs";import a from"node:path";import n from"qrcode";import{resolveShennianPath as p}from"../config/index.js";async function f(e,r=p("room-invites")){t.mkdirSync(r,{recursive:!0,mode:448});const m=i.createHash("sha256").update(e).digest("hex").slice(0,24),o=a.resolve(r,`room-invite-${m}.png`);return await n.toFile(o,e,{type:"png",width:640,margin:4,errorCorrectionLevel:"M"}),t.chmodSync(o,384),{imagePath:o,mimeType:"image/png",payload:e}}export{f as createRoomInviteQr};
@@ -7,7 +7,8 @@ export declare class PublicRoomClient {
7
7
  private readonly fetchImpl;
8
8
  constructor(fetchImpl?: typeof fetch);
9
9
  create(name: string): Promise<Record<string, unknown>>;
10
- join(inviteToken: string): Promise<Record<string, unknown>>;
10
+ join(roomId: string, inviteToken: string): Promise<Record<string, unknown>>;
11
+ open(roomId: string): Promise<Record<string, unknown>>;
11
12
  status(roomId: string): Promise<Record<string, unknown>>;
12
13
  read(roomId: string, input: {
13
14
  before?: string;
@@ -1 +1 @@
1
- import{loadConfig as w}from"../config/index.js";import{SERVERS as y}from"../region.js";class r extends Error{code;nextAction;constructor(e,t,n=null){super(t),this.code=e,this.nextAction=n}}class b{fetchImpl;constructor(e=fetch){this.fetchImpl=e}create(e){return this.request("POST","/api/room-cli/create",{name:e})}join(e){return this.request("POST","/api/room-cli/join",{inviteToken:e})}status(e){return this.request("GET",`/api/room-cli/${encodeURIComponent(e)}/status`)}read(e,t){const n=new URLSearchParams({limit:String(t.limit)});return t.before&&n.set("before",t.before),t.after&&n.set("after",t.after),this.request("GET",`/api/room-cli/${encodeURIComponent(e)}/messages?${n}`)}send(e,t){return this.request("POST",`/api/room-cli/${encodeURIComponent(e)}/messages`,t,{retryNetworkOnce:!0})}initializeAttachment(e,t){return this.request("POST",`/api/room-cli/${encodeURIComponent(e)}/attachments/init`,t)}completeAttachment(e,t){return this.request("POST",`/api/room-cli/${encodeURIComponent(e)}/attachments/${encodeURIComponent(t)}/complete`,{})}getAttachmentDownload(e,t){return this.request("GET",`/api/room-cli/${encodeURIComponent(e)}/attachments/${encodeURIComponent(t)}/download`)}leave(e){return this.request("POST",`/api/room-cli/${encodeURIComponent(e)}/leave`,{})}async upload(e,t,n){let s;try{s=await this.fetchImpl(e,{method:"PUT",headers:t,body:n,redirect:"error",signal:AbortSignal.timeout(12e4)})}catch{throw new r("attachment_upload_failed","Attachment upload failed.")}if(!s.ok)throw new r("attachment_upload_failed","Attachment upload failed.")}async download(e){try{const t=await this.fetchImpl(e,{redirect:"error",signal:AbortSignal.timeout(6e4)});if(!t.ok||!t.body)throw new r("attachment_download_failed","Attachment download failed.");return t}catch(t){throw t instanceof r?t:new r("attachment_download_failed","Attachment download failed.")}}async request(e,t,n,s={}){const l=w();if(!l.machineToken)throw new r("not_paired","Shennian CLI is not paired.",{type:"run_command",command:"shennian pair --browser"});const d=(l.serverUrl??y.cn.url).replace(/\/+$/,""),h=()=>this.fetchImpl(`${d}${t}`,{method:e,headers:{authorization:`Bearer ${l.machineToken}`,...n==null?{}:{"content-type":"application/json"}},...n==null?{}:{body:JSON.stringify(n)},signal:AbortSignal.timeout(2e4)});let i;try{i=await h()}catch{if(!s.retryNetworkOnce)throw new r("server_unavailable","Shennian service is unavailable.");try{i=await h()}catch{throw new r("server_unavailable","Shennian service is unavailable.")}}const c=await S(i);if(!i.ok){const a=m(c)?c:{},u=typeof a.error=="string"?a.error:`http_${i.status}`,p=typeof a.message=="string"?a.message:"Room request failed.",f=g(a.nextAction)?a.nextAction:null;throw new r(u,p,f)}if(!m(c))throw new r("invalid_server_response","Shennian returned an invalid Room response.");return c}}function g(o){return m(o)&&Object.values(o).every(e=>typeof e=="string")}async function S(o){try{return await o.json()}catch{return null}}function m(o){return!!o&&typeof o=="object"&&!Array.isArray(o)}export{b as PublicRoomClient,r as PublicRoomClientError};
1
+ import{loadConfig as w}from"../config/index.js";import{SERVERS as y}from"../region.js";class r extends Error{code;nextAction;constructor(e,t,n=null){super(t),this.code=e,this.nextAction=n}}class b{fetchImpl;constructor(e=fetch){this.fetchImpl=e}create(e){return this.request("POST","/api/room-cli/create",{name:e})}join(e,t){return this.request("POST","/api/room-cli/join",{roomId:e,inviteToken:t})}open(e){return this.request("POST",`/api/room-cli/${encodeURIComponent(e)}/open`,{})}status(e){return this.request("GET",`/api/room-cli/${encodeURIComponent(e)}/status`)}read(e,t){const n=new URLSearchParams({limit:String(t.limit)});return t.before&&n.set("before",t.before),t.after&&n.set("after",t.after),this.request("GET",`/api/room-cli/${encodeURIComponent(e)}/messages?${n}`)}send(e,t){return this.request("POST",`/api/room-cli/${encodeURIComponent(e)}/messages`,t,{retryNetworkOnce:!0})}initializeAttachment(e,t){return this.request("POST",`/api/room-cli/${encodeURIComponent(e)}/attachments/init`,t)}completeAttachment(e,t){return this.request("POST",`/api/room-cli/${encodeURIComponent(e)}/attachments/${encodeURIComponent(t)}/complete`,{})}getAttachmentDownload(e,t){return this.request("GET",`/api/room-cli/${encodeURIComponent(e)}/attachments/${encodeURIComponent(t)}/download`)}leave(e){return this.request("POST",`/api/room-cli/${encodeURIComponent(e)}/leave`,{})}async upload(e,t,n){let s;try{s=await this.fetchImpl(e,{method:"PUT",headers:t,body:n,redirect:"error",signal:AbortSignal.timeout(12e4)})}catch{throw new r("attachment_upload_failed","Attachment upload failed.")}if(!s.ok)throw new r("attachment_upload_failed","Attachment upload failed.")}async download(e){try{const t=await this.fetchImpl(e,{redirect:"error",signal:AbortSignal.timeout(6e4)});if(!t.ok||!t.body)throw new r("attachment_download_failed","Attachment download failed.");return t}catch(t){throw t instanceof r?t:new r("attachment_download_failed","Attachment download failed.")}}async request(e,t,n,s={}){const l=w();if(!l.machineToken)throw new r("not_paired","Shennian CLI is not paired.",{type:"run_command",command:"shennian pair --browser"});const d=(l.serverUrl??y.cn.url).replace(/\/+$/,""),h=()=>this.fetchImpl(`${d}${t}`,{method:e,headers:{authorization:`Bearer ${l.machineToken}`,...n==null?{}:{"content-type":"application/json"}},...n==null?{}:{body:JSON.stringify(n)},signal:AbortSignal.timeout(2e4)});let i;try{i=await h()}catch{if(!s.retryNetworkOnce)throw new r("server_unavailable","Shennian service is unavailable.");try{i=await h()}catch{throw new r("server_unavailable","Shennian service is unavailable.")}}const c=await S(i);if(!i.ok){const a=m(c)?c:{},u=typeof a.error=="string"?a.error:`http_${i.status}`,p=typeof a.message=="string"?a.message:"Room request failed.",f=g(a.nextAction)?a.nextAction:null;throw new r(u,p,f)}if(!m(c))throw new r("invalid_server_response","Shennian returned an invalid Room response.");return c}}function g(o){return m(o)&&Object.values(o).every(e=>typeof e=="string")}async function S(o){try{return await o.json()}catch{return null}}function m(o){return!!o&&typeof o=="object"&&!Array.isArray(o)}export{b as PublicRoomClient,r as PublicRoomClientError};
@@ -39,7 +39,7 @@ export type RemoteRoomJoinResult = {
39
39
  room: {
40
40
  id: string;
41
41
  name: string;
42
- webUrl: string;
42
+ canonicalUrl: string;
43
43
  };
44
44
  agentIdentity: {
45
45
  id: string;
@@ -90,8 +90,8 @@ export type RemoteRoomCreateResult = {
90
90
  id: string;
91
91
  name: string;
92
92
  visibility: 'private';
93
- joinPolicy: 'approval';
94
- webUrl: string;
93
+ joinPolicy: 'open';
94
+ canonicalUrl: string;
95
95
  inviteUrl: string;
96
96
  };
97
97
  agentIdentity: {
@@ -114,8 +114,8 @@ export type RemoteOperatorRoomCreateResult = {
114
114
  id: string;
115
115
  name: string;
116
116
  visibility: 'private';
117
- joinPolicy: 'approval';
118
- webUrl: string;
117
+ joinPolicy: 'open';
118
+ canonicalUrl: string;
119
119
  };
120
120
  };
121
121
  export declare class RoomActionHttpError extends Error {
@@ -1 +1 @@
1
- import S from"node:path";class c extends Error{code;constructor(e,t){super(t),this.code=e,this.name="RoomActionHttpError"}}class R{fetchImpl;baseUrl;constructor(e,t=fetch){this.fetchImpl=t,this.baseUrl=e.replace(/\/+$/,"")}async createOperatorRoom(e){let t;try{t=await this.fetchImpl(`${this.baseUrl}/api/rooms/actions/operator/create`,{method:"POST",headers:{authorization:`Bearer ${e.machineToken}`,"content-type":"application/json"},body:JSON.stringify({name:e.name}),signal:AbortSignal.timeout(1e4)})}catch{throw new c("room_action_server_unavailable","Shennian Room service is unavailable.")}const o=await p(t);if(!t.ok)throw y(t.status,o);return _(o,e,this.baseUrl)}async resolveNativeSession(e){let t;try{t=await this.fetchImpl(`${this.baseUrl}/api/rooms/actions/resolve-session`,{method:"POST",headers:{authorization:`Bearer ${e.machineToken}`,"content-type":"application/json"},body:JSON.stringify({runtimeAdapter:e.runtimeAdapter,sourceSessionKey:e.sourceSessionKey}),signal:AbortSignal.timeout(1e4)})}catch{throw new c("room_action_server_unavailable","Shennian Room service is unavailable.")}const o=await p(t);if(!t.ok)throw y(t.status,o);(!o||typeof o!="object")&&r();const n=o;return(typeof n.nianSessionId!="string"||!/^[A-Za-z0-9_.:@/-]{1,512}$/.test(n.nianSessionId)||typeof n.created!="boolean")&&r(),n}async createRoom(e){let t;try{t=await this.fetchImpl(`${this.baseUrl}/api/rooms/actions/create`,{method:"POST",headers:{authorization:`Bearer ${e.machineToken}`,"content-type":"application/json"},body:JSON.stringify({bindingIntentId:e.bindingIntentId,nianSessionId:e.nianSessionId,runtimeAdapter:e.runtimeAdapter,sessionRegistryKey:e.sessionRegistryKey,name:e.name,agentAlias:e.agentAlias,agentDescription:e.agentDescription,activationCapability:e.activationCapability,activationMode:e.activationMode}),signal:AbortSignal.timeout(1e4)})}catch{throw new c("room_action_server_unavailable","Shennian Room service is unavailable.")}const o=await p(t);if(!t.ok)throw y(t.status,o);return j(o,e,this.baseUrl)}async joinRoom(e){let t;try{t=await this.fetchImpl(`${this.baseUrl}/api/rooms/actions/join`,{method:"POST",headers:{authorization:`Bearer ${e.machineToken}`,"content-type":"application/json"},body:JSON.stringify({bindingIntentId:e.bindingIntentId,nianSessionId:e.nianSessionId,runtimeAdapter:e.runtimeAdapter,sessionRegistryKey:e.sessionRegistryKey,inviteToken:e.inviteToken,message:e.message,agentAlias:e.agentAlias,agentDescription:e.agentDescription,activationCapability:e.activationCapability,activationMode:e.activationMode}),signal:AbortSignal.timeout(1e4)})}catch{throw new c("room_action_server_unavailable","Shennian Room service is unavailable.")}const o=await p(t);if(!t.ok)throw y(t.status,o);return A(o,e,this.baseUrl)}async status(e){const t=await this.request(e,"status");return w(t,e)}async getAttachmentCapability(e){const t=await this.request(e,`attachments/${encodeURIComponent(e.attachmentId)}/download-capability`);(!t||typeof t!="object")&&r();const o=t;return(o.attachmentId!==e.attachmentId||o.roomId!==e.roomId||typeof o.safeName!="string"||!b(o.safeName)||typeof o.mimeType!="string"||o.mimeType.length<1||o.mimeType.length>191||typeof o.byteSize!="string"||!d(o.byteSize)||typeof o.sha256!="string"||!/^[a-f0-9]{64}$/.test(o.sha256)||!g(o.downloadUrl)||typeof o.expiresAt!="string"||!Number.isFinite(Date.parse(o.expiresAt)))&&r(),o}async initializeAttachment(e){const t=await this.request(e,"attachments/init",{method:"POST",body:JSON.stringify({clientUploadId:e.clientUploadId,name:e.name,mediaKind:e.mediaKind,mimeType:e.mimeType,byteSize:e.byteSize,sha256:e.sha256})});(!t||typeof t!="object")&&r();const o=t,n=o.attachment,a=o.upload;return(!n||typeof n.id!="string"||n.roomId!==e.roomId||typeof n.name!="string"||!b(n.name)||n.mimeType!==e.mimeType||n.byteSize!==String(e.byteSize)||n.sha256!==e.sha256||n.status!=="pending"&&n.status!=="uploaded"||!a||a.method!=="PUT"||!g(a.url)||!a.headers||typeof a.headers!="object"||a.headers["content-type"]!==e.mimeType||a.headers["x-oss-meta-sha256"]!==e.sha256||typeof a.expiresAt!="string"||!Number.isFinite(Date.parse(a.expiresAt))||Date.parse(a.expiresAt)<=Date.now()||typeof o.idempotent!="boolean")&&r(),o}async completeAttachment(e){const t=await this.request(e,`attachments/${encodeURIComponent(e.attachmentId)}/complete`,{method:"POST"});(!t||typeof t!="object")&&r();const o=t;typeof o.idempotent!="boolean"&&r();const n=o.attachment;return(!n||n.attachmentId!==e.attachmentId||n.mediaKind!=="image"&&n.mediaKind!=="audio"&&n.mediaKind!=="file"||typeof n.name!="string"||typeof n.mimeType!="string"||typeof n.byteSize!="string"||!d(n.byteSize)||typeof n.sha256!="string"||!/^[a-f0-9]{64}$/.test(n.sha256)||n.status!=="ready")&&r(),{attachment:n,idempotent:o.idempotent}}async read(e){if(e.beforeSequence&&e.afterSequence)throw new c("ambiguous_sequence_cursor","beforeSequence and afterSequence are mutually exclusive.");const t=new URLSearchParams({limit:String(e.limit)});e.beforeSequence&&t.set("before",e.beforeSequence),e.afterSequence&&t.set("after",e.afterSequence);const o=await this.request(e,`messages?${t}`);(!o||typeof o!="object")&&r();const n=o;return(!Array.isArray(n.messages)||typeof n.nextSequence!="string"||!d(n.nextSequence)||typeof n.hasMore!="boolean"||typeof n.historyFromSequence!="string"||!d(n.historyFromSequence)||typeof n.roomName!="string")&&r(),{messages:n.messages.map(s=>h(s,e.roomId)),nextSequence:n.nextSequence,hasMore:n.hasMore,historyFromSequence:n.historyFromSequence,roomName:n.roomName}}async send(e){const t=await this.request(e,"messages",{method:"POST",body:JSON.stringify({clientMessageId:e.clientMessageId,text:e.text,mentionMembershipIds:e.mentionMembershipIds,replyToMessageId:e.replyToMessageId,activationCauseMessageId:e.activationCauseMessageId,attachmentIds:e.attachmentIds})});(!t||typeof t!="object")&&r();const o=t;return typeof o.idempotent!="boolean"&&r(),{message:h(o.message,e.roomId),idempotent:o.idempotent}}async mode(e){const t=await this.request(e,"mode",{method:"PATCH",body:JSON.stringify({activationMode:e.activationMode})});(!t||typeof t!="object")&&r();const o=t;return(o.bindingId!==e.bindingId||o.roomId!==e.roomId||o.status!=="active"&&o.status!=="paused"||!I(o.activationMode)||o.activationMode!==e.activationMode||typeof o.idempotent!="boolean")&&r(),o}async request(e,t,o={}){let n;try{n=await this.fetchImpl(`${this.baseUrl}/api/rooms/${encodeURIComponent(e.roomId)}/bindings/${encodeURIComponent(e.bindingId)}/${t}`,{...o,headers:{authorization:`Bearer ${e.machineToken}`,...o.body?{"content-type":"application/json"}:{}},signal:AbortSignal.timeout(1e4)})}catch{throw new c("room_action_server_unavailable","Shennian Room service is unavailable.")}if(!n.ok){let a=`room_action_http_${n.status}`;try{const s=await n.json();typeof s.error=="string"&&/^[a-z0-9_-]{1,80}$/.test(s.error)&&(a=s.error)}catch{}throw new c(a,"Shennian Room action failed.")}try{return await n.json()}catch{r()}}}function w(i,e){(!i||typeof i!="object")&&r();const t=i,o=t.room,n=t.agent;(t.bindingId!==e.bindingId||t.roomId!==e.roomId||typeof t.agentIdentityId!="string"||t.agentMembershipId!==null&&typeof t.agentMembershipId!="string"||t.status!=="pending_approval"&&t.status!=="active"&&t.status!=="paused"&&t.status!=="detached"||!I(t.activationMode)||t.activationCapability!==void 0&&t.activationCapability!=="managed"&&t.activationCapability!=="idle_resume"&&t.activationCapability!=="notify_only"||t.managedWorkspacePath!==void 0&&t.managedWorkspacePath!==null&&(typeof t.managedWorkspacePath!="string"||t.managedWorkspacePath.length>4096||!S.isAbsolute(t.managedWorkspacePath))||typeof t.lastDeliveredSequence!="string"||!d(t.lastDeliveredSequence)||typeof t.lastActivatedSequence!="string"||!d(t.lastActivatedSequence)||!o||typeof o!="object"||n!==void 0&&(!n||typeof n!="object"))&&r(),(t.status==="pending_approval"&&t.agentMembershipId!==null||(t.status==="active"||t.status==="paused")&&typeof t.agentMembershipId!="string")&&r();const a=o,s=n,m=a.members;return(s!==void 0&&(typeof s.displayName!="string"||!s.displayName.trim()||typeof s.description!="string")||typeof a.name!="string"||typeof a.status!="string"||a.memberCount!==void 0&&(typeof a.memberCount!="number"||!Number.isInteger(a.memberCount)||a.memberCount<0)||typeof a.lastSequence!="string"||!d(a.lastSequence)||m!==void 0&&(!Array.isArray(m)||m.some(f=>{if(!f||typeof f!="object")return!0;const l=f;return typeof l.displayName!="string"||l.principalType!=="human"&&l.principalType!=="agent"})))&&r(),t}function h(i,e){(!i||typeof i!="object")&&r();const t=i;if((typeof t.id!="string"||t.roomId!==e||typeof t.sequence!="string"||!d(t.sequence)||!t.author||typeof t.author.membershipId!="string"||!Array.isArray(t.attachments)||typeof t.createdAt!="string")&&r(),t.activationCause!=null){const o=t.activationCause;(typeof o.causeMessageId!="string"||typeof o.rootCauseMessageId!="string"||!Number.isInteger(o.hopCount)||o.hopCount<1||o.hopCount>32)&&r()}return t}function j(i,e,t){(!i||typeof i!="object")&&r();const o=i,n=o.room,a=o.agentIdentity,s=o.binding;return(o.outcome!=="created"||!n||typeof n.id!="string"||n.name!==e.name||n.visibility!=="private"||n.joinPolicy!=="approval"||!u(n.webUrl,t,`/spaces/${n.id}`)||!q(n.inviteUrl,t)||!a||typeof a.id!="string"||a.displayName!==e.agentAlias||!s||typeof s.id!="string"||s.roomId!==n.id||s.agentIdentityId!==a.id||typeof s.agentMembershipId!="string"||s.status!=="active"&&s.status!=="paused"||s.activationMode!==e.activationMode||typeof o.idempotent!="boolean")&&r(),o}function _(i,e,t){(!i||typeof i!="object")&&r();const o=i,n=o.room;return(o.outcome!=="created"||!n||typeof n.id!="string"||n.name!==e.name||n.visibility!=="private"||n.joinPolicy!=="approval"||!u(n.webUrl,t,`/spaces/${n.id}`))&&r(),o}function A(i,e,t){(!i||typeof i!="object")&&r();const o=i,n=o.room,a=o.agentIdentity,s=o.binding;return(o.outcome!=="joined"&&o.outcome!=="pending_approval"||!n||typeof n.id!="string"||typeof n.name!="string"||!u(n.webUrl,t,`/spaces/${n.id}`)||!a||typeof a.id!="string"||a.displayName!==e.agentAlias||!s||typeof s.id!="string"||s.roomId!==n.id||s.agentIdentityId!==a.id||s.activationMode!==e.activationMode||typeof o.idempotent!="boolean")&&r(),(o.outcome==="pending_approval"&&(s.status!=="pending_approval"||s.agentMembershipId!==null||typeof o.joinRequestId!="string")||o.outcome==="joined"&&(s.status!=="active"&&s.status!=="paused"||typeof s.agentMembershipId!="string"||o.joinRequestId!==void 0))&&r(),o}function d(i){return/^(0|[1-9]\d{0,19})$/.test(i)}function b(i){return i.length<=255&&i===i.trim()&&i!=="."&&i!==".."&&!/[\\/\0-\x1f\x7f]/.test(i)}function g(i){if(typeof i!="string")return!1;try{const e=new URL(i);return e.protocol==="https:"&&!e.username&&!e.password&&!e.hash}catch{return!1}}function I(i){return i==="auto_follow"||i==="mention_only"||i==="paused"}function r(){throw new c("invalid_room_action_response","Invalid Room action response.")}async function p(i){try{return await i.json()}catch{r()}}function y(i,e){let t=`room_action_http_${i}`;if(e&&typeof e=="object"){const o=e.error;typeof o=="string"&&/^[a-z0-9_-]{1,80}$/.test(o)&&(t=o)}return new c(t,"Shennian Room action failed.")}function u(i,e,t){return v(i,e,o=>o.pathname===t)}function q(i,e){return v(i,e,t=>/^\/spaces\/join\/[A-Za-z0-9_-]{43}$/.test(t.pathname))}function v(i,e,t){if(typeof i!="string")return!1;try{const o=new URL(i),n=new URL(e),a=o.origin===n.origin,s=n.protocol==="http:"&&o.protocol==="http:"&&o.hostname===n.hostname,m=o.protocol==="https:"&&o.port===""&&(o.hostname==="shennian.net"||o.hostname==="app.shennian.net");return(a||s||m)&&!o.username&&!o.password&&!o.search&&!o.hash&&t(o)}catch{return!1}}export{R as HttpRoomActionClient,c as RoomActionHttpError};
1
+ import v from"node:path";class c extends Error{code;constructor(e,n){super(n),this.code=e,this.name="RoomActionHttpError"}}class R{fetchImpl;baseUrl;constructor(e,n=fetch){this.fetchImpl=n,this.baseUrl=e.replace(/\/+$/,"")}async createOperatorRoom(e){let n;try{n=await this.fetchImpl(`${this.baseUrl}/api/rooms/actions/operator/create`,{method:"POST",headers:{authorization:`Bearer ${e.machineToken}`,"content-type":"application/json"},body:JSON.stringify({name:e.name}),signal:AbortSignal.timeout(1e4)})}catch{throw new c("room_action_server_unavailable","Shennian Room service is unavailable.")}const t=await p(n);if(!n.ok)throw y(n.status,t);return _(t,e,this.baseUrl)}async resolveNativeSession(e){let n;try{n=await this.fetchImpl(`${this.baseUrl}/api/rooms/actions/resolve-session`,{method:"POST",headers:{authorization:`Bearer ${e.machineToken}`,"content-type":"application/json"},body:JSON.stringify({runtimeAdapter:e.runtimeAdapter,sourceSessionKey:e.sourceSessionKey}),signal:AbortSignal.timeout(1e4)})}catch{throw new c("room_action_server_unavailable","Shennian Room service is unavailable.")}const t=await p(n);if(!n.ok)throw y(n.status,t);(!t||typeof t!="object")&&r();const o=t;return(typeof o.nianSessionId!="string"||!/^[A-Za-z0-9_.:@/-]{1,512}$/.test(o.nianSessionId)||typeof o.created!="boolean")&&r(),o}async createRoom(e){let n;try{n=await this.fetchImpl(`${this.baseUrl}/api/rooms/actions/create`,{method:"POST",headers:{authorization:`Bearer ${e.machineToken}`,"content-type":"application/json"},body:JSON.stringify({bindingIntentId:e.bindingIntentId,nianSessionId:e.nianSessionId,runtimeAdapter:e.runtimeAdapter,sessionRegistryKey:e.sessionRegistryKey,name:e.name,agentAlias:e.agentAlias,agentDescription:e.agentDescription,activationCapability:e.activationCapability,activationMode:e.activationMode}),signal:AbortSignal.timeout(1e4)})}catch{throw new c("room_action_server_unavailable","Shennian Room service is unavailable.")}const t=await p(n);if(!n.ok)throw y(n.status,t);return j(t,e,this.baseUrl)}async joinRoom(e){let n;try{n=await this.fetchImpl(`${this.baseUrl}/api/rooms/actions/join`,{method:"POST",headers:{authorization:`Bearer ${e.machineToken}`,"content-type":"application/json"},body:JSON.stringify({bindingIntentId:e.bindingIntentId,nianSessionId:e.nianSessionId,runtimeAdapter:e.runtimeAdapter,sessionRegistryKey:e.sessionRegistryKey,inviteToken:e.inviteToken,message:e.message,agentAlias:e.agentAlias,agentDescription:e.agentDescription,activationCapability:e.activationCapability,activationMode:e.activationMode}),signal:AbortSignal.timeout(1e4)})}catch{throw new c("room_action_server_unavailable","Shennian Room service is unavailable.")}const t=await p(n);if(!n.ok)throw y(n.status,t);return A(t,e,this.baseUrl)}async status(e){const n=await this.request(e,"status");return w(n,e)}async getAttachmentCapability(e){const n=await this.request(e,`attachments/${encodeURIComponent(e.attachmentId)}/download-capability`);(!n||typeof n!="object")&&r();const t=n;return(t.attachmentId!==e.attachmentId||t.roomId!==e.roomId||typeof t.safeName!="string"||!b(t.safeName)||typeof t.mimeType!="string"||t.mimeType.length<1||t.mimeType.length>191||typeof t.byteSize!="string"||!d(t.byteSize)||typeof t.sha256!="string"||!/^[a-f0-9]{64}$/.test(t.sha256)||!g(t.downloadUrl)||typeof t.expiresAt!="string"||!Number.isFinite(Date.parse(t.expiresAt)))&&r(),t}async initializeAttachment(e){const n=await this.request(e,"attachments/init",{method:"POST",body:JSON.stringify({clientUploadId:e.clientUploadId,name:e.name,mediaKind:e.mediaKind,mimeType:e.mimeType,byteSize:e.byteSize,sha256:e.sha256})});(!n||typeof n!="object")&&r();const t=n,o=t.attachment,a=t.upload;return(!o||typeof o.id!="string"||o.roomId!==e.roomId||typeof o.name!="string"||!b(o.name)||o.mimeType!==e.mimeType||o.byteSize!==String(e.byteSize)||o.sha256!==e.sha256||o.status!=="pending"&&o.status!=="uploaded"||!a||a.method!=="PUT"||!g(a.url)||!a.headers||typeof a.headers!="object"||a.headers["content-type"]!==e.mimeType||a.headers["x-oss-meta-sha256"]!==e.sha256||typeof a.expiresAt!="string"||!Number.isFinite(Date.parse(a.expiresAt))||Date.parse(a.expiresAt)<=Date.now()||typeof t.idempotent!="boolean")&&r(),t}async completeAttachment(e){const n=await this.request(e,`attachments/${encodeURIComponent(e.attachmentId)}/complete`,{method:"POST"});(!n||typeof n!="object")&&r();const t=n;typeof t.idempotent!="boolean"&&r();const o=t.attachment;return(!o||o.attachmentId!==e.attachmentId||o.mediaKind!=="image"&&o.mediaKind!=="audio"&&o.mediaKind!=="file"||typeof o.name!="string"||typeof o.mimeType!="string"||typeof o.byteSize!="string"||!d(o.byteSize)||typeof o.sha256!="string"||!/^[a-f0-9]{64}$/.test(o.sha256)||o.status!=="ready")&&r(),{attachment:o,idempotent:t.idempotent}}async read(e){if(e.beforeSequence&&e.afterSequence)throw new c("ambiguous_sequence_cursor","beforeSequence and afterSequence are mutually exclusive.");const n=new URLSearchParams({limit:String(e.limit)});e.beforeSequence&&n.set("before",e.beforeSequence),e.afterSequence&&n.set("after",e.afterSequence);const t=await this.request(e,`messages?${n}`);(!t||typeof t!="object")&&r();const o=t;return(!Array.isArray(o.messages)||typeof o.nextSequence!="string"||!d(o.nextSequence)||typeof o.hasMore!="boolean"||typeof o.historyFromSequence!="string"||!d(o.historyFromSequence)||typeof o.roomName!="string")&&r(),{messages:o.messages.map(s=>h(s,e.roomId)),nextSequence:o.nextSequence,hasMore:o.hasMore,historyFromSequence:o.historyFromSequence,roomName:o.roomName}}async send(e){const n=await this.request(e,"messages",{method:"POST",body:JSON.stringify({clientMessageId:e.clientMessageId,text:e.text,mentionMembershipIds:e.mentionMembershipIds,replyToMessageId:e.replyToMessageId,activationCauseMessageId:e.activationCauseMessageId,attachmentIds:e.attachmentIds})});(!n||typeof n!="object")&&r();const t=n;return typeof t.idempotent!="boolean"&&r(),{message:h(t.message,e.roomId),idempotent:t.idempotent}}async mode(e){const n=await this.request(e,"mode",{method:"PATCH",body:JSON.stringify({activationMode:e.activationMode})});(!n||typeof n!="object")&&r();const t=n;return(t.bindingId!==e.bindingId||t.roomId!==e.roomId||t.status!=="active"&&t.status!=="paused"||!I(t.activationMode)||t.activationMode!==e.activationMode||typeof t.idempotent!="boolean")&&r(),t}async request(e,n,t={}){let o;try{o=await this.fetchImpl(`${this.baseUrl}/api/rooms/${encodeURIComponent(e.roomId)}/bindings/${encodeURIComponent(e.bindingId)}/${n}`,{...t,headers:{authorization:`Bearer ${e.machineToken}`,...t.body?{"content-type":"application/json"}:{}},signal:AbortSignal.timeout(1e4)})}catch{throw new c("room_action_server_unavailable","Shennian Room service is unavailable.")}if(!o.ok){let a=`room_action_http_${o.status}`;try{const s=await o.json();typeof s.error=="string"&&/^[a-z0-9_-]{1,80}$/.test(s.error)&&(a=s.error)}catch{}throw new c(a,"Shennian Room action failed.")}try{return await o.json()}catch{r()}}}function w(i,e){(!i||typeof i!="object")&&r();const n=i,t=n.room,o=n.agent;(n.bindingId!==e.bindingId||n.roomId!==e.roomId||typeof n.agentIdentityId!="string"||n.agentMembershipId!==null&&typeof n.agentMembershipId!="string"||n.status!=="pending_approval"&&n.status!=="active"&&n.status!=="paused"&&n.status!=="detached"||!I(n.activationMode)||n.activationCapability!==void 0&&n.activationCapability!=="managed"&&n.activationCapability!=="idle_resume"&&n.activationCapability!=="notify_only"||n.managedWorkspacePath!==void 0&&n.managedWorkspacePath!==null&&(typeof n.managedWorkspacePath!="string"||n.managedWorkspacePath.length>4096||!v.isAbsolute(n.managedWorkspacePath))||typeof n.lastDeliveredSequence!="string"||!d(n.lastDeliveredSequence)||typeof n.lastActivatedSequence!="string"||!d(n.lastActivatedSequence)||!t||typeof t!="object"||o!==void 0&&(!o||typeof o!="object"))&&r(),(n.status==="pending_approval"&&n.agentMembershipId!==null||(n.status==="active"||n.status==="paused")&&typeof n.agentMembershipId!="string")&&r();const a=t,s=o,m=a.members;return(s!==void 0&&(typeof s.displayName!="string"||!s.displayName.trim()||typeof s.description!="string")||typeof a.name!="string"||typeof a.status!="string"||a.memberCount!==void 0&&(typeof a.memberCount!="number"||!Number.isInteger(a.memberCount)||a.memberCount<0)||typeof a.lastSequence!="string"||!d(a.lastSequence)||m!==void 0&&(!Array.isArray(m)||m.some(f=>{if(!f||typeof f!="object")return!0;const l=f;return typeof l.displayName!="string"||l.principalType!=="human"&&l.principalType!=="agent"})))&&r(),n}function h(i,e){(!i||typeof i!="object")&&r();const n=i;if((typeof n.id!="string"||n.roomId!==e||typeof n.sequence!="string"||!d(n.sequence)||!n.author||typeof n.author.membershipId!="string"||!Array.isArray(n.attachments)||typeof n.createdAt!="string")&&r(),n.activationCause!=null){const t=n.activationCause;(typeof t.causeMessageId!="string"||typeof t.rootCauseMessageId!="string"||!Number.isInteger(t.hopCount)||t.hopCount<1||t.hopCount>32)&&r()}return n}function j(i,e,n){(!i||typeof i!="object")&&r();const t=i,o=t.room,a=t.agentIdentity,s=t.binding;return(t.outcome!=="created"||!o||typeof o.id!="string"||o.name!==e.name||o.visibility!=="private"||o.joinPolicy!=="open"||!u(o.canonicalUrl,n,`/spaces/${o.id}`)||!q(o.inviteUrl,n,o.id)||!a||typeof a.id!="string"||a.displayName!==e.agentAlias||!s||typeof s.id!="string"||s.roomId!==o.id||s.agentIdentityId!==a.id||typeof s.agentMembershipId!="string"||s.status!=="active"&&s.status!=="paused"||s.activationMode!==e.activationMode||typeof t.idempotent!="boolean")&&r(),t}function _(i,e,n){(!i||typeof i!="object")&&r();const t=i,o=t.room;return(t.outcome!=="created"||!o||typeof o.id!="string"||o.name!==e.name||o.visibility!=="private"||o.joinPolicy!=="open"||!u(o.canonicalUrl,n,`/spaces/${o.id}`))&&r(),t}function A(i,e,n){(!i||typeof i!="object")&&r();const t=i,o=t.room,a=t.agentIdentity,s=t.binding;return(t.outcome!=="joined"&&t.outcome!=="pending_approval"||!o||typeof o.id!="string"||typeof o.name!="string"||!u(o.canonicalUrl,n,`/spaces/${o.id}`)||!a||typeof a.id!="string"||a.displayName!==e.agentAlias||!s||typeof s.id!="string"||s.roomId!==o.id||s.agentIdentityId!==a.id||s.activationMode!==e.activationMode||typeof t.idempotent!="boolean")&&r(),(t.outcome==="pending_approval"&&(s.status!=="pending_approval"||s.agentMembershipId!==null||typeof t.joinRequestId!="string")||t.outcome==="joined"&&(s.status!=="active"&&s.status!=="paused"||typeof s.agentMembershipId!="string"||t.joinRequestId!==void 0))&&r(),t}function d(i){return/^(0|[1-9]\d{0,19})$/.test(i)}function b(i){return i.length<=255&&i===i.trim()&&i!=="."&&i!==".."&&!/[\\/\0-\x1f\x7f]/.test(i)}function g(i){if(typeof i!="string")return!1;try{const e=new URL(i);return e.protocol==="https:"&&!e.username&&!e.password&&!e.hash}catch{return!1}}function I(i){return i==="auto_follow"||i==="mention_only"||i==="paused"}function r(){throw new c("invalid_room_action_response","Invalid Room action response.")}async function p(i){try{return await i.json()}catch{r()}}function y(i,e){let n=`room_action_http_${i}`;if(e&&typeof e=="object"){const t=e.error;typeof t=="string"&&/^[a-z0-9_-]{1,80}$/.test(t)&&(n=t)}return new c(n,"Shennian Room action failed.")}function u(i,e,n){return S(i,e,t=>t.pathname===n)}function q(i,e,n){if(typeof i!="string")return!1;try{const t=new URL(i);return S(`${t.origin}${t.pathname}`,e,o=>o.pathname===`/spaces/${n}`)?t.searchParams.size===1&&/^[A-Za-z0-9_-]{32,256}$/.test(t.searchParams.get("invite")??"")&&!t.hash&&!t.username&&!t.password:!1}catch{return!1}}function S(i,e,n){if(typeof i!="string")return!1;try{const t=new URL(i),o=new URL(e),a=t.origin===o.origin,s=o.protocol==="http:"&&t.protocol==="http:"&&t.hostname===o.hostname,m=t.protocol==="https:"&&t.port===""&&t.hostname==="app.shennian.net";return(a||s||m)&&!t.username&&!t.password&&!t.search&&!t.hash&&n(t)}catch{return!1}}export{R as HttpRoomActionClient,c as RoomActionHttpError};
@@ -1,4 +1,4 @@
1
- // @arch docs/features/external-message-context-cache.md#agent-输入格式
1
+ // @arch docs/features/free-growth-phase.md
2
2
  // @test packages/wire/src/__tests__/external-message-format.test.ts
3
3
  function cleanText(value) {
4
4
  return typeof value === 'string' ? value.replace(/\s+/g, ' ').trim() : '';
@@ -33,6 +33,18 @@ export type RoomSummary = {
33
33
  membershipRole?: RoomMembershipRole;
34
34
  pinnedAt?: string | null;
35
35
  updatedAt: string;
36
+ /** Optional list projection used by local-first clients to avoid per-room requests. */
37
+ latestMessage?: RoomMessageView | null;
38
+ /** Compact active-member projection for the list avatar. */
39
+ avatarMembers?: Array<{
40
+ id: string;
41
+ principalType: RoomPrincipalType;
42
+ displayAlias: string;
43
+ /** Stable membership ordering key; older servers may omit it. */
44
+ joinedAt?: string;
45
+ avatar?: Record<string, unknown> | null;
46
+ ownerAvatar?: Record<string, unknown> | null;
47
+ }>;
36
48
  };
37
49
  export type RoomAuthorSnapshot = {
38
50
  membershipId: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shennian",
3
- "version": "0.3.40",
3
+ "version": "0.3.42",
4
4
  "description": "Shennian — AI Agent Control Plane CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -40,12 +40,14 @@
40
40
  "@sinclair/typebox": "^0.34.49",
41
41
  "chalk": "^5.4.1",
42
42
  "commander": "^13.1.0",
43
+ "qrcode": "^1.5.4",
43
44
  "qrcode-terminal": "^0.12.0",
44
45
  "ws": "^8.18.1",
45
46
  "@shennian/wire": "0.1.5"
46
47
  },
47
48
  "devDependencies": {
48
49
  "@types/node": "^20",
50
+ "@types/qrcode": "^1.5.6",
49
51
  "@types/qrcode-terminal": "^0.12.2",
50
52
  "@types/ws": "^8.18.1",
51
53
  "esbuild": "^0.27.4",
@@ -1,7 +0,0 @@
1
- import type { Command } from 'commander';
2
- type FetchLike = typeof fetch;
3
- type AgentCommandOptions = {
4
- fetchImpl?: FetchLike;
5
- };
6
- export declare function registerAgentCommand(program: Command, options?: AgentCommandOptions): void;
7
- export {};
@@ -1,3 +0,0 @@
1
- import{loadConfig as f}from"../config/index.js";import{SERVERS as A}from"../region.js";function k(t,n={}){const e=w(n.fetchImpl??fetch),a=t.command("agent").description("Create and manage stable Shennian Agents");a.command("create").description("Create a stable Agent owned by the paired user").requiredOption("--name <name>","Agent display name").requiredOption("--description <description>","Public Agent responsibility").requiredOption("--runtime <runtime>","Agent runtime, for example codex or claude").requiredOption("--workdir <absolute-path>","Absolute working directory").option("--model <model>","Default model").option("--system-prompt <prompt>","Private system prompt").action(async o=>{await u("agent.create",()=>e("POST","/api/agents",{displayName:o.name,description:o.description,runtimeAdapter:o.runtime,workDir:o.workdir,...o.model!=null?{modelId:o.model}:{},...o.systemPrompt!=null?{systemPrompt:o.systemPrompt}:{}}))}),a.command("get <agent-ref>").description("Get one explicitly referenced stable Agent").action(async o=>{const r=g(o);if(!r)return d("invalid_agent_ref","agent-ref is invalid.",null);await u("agent.get",()=>e("GET",`/api/agents/${encodeURIComponent(r)}`))}),a.command("update <agent-ref>").description("Update fields on one explicitly referenced stable Agent").option("--name <name>","Agent display name").option("--description <description>","Public Agent responsibility").option("--runtime <runtime>","Agent runtime").option("--workdir <absolute-path>","Absolute working directory").option("--model <model>","Default model; use an empty value to clear").option("--system-prompt <prompt>","Private system prompt; use an empty value to clear").action(async(o,r)=>{const s=g(o);if(!s)return d("invalid_agent_ref","agent-ref is invalid.",null);const i={};if(r.name!=null&&(i.displayName=r.name),r.description!=null&&(i.description=r.description),r.runtime!=null&&(i.runtimeAdapter=r.runtime),r.workdir!=null&&(i.workDir=r.workdir),r.model!=null&&(i.modelId=r.model),r.systemPrompt!=null&&(i.systemPrompt=r.systemPrompt),Object.keys(i).length===0)return d("no_changes","Provide at least one field to update.",null);await u("agent.update",()=>e("PATCH",`/api/agents/${encodeURIComponent(s)}`,i))}),l(a,"add"),l(a,"list"),l(a,"remove")}function l(t,n){const e=n==="list"?n:`${n} [name]`;t.command(e,{hidden:!0}).allowUnknownOption(!0).description(`Moved to shennian runtime ${n}`).action(()=>d("command_moved",`Custom runtime management moved to "shennian runtime ${n}".`,{type:"run_command",command:`shennian runtime ${n}`}))}function w(t){return async(n,e,a)=>{const o=f();if(!o.machineToken)throw new m("not_paired","Shennian CLI is not paired.",{type:"run_command",command:"shennian pair --browser"});const r=(o.serverUrl??A.cn.url).replace(/\/+$/,"");let s;try{s=await t(`${r}${e}`,{method:n,headers:{authorization:`Bearer ${o.machineToken}`,...a==null?{}:{"content-type":"application/json"}},...a==null?{}:{body:JSON.stringify(a)},signal:AbortSignal.timeout(15e3)})}catch{throw new m("server_unavailable","Shennian service is unavailable.",null)}const i=await h(s);if(!s.ok){const p=y(i)?i:{};throw new m(typeof p.code=="string"?`agent_${p.code}`:`http_${s.status}`,typeof p.error=="string"?p.error:"Agent request failed.",null)}const c=y(i)&&y(i.agent)?i.agent:null;if(!c||typeof c.id!="string"||typeof c.displayName!="string")throw new m("invalid_server_response","Shennian returned an invalid Agent response.",null);return c}}async function u(t,n){try{const e=await n();process.stdout.write(`${JSON.stringify({ok:!0,command:t,result:{agentRef:e.id,name:e.displayName,description:e.description,runtime:e.runtimeAdapter,workdir:e.workDir,model:e.modelId,systemPromptConfigured:!!e.systemPrompt,avatar:e.avatar??null,status:e.status,createdAt:e.createdAt,updatedAt:e.updatedAt}})}
2
- `)}catch(e){if(e instanceof m){d(e.code,e.message,e.nextAction);return}d("agent_command_failed","Shennian Agent command failed.",null)}}class m extends Error{code;nextAction;constructor(n,e,a){super(e),this.code=n,this.nextAction=a}}function g(t){const n=t.startsWith("agent:")?t.slice(6):t;return/^[A-Za-z0-9_-]{1,191}$/.test(n)?n:null}async function h(t){try{return await t.json()}catch{return null}}function y(t){return!!t&&typeof t=="object"&&!Array.isArray(t)}function d(t,n,e){process.stdout.write(`${JSON.stringify({ok:!1,error:t,message:n,nextAction:e})}
3
- `),process.exitCode=1}export{k as registerAgentCommand};