shennian 0.3.4 → 0.3.6

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.
@@ -31,8 +31,8 @@
31
31
  },
32
32
  {
33
33
  "file": "src/agents/codex.js",
34
- "beforeBytes": 39045,
35
- "afterBytes": 19734
34
+ "beforeBytes": 42453,
35
+ "afterBytes": 21170
36
36
  },
37
37
  {
38
38
  "file": "src/agents/command-spec.js",
@@ -266,8 +266,8 @@
266
266
  },
267
267
  {
268
268
  "file": "src/index.js",
269
- "beforeBytes": 25091,
270
- "afterBytes": 13039
269
+ "beforeBytes": 25142,
270
+ "afterBytes": 13054
271
271
  },
272
272
  {
273
273
  "file": "src/integrations/claude-room-plugin-lifecycle.js",
@@ -366,8 +366,8 @@
366
366
  },
367
367
  {
368
368
  "file": "src/native-fusion/service.js",
369
- "beforeBytes": 7549,
370
- "afterBytes": 3456
369
+ "beforeBytes": 7841,
370
+ "afterBytes": 3598
371
371
  },
372
372
  {
373
373
  "file": "src/native-fusion/state.js",
@@ -466,8 +466,8 @@
466
466
  },
467
467
  {
468
468
  "file": "src/room/managed-room-binding.js",
469
- "beforeBytes": 2323,
470
- "afterBytes": 1329
469
+ "beforeBytes": 3203,
470
+ "afterBytes": 1658
471
471
  },
472
472
  {
473
473
  "file": "src/room/managed-room-session.js",
@@ -561,8 +561,8 @@
561
561
  },
562
562
  {
563
563
  "file": "src/session/handlers/chat.js",
564
- "beforeBytes": 29785,
565
- "afterBytes": 13067
564
+ "beforeBytes": 30611,
565
+ "afterBytes": 13371
566
566
  },
567
567
  {
568
568
  "file": "src/session/handlers/control.js",
@@ -25,6 +25,10 @@ export type AgentEvent = {
25
25
  source?: string;
26
26
  agentSessionId?: string;
27
27
  };
28
+ export type AgentSendResult = {
29
+ deliveryMode?: 'direct' | 'external_owner_queue';
30
+ queuedSubmissionId?: string;
31
+ };
28
32
  export interface AgentAdapterEvents {
29
33
  agentEvent: [event: AgentEvent];
30
34
  error: [error: Error];
@@ -44,7 +48,7 @@ export declare abstract class AgentAdapter extends EventEmitter<AgentAdapterEven
44
48
  }>;
45
49
  setTitle?(agentSessionId: string, title: string, workDir?: string): Promise<void>;
46
50
  abstract start(sessionId: string, workDir: string, agentSessionId?: string | null): Promise<void>;
47
- abstract send(text: string, modelId?: string, reasoningEffort?: string, attachments?: ChatAttachmentMeta[], systemPrompt?: string | null): Promise<void>;
51
+ abstract send(text: string, modelId?: string, reasoningEffort?: string, attachments?: ChatAttachmentMeta[], systemPrompt?: string | null): Promise<void | AgentSendResult>;
48
52
  abstract resume(agentSessionId: string): Promise<void>;
49
53
  abstract stop(): Promise<void>;
50
54
  }
@@ -1,7 +1,8 @@
1
- import { AgentAdapter } from './adapter.js';
1
+ import { AgentAdapter, type AgentSendResult } from './adapter.js';
2
2
  import type { ChatAttachmentMeta, ExternalChannelSessionStatus } from '@shennian/wire';
3
3
  import type { SessionRunPhase } from '@shennian/wire';
4
4
  export { isCodexUnsupportedEffortError, isMissingCodexRolloutError, normalizeCodexModelId, normalizeCodexReasoningEffort, } from './codex-utils.js';
5
+ export declare function isCodexActiveWriterError(error: unknown): boolean;
5
6
  export declare class CodexAdapter extends AgentAdapter {
6
7
  private readonly options;
7
8
  readonly type: "codex";
@@ -38,7 +39,8 @@ export declare class CodexAdapter extends AgentAdapter {
38
39
  env?: NodeJS.ProcessEnv;
39
40
  }): void;
40
41
  start(_sessionId: string, workDir: string, agentSessionId?: string | null): Promise<void>;
41
- send(text: string, modelId?: string, reasoningEffort?: string, attachments?: ChatAttachmentMeta[], agentSystemPrompt?: string | null): Promise<void>;
42
+ send(text: string, modelId?: string, reasoningEffort?: string, attachments?: ChatAttachmentMeta[], agentSystemPrompt?: string | null): Promise<void | AgentSendResult>;
43
+ private enqueueForExternalOwner;
42
44
  resume(agentSessionId: string): Promise<void>;
43
45
  setTitle(agentSessionId: string, title: string, workDir?: string): Promise<void>;
44
46
  stop(): Promise<void>;
@@ -1,16 +1,17 @@
1
- import{spawnSync as x}from"node:child_process";import{createInterface as u}from"node:readline";import{randomUUID as I}from"node:crypto";import S from"node:path";import{AgentAdapter as T,registerAgent as k}from"./adapter.js";import{resolveBuiltinCommand as h,spawnAgentCommand as m}from"./command-spec.js";import{buildAgentProcessEnv as f}from"../agent-env.js";import{ensurePlatformInstructionsFile as w}from"./platform-instructions.js";import{CODEX_APP_SERVER_CLIENT_INFO as E,extractAppServerErrorMessage as C,formatCodexErrorMessage as A,formatTransientCodexStatus as _,isCodexCollabAgentItem as R,isCodexCollabAgentToolName as d,isCodexLegacyCollabAgentItem as P,isCodexUnsupportedEffortError as q,isMissingCodexRolloutError as O,isTransientCodexErrorMessage as $,looksLikeCodexInteractiveAuthPrompt as b,looksLikeFatalCodexStderr as F,makeThreadTitle as L,normalizeCodexModelId as g,normalizeCodexReasoningEffort as U,normalizeCodexStderr as v,normalizeTerminalText as M,safeStringify as y,stripGitDirectiveArtifacts as N}from"./codex-utils.js";import{isCodexUnsupportedEffortError as re,isMissingCodexRolloutError as ne,normalizeCodexModelId as ie,normalizeCodexReasoningEffort as ae}from"./codex-utils.js";function D(a,e){const t=e?.flatMap(r=>z(r)).filter(r=>!/^https?:\/\//i.test(r.path)).filter(r=>r.path);return{type:"text",text:W(a,e),text_elements:[],...t?.length?{local_images:t}:{}}}function z(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=B(a.path);return e?[{path:e}]:[]}return[]}function B(a){if(!a||/^https?:\/\//i.test(a))return null;const e=S.extname(a),s=`${e?a.slice(0,-e.length):a}.preview.png`;return x("ffmpeg",["-y","-ss","0","-i",a,"-frames:v","1","-vf","scale=320:-1",s],{stdio:"ignore",timeout:15e3}).status===0?s:null}function W(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`[${G(o)}${l}](<${i.path}>)`});return[a,"",...r].join(`
2
- `)}function G(a){return a.replace(/[[\]\\]/g,"\\$&")}class J extends T{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=g(t),o=U(s),l=i?.trim()??"";l!==this.agentSystemPrompt&&(this.agentSystemPrompt=l,this.process&&await this.killProcess()),this.runId=I(),this.seq=0,this.resetRunState(),await this.ensureAppServer(n);const p=this.agentSessionId;if(!p)throw new Error("codex app-server did not create a thread");this.emitEvent({state:"start",agentSessionId:p}),this.namedThread||(await this.sendRpc("thread/name/set",{threadId:p,name:L(e)}).catch(()=>{}),this.namedThread=!0);const c=await this.startTurnWithRecovery(p,e,n,o,r);this.activeTurnId=c.turn?.id??null}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=h("codex");if(!t){this.emit("error",new Error('Command "codex" not found. Is OpenAI Codex CLI installed?'));return}const s=m(t,e,{cwd:this.workDir??void 0,stdio:["ignore","pipe","pipe"],env:f(this.extraEnv)});this.process=s,u({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=h("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??w(this.workDir??process.cwd(),this.externalChannel,this.shennianSessionId??void 0,this.agentSystemPrompt);s&&t.push("-c",`model_instructions_file=${JSON.stringify(s)}`);const r=m(e,t,{cwd:this.workDir??void 0,stdio:["pipe","pipe","pipe"],env:f({NO_COLOR:"1",...this.extraEnv})});this.process=r,this.stderrBuf="",this.appServerStartupError=null,u({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(v(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=g(e);if(await this.sendRpc("initialize",{clientInfo:E,capabilities:{experimentalApi:!0}}),this.agentSessionId){const i=await this.sendRpc("thread/resume",{threadId:this.agentSessionId,cwd:this.workDir,approvalPolicy:"never",sandbox:"danger-full-access",persistExtendedHistory:!0,...this.options.hidden?{threadSource:"subagent"}:{},...t?{model:t}:{}});this.agentSessionId=i.thread?.id??this.agentSessionId,this.namedThread=!!i.thread?.name;return}const s=await this.sendRpc("thread/start",{cwd:this.workDir,approvalPolicy:"never",sandbox:"danger-full-access",serviceName:"Shennian",threadSource:this.options.hidden?"subagent":"user",experimentalRawEvents:!1,persistExtendedHistory:!0,...t?{model:t}:{}}),r=s.thread?.id;if(!r)throw new Error("codex app-server thread/start returned no thread id");this.agentSessionId=r,this.namedThread=!!s.thread?.name}async startTurnWithRecovery(e,t,s,r,i){try{return await this.startTurn(e,t,s,r,i)}catch(n){if(!O(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:[D(t,i)],approvalPolicy:"never",sandboxPolicy:{type:"dangerFullAccess"},...s?{model:s}:{},...r?{effort:r}:{}})}catch(n){throw r&&q(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 p=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:p}),r.stdin.write(`${n}
3
- `,c=>{c&&(clearTimeout(p),this.pendingRequests.delete(i),l(c))})})}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=C(t);if($(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=M(e);b(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")&&!R(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?`
4
- (exit ${t.exitCode})`:""].join("").trim()});break}case"mcpToolCall":case"dynamicToolCall":{const s=typeof t.tool=="string"?t.tool:t.type;if(d(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:y(t.error??t.result??t.contentItems??"")});break}case"fileChange":{const r=(Array.isArray(t.changes)?t.changes:[]).map(i=>{if(typeof i!="object"||i===null)return"";const n=i,o=typeof n.path=="string"?n.path:typeof n.file=="string"?n.file:"",l=typeof n.kind=="string"?n.kind:typeof n.type=="string"?n.type:"change";return o?`${l}: ${o}`:""}).filter(Boolean).join(`
5
- `);r&&this.emitText(r,"block");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&&!P(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?`
6
- (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&&d(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??y(t.result)})}break}case"file_change":{if(e.type==="item.completed"&&t.changes?.length){const s=t.changes.map(r=>`${r.kind}: ${r.path}`).join(`
1
+ import{spawnSync as R}from"node:child_process";import{createInterface as w}from"node:readline";import{randomUUID as P}from"node:crypto";import q from"node:path";import{AgentAdapter as O,registerAgent as $}from"./adapter.js";import{resolveBuiltinCommand as v,spawnAgentCommand as y}from"./command-spec.js";import{buildAgentProcessEnv as x}from"../agent-env.js";import{ensurePlatformInstructionsFile as b}from"./platform-instructions.js";import{CODEX_APP_SERVER_CLIENT_INFO as F,extractAppServerErrorMessage as L,formatCodexErrorMessage as M,formatTransientCodexStatus as U,isCodexCollabAgentItem as N,isCodexCollabAgentToolName as f,isCodexLegacyCollabAgentItem as D,isCodexUnsupportedEffortError as B,isMissingCodexRolloutError as W,isTransientCodexErrorMessage as z,looksLikeCodexInteractiveAuthPrompt as G,looksLikeFatalCodexStderr as J,makeThreadTitle as H,normalizeCodexModelId as k,normalizeCodexReasoningEffort as V,normalizeCodexStderr as E,normalizeTerminalText as K,safeStringify as C,stripGitDirectiveArtifacts as Q}from"./codex-utils.js";import{isCodexUnsupportedEffortError as he,isMissingCodexRolloutError as me,normalizeCodexModelId as fe,normalizeCodexReasoningEffort as ge}from"./codex-utils.js";function X(a){const e=a instanceof Error?a.message:String(a);return/already has an active writer|thread-store conflict/i.test(e)}function _(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=q.extname(a),s=`${e?a.slice(0,-e.length):a}.preview.png`;return R("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`[${ee(o)}${l}](<${i.path}>)`});return[a,"",...r].join(`
2
+ `)}function ee(a){return a.replace(/[[\]\\]/g,"\\$&")}class te extends O{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=k(t),o=V(s),l=i?.trim()??"";l!==this.agentSystemPrompt&&(this.agentSystemPrompt=l,this.process&&await this.killProcess()),this.runId=P(),this.seq=0,this.resetRunState();try{await this.ensureAppServer(n)}catch(u){if(!this.agentSessionId||!X(u))throw u;return{deliveryMode:"external_owner_queue",queuedSubmissionId:await this.enqueueForExternalOwner(this.agentSessionId,e,r)}}const c=this.agentSessionId;if(!c)throw new Error("codex app-server did not create a thread");this.emitEvent({state:"start",agentSessionId:c}),this.namedThread||(await this.sendRpc("thread/name/set",{threadId:c,name:H(e)}).catch(()=>{}),this.namedThread=!0);const p=await this.startTurnWithRecovery(c,e,n,o,r);return this.activeTurnId=p.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=_(t,s),n=typeof i.text=="string"?i.text:t,o=Array.isArray(i.local_images)?i.local_images.map(p=>p&&typeof p=="object"&&typeof p.path=="string"?p.path:"").filter(Boolean):[],l=["queue","--thread",e,"--message",n];for(const p of o)l.push("-i",p);const c=y(r,l,{cwd:this.workDir??void 0,stdio:["ignore","pipe","pipe"],env:x({NO_COLOR:"1",...this.extraEnv})});return await new Promise((p,u)=>{let g="",I="",S=!1;const h=(d,m)=>{S||(S=!0,clearTimeout(T),d?u(d):p(m))},T=setTimeout(()=>{c.kill("SIGTERM"),h(new Error("Codex Desktop message queue timed out"))},6e4);T.unref?.(),c.stdout?.on("data",d=>{g+=d.toString()}),c.stderr?.on("data",d=>{I+=d.toString()}),c.once("error",d=>h(d)),c.once("close",d=>{const m=`${g}
3
+ ${I}`.trim();if(d!==0){h(new Error(m||`codex queue exited with code ${d??"unknown"}`));return}const A=m.match(/Queued message\s+([^\s]+)\s+for thread/i)?.[1];h(void 0,A)})})}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=y(t,e,{cwd:this.workDir??void 0,stdio:["ignore","pipe","pipe"],env:x(this.extraEnv)});this.process=s,w({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=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??b(this.workDir??process.cwd(),this.externalChannel,this.shennianSessionId??void 0,this.agentSystemPrompt);s&&t.push("-c",`model_instructions_file=${JSON.stringify(s)}`);const r=y(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,w({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(E(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=k(e);if(await this.sendRpc("initialize",{clientInfo:F,capabilities:{experimentalApi:!0}}),this.agentSessionId){const i=await this.sendRpc("thread/resume",{threadId:this.agentSessionId,cwd:this.workDir,approvalPolicy:"never",sandbox:"danger-full-access",persistExtendedHistory:!0,...this.options.hidden?{threadSource:"subagent"}:{},...t?{model:t}:{}});this.agentSessionId=i.thread?.id??this.agentSessionId,this.namedThread=!!i.thread?.name;return}const s=await this.sendRpc("thread/start",{cwd:this.workDir,approvalPolicy:"never",sandbox:"danger-full-access",serviceName:"Shennian",threadSource:this.options.hidden?"subagent":"user",experimentalRawEvents:!1,persistExtendedHistory:!0,...t?{model:t}:{}}),r=s.thread?.id;if(!r)throw new Error("codex app-server thread/start returned no thread id");this.agentSessionId=r,this.namedThread=!!s.thread?.name}async startTurnWithRecovery(e,t,s,r,i){try{return await this.startTurn(e,t,s,r,i)}catch(n){if(!W(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:[_(t,i)],approvalPolicy:"never",sandboxPolicy:{type:"dangerFullAccess"},...s?{model:s}:{},...r?{effort:r}:{}})}catch(n){throw r&&B(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 c=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:c}),r.stdin.write(`${n}
4
+ `,p=>{p&&(clearTimeout(c),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=L(t);if(z(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=K(e);G(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")&&!N(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?`
5
+ (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:C(t.error??t.result??t.contentItems??"")});break}case"fileChange":{const r=(Array.isArray(t.changes)?t.changes:[]).map(i=>{if(typeof i!="object"||i===null)return"";const n=i,o=typeof n.path=="string"?n.path:typeof n.file=="string"?n.file:"",l=typeof n.kind=="string"?n.kind:typeof n.type=="string"?n.type:"change";return o?`${l}: ${o}`:""}).filter(Boolean).join(`
6
+ `);r&&this.emitText(r,"block");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&&!D(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??C(t.result)})}break}case"file_change":{if(e.type==="item.completed"&&t.changes?.length){const s=t.changes.map(r=>`${r.kind}: ${r.path}`).join(`
7
8
  `);this.emitText(s,"block")}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&&d(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&&d(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=v(t);if(e!==0&&e!==null){const r=s||`codex exited with code ${e}`;this.emitErrorIfOpen(r)}else F(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}))}emitErrorIfOpen(e){this.terminalState==="open"&&(this.terminalState="error",this.emitEvent({state:"error",message:A(e)}),this.scheduleProcessClose())}resetRunState(){this.pendingUsage=void 0,this.terminalState="open",this.deltaItemIds.clear(),this.lastTransientStatus="",this.clearForceCloseTimer(),this.resetTextState()}emitTransientStatus(e){const t=_(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=N(e);s&&(t==="block"&&this.hasEmittedText&&!s.startsWith(`
9
+ `);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=E(t);if(e!==0&&e!==null){const r=s||`codex exited with code ${e}`;this.emitErrorIfOpen(r)}else J(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:M(e)}),this.scheduleProcessClose())}resetRunState(){this.pendingUsage=void 0,this.terminalState="open",this.deltaItemIds.clear(),this.lastTransientStatus="",this.clearForceCloseTimer(),this.resetTextState()}emitTransientStatus(e){const t=U(e);!t||t===this.lastTransientStatus||this.terminalState!=="open"||(this.lastTransientStatus=t,this.emitEvent({state:"delta",text:`${t}
10
+ `,thinking:!0}))}resetTextState(){this.hasEmittedText=!1,this.textTail=""}emitText(e,t="delta"){if(!e)return;let s=Q(e);s&&(t==="block"&&this.hasEmittedText&&!s.startsWith(`
10
11
  `)&&(this.textTail.endsWith(`
11
12
  `)?s=`
12
13
  ${s}`:this.textTail.endsWith(`
13
14
 
14
15
  `)||(s=`
15
16
 
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)}}k("codex",()=>new J);export{J as CodexAdapter,re as isCodexUnsupportedEffortError,ne as isMissingCodexRolloutError,ie as normalizeCodexModelId,ae as normalizeCodexReasoningEffort};
17
+ ${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)}}$("codex",()=>new te);export{te as CodexAdapter,X as isCodexActiveWriterError,he as isCodexUnsupportedEffortError,me as isMissingCodexRolloutError,fe as normalizeCodexModelId,ge as normalizeCodexReasoningEffort};
package/dist/src/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import{Command as ae}from"commander";import I from"chalk";import U from"node:os";import b from"node:fs";import{loadConfig as a,saveConfig as J,getConfigPath as ce,getShennianDir as me,resolveShennianPath as W,normalizeAutoUpgradePolicy as le}from"./config/index.js";import{CliRelayClient as de}from"./relay/client.js";import{registerPairCommand as pe,runSmartStart as ge}from"./commands/pair.js";import{clearDaemonPidIfOwner as j,createDaemonInstanceId as he,clearDaemonLauncher as K,findRunningDaemonProcessIds as ue,isRemoteAccessDisabled as fe,isIsolatedDaemonE2eInstance as ve,registerDaemonCommand as Ie,writeDaemonPid as q,writeDaemonLauncher as Q}from"./commands/daemon.js";import{registerAgentCommand as Se}from"./commands/agent.js";import{registerManagerCommand as we}from"./commands/manager.js";import{registerUpgradeCommand as ye}from"./commands/upgrade.js";import{registerToolsCommand as Re}from"./commands/tools.js";import{registerIntegrationCommand as ke}from"./commands/integration.js";import{registerRoomCommand as Ce}from"./commands/room.js";import{SessionManager as Te}from"./session/manager.js";import{SERVERS as O,regionToUrl as Ae,urlToRegion as De}from"./region.js";import{getCurrentVersion as X,handleStartupCrashCheck as $e}from"./upgrade/engine.js";import{AutoUpgradeScheduler as Ee}from"./upgrade/scheduler.js";import{detectAgents as Ue}from"./agents/detect.js";import{augmentProcessPath as Y}from"./env-path.js";Y();const Z=X();import{getCachedAgentInfos as be,resolveAgentInfos as Oe}from"./agents/model-registry.js";import{initCliLogReporter as Ne,reportLog as S}from"./log-reporter.js";import{isNativeFusionEnabled as Pe}from"./native-fusion/config.js";import{NativeSessionFusionService as Me}from"./native-fusion/service.js";import{startDaemonLogRetention as Le}from"./daemon-log.js";import{startDaemonIpcServer as He}from"./daemon-ipc/server.js";import{createAuthorizedActionIpcHandlers as xe}from"./room/authorized-action-ipc.js";import{PendingAuthorizedActionStore as _e}from"./room/pending-authorized-actions.js";import{HttpDeviceAuthorizationClient as ze}from"./room/device-authorization-client.js";import{AuthorizedActionCoordinator as Ve}from"./room/authorized-action-coordinator.js";import{RoomSessionRegistry as Be}from"./room/session-registry.js";import{createRoomSessionRegistryIpcHandlers as Fe}from"./room/session-registry-ipc.js";import{RoomInvocationTokenAuthority as Ge}from"./room/invocation-token.js";import{createRoomInvocationTokenIpcHandlers as Je}from"./room/invocation-token-ipc.js";import{HttpRoomBindingClient as We}from"./room/room-binding-client.js";import{TrustedManagedRoomBinder as je}from"./room/managed-room-binding.js";import{createRoomBindingIpcHandlers as Ke}from"./room/room-binding-ipc.js";import{RoomHostSessionObservationStore as qe}from"./room/host-session-observations.js";import{createRoomHostSessionObservationIpcHandlers as Qe}from"./room/host-session-observation-ipc.js";import{runWorkBuddySessionHook as Xe}from"./integrations/workbuddy-session-hook.js";import{runRoomSessionHook as Ye}from"./integrations/room-session-hook.js";import{RoomJsonlMirror as ee}from"./room/jsonl-mirror.js";import{RoomMirrorSyncService as Ze}from"./room/mirror-sync-service.js";import{RoomActivationService as eo}from"./room/activation-service.js";import{RoomActivationAudit as oo}from"./room/activation-audit.js";import{RoomAttachmentCache as no}from"./room/attachment-cache.js";import{RoomAttachmentUploader as to}from"./room/attachment-upload.js";import{runRoomToolServer as ro}from"./room/tool-server.js";import{RoomAuthorizedActionExecutor as io}from"./room/room-authorized-action-executor.js";import{createRoomOperatorIpcHandlers as so}from"./room/operator-ipc.js";import{openExternalUrl as ao}from"./commands/pair-browser.js";const oe=me(),co=W("daemon.pid");function mo(){try{const o=b.readFileSync(co,"utf-8").trim();if(o.startsWith("{")){const r=JSON.parse(o),c=Number(r.pid);return Number.isInteger(c)&&c>0?c:null}const n=parseInt(o,10);return Number.isInteger(n)&&n>0?n:null}catch{return null}}function lo(o){return o.replace(/^https:\/\//,"wss://").replace(/^http:\/\//,"ws://")}function po(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 ne(o,n=5e3){const r=Date.now();for(;Date.now()-r<n;){try{process.kill(o,0)}catch{return!0}await new Promise(c=>setTimeout(c,100))}try{return process.kill(o,0),!1}catch{return!0}}const t=new ae;t.name("shennian").description("Shennian \u2014 AI Agent Control Plane").version(Z),t.enablePositionalOptions(),ke(t),Ce(t),t.option("--api <url>","Server URL override").option("--name <name>","Machine name",U.hostname()).action(async o=>{const n=a(),r=o.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??n.serverUrl??void 0;await ge(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=W("env.json");try{const e=JSON.parse(b.readFileSync(n,"utf-8"));for(const[s,m]of Object.entries(e))process.env[s]||(process.env[s]=m);Y()}catch{}const r=Le();fe()&&(console.log(`[${new Date().toISOString()}] remote access disabled, service start skipped`),clearInterval(r),process.exit(0));const c=!!(process.env.INVOCATION_ID||process.env.JOURNAL_STREAM||process.env.SHENNIAN_DESKTOP_SERVER_URL),N=async(e,s)=>{console.log(`[${new Date().toISOString()}] ${s} (PID ${e})`),process.kill(e,"SIGTERM"),await ne(e)||(process.kill(e,"SIGKILL"),await ne(e,2e3))};try{const e=mo();if(e&&e!==process.pid)try{process.kill(e,0),c?await N(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 A=ve(process.env,oe)?[]:ue(process.pid);if(A.length>0)if(c)for(const e of A)try{await N(e,"managed start taking over from orphan daemon")}catch{}else console.log(`[${new Date().toISOString()}] daemon already running (PID ${A[0]}), skipping duplicate start`),process.exit(0);const l=he();q(process.pid,l,{version:Z}),Q(process.pid,void 0,l),process.on("exit",()=>{j(process.pid,l),K(l)}),await $e()&&(console.log(`[${new Date().toISOString()}] Rolled back to previous version, restarting...`),process.exit(0));let i=a();const h=o.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??i.serverUrl??O.cn.url,u=X(),D=Ue(),f=D.map(e=>e.type),$=be(D),P=new _e,d=new Be,v=new qe,M=new Ge(d),p=new We(h),re=new no({actionClient:p}),ie=new to({actionClient:p}),L=new ze(h);let E=null;const H=await He({handlers:{...xe({store:P,authorizationClient:L,machineName:U.hostname(),machineOs:`${process.platform} ${U.release()}`,agentList:f,openPublicLoginUrl:ao}),...Fe({registry:d,observations:v,sessionResolver:p,getMachineCredentials:()=>{const e=a();return{machineId:e.machineId,machineToken:e.machineToken}}}),...Qe({store:v,registry:d,getMachineId:()=>a().machineId,onRegistrationIdle:e=>{E?.drain(e).catch(s=>{console.error("[room.activation] idle drain failed",s)})}}),...Je({authority:M,getMachineCredentials:()=>{const e=a();return{machineId:e.machineId,machineToken:e.machineToken}}}),...Ke({authority:M,registry:d,bindingClient:p,getMachineCredentials:()=>{const e=a();return{machineId:e.machineId,machineToken:e.machineToken}},mirrorFactory:e=>new ee(e),attachmentCache:re,attachmentUploader:ie,observations:v}),...so({registry:d,actionClient:p,getMachineCredentials:()=>{const e=a();return{machineId:e.machineId,machineToken:e.machineToken}}})}}),se=new io({registry:d,observations:v,actionClient:p,getMachineCredentials:()=>{const e=a();return{machineId:e.machineId,machineToken:e.machineToken}}}),x=new Ve(P,L,se,({machineId:e,machineToken:s})=>{J({...a(),machineId:e,machineToken:s,serverUrl:h})});let w=!1;const y=()=>{w||(w=!0,H.close().finally(()=>process.exit(0)))};if(process.once("SIGINT",y),process.once("SIGTERM",y),!i.machineToken){for(console.log(`[${new Date().toISOString()}] Waiting for browser device authorization...`);!w&&!i.machineToken;)await x.runOnce(),i=a(),i.machineToken||await new Promise(e=>setTimeout(e,1e3));if(w)return}process.removeListener("SIGINT",y),process.removeListener("SIGTERM",y);const _=setInterval(()=>{x.runOnce().catch(e=>{console.error("[room.authorization] pending action retry failed",e)})},1e3);_.unref?.();const z=`${lo(h)}/relay/machine`,V=i.machineToken;if(!V)return;i.machineId&&Ne(h,i.machineId),console.log(`[${new Date().toISOString()}] Connecting to ${z}... (v${u}) agents: ${f.join(",")}`),S({level:"info",wsEvent:"daemon.start",metadata:{version:u,agents:f}});let R=null,k=null;const B=le(i.autoUpgrade),g=new de({serverUrl:z,machineToken:V,cliVersion:u,agentList:f,autoUpgradePolicy:B,onConnected:()=>{console.log(`[${new Date().toISOString()}] \u2713 Connected`),S({level:"info",wsEvent:"daemon.connected"}),$.some(e=>e.models.length>0)&&g.sendEvent({type:"event",event:"machine.agents",payload:{agentList:f,agents:$}}),Oe(D,{serverUrl:i.serverUrl??h,authToken:i.machineToken??i.accessToken}).then(e=>{JSON.stringify(e)!==JSON.stringify($)&&g.sendEvent({type:"event",event:"machine.agents",payload:{agentList:f,agents:e}})}).catch(()=>{}),import("./upgrade/engine.js").then(({readUpgradeAttempt:e,clearUpgradeAttempt:s})=>{const m=e();s(),m&&(console.log(`[${new Date().toISOString()}] [upgrade] Reporting success: ${m.from} \u2192 ${m.to}`),g.sendEvent({type:"event",event:"upgrade",payload:{state:"success",machineId:"self",from:m.from,to:m.to}}))}).catch(()=>{}),C.start(),R?.handleConnected(),k?.start()},onDisconnected:e=>{k?.stop(),console.log(`[${new Date().toISOString()}] \u26A0 Disconnected, reconnecting... ${po(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}),T.handleReq(e)}});R=Pe()?new Me(g):null,k=new Ze({registry:d,actionClient:p,getMachineCredentials:()=>({machineToken:a().machineToken}),mirrorFactory:e=>new ee(e),onMessages:async(e,s,m)=>{await E?.processMessages(e,s,m)}});const C=new Ee({currentVersion:u,machineId:i.machineId??"unknown-machine",serverUrl:i.serverUrl??h,getIdleState:()=>T.getUpgradeIdleState(),startUpgrade:async e=>{const{handleUpgradeStart:s}=await import("./commands/upgrade.js");await s(g,"auto-upgrade",e,{currentVersion:u})}});C.setPolicy(B);const T=new Te(g,R,u,C,new je({registry:d,bindingClient:p,getMachineCredentials:()=>{const e=a();return{machineId:e.machineId,machineToken:e.machineToken}},hostProcessIdentity:()=>`daemon:${l}`}));E=new eo({registry:d,observations:v,activate:e=>T.activateRoomSession(e),audit:new oo}),b.mkdirSync(oe,{recursive:!0}),q(process.pid,l,{version:u}),Q(process.pid,void 0,l),g.connect(),process.stdin.resume();let F=!1;const G=async()=>{if(!F){F=!0,console.log(I.gray(`
1
+ import{Command as ae}from"commander";import I from"chalk";import U from"node:os";import b from"node:fs";import{loadConfig as a,saveConfig as J,getConfigPath as ce,getShennianDir as me,resolveShennianPath as W,normalizeAutoUpgradePolicy as le}from"./config/index.js";import{CliRelayClient as de}from"./relay/client.js";import{registerPairCommand as pe,runSmartStart as ge}from"./commands/pair.js";import{clearDaemonPidIfOwner as j,createDaemonInstanceId as he,clearDaemonLauncher as K,findRunningDaemonProcessIds as ue,isRemoteAccessDisabled as fe,isIsolatedDaemonE2eInstance as ve,registerDaemonCommand as Ie,writeDaemonPid as q,writeDaemonLauncher as Q}from"./commands/daemon.js";import{registerAgentCommand as Se}from"./commands/agent.js";import{registerManagerCommand as we}from"./commands/manager.js";import{registerUpgradeCommand as ye}from"./commands/upgrade.js";import{registerToolsCommand as Re}from"./commands/tools.js";import{registerIntegrationCommand as ke}from"./commands/integration.js";import{registerRoomCommand as Ce}from"./commands/room.js";import{SessionManager as Te}from"./session/manager.js";import{SERVERS as O,regionToUrl as Ae,urlToRegion as De}from"./region.js";import{getCurrentVersion as X,handleStartupCrashCheck as $e}from"./upgrade/engine.js";import{AutoUpgradeScheduler as Ee}from"./upgrade/scheduler.js";import{detectAgents as Ue}from"./agents/detect.js";import{augmentProcessPath as Y}from"./env-path.js";Y();const Z=X();import{getCachedAgentInfos as be,resolveAgentInfos as Oe}from"./agents/model-registry.js";import{initCliLogReporter as Ne,reportLog as S}from"./log-reporter.js";import{isNativeFusionEnabled as Pe}from"./native-fusion/config.js";import{NativeSessionFusionService as Me}from"./native-fusion/service.js";import{startDaemonLogRetention as Le}from"./daemon-log.js";import{startDaemonIpcServer as He}from"./daemon-ipc/server.js";import{createAuthorizedActionIpcHandlers as xe}from"./room/authorized-action-ipc.js";import{PendingAuthorizedActionStore as _e}from"./room/pending-authorized-actions.js";import{HttpDeviceAuthorizationClient as ze}from"./room/device-authorization-client.js";import{AuthorizedActionCoordinator as Ve}from"./room/authorized-action-coordinator.js";import{RoomSessionRegistry as Be}from"./room/session-registry.js";import{createRoomSessionRegistryIpcHandlers as Fe}from"./room/session-registry-ipc.js";import{RoomInvocationTokenAuthority as Ge}from"./room/invocation-token.js";import{createRoomInvocationTokenIpcHandlers as Je}from"./room/invocation-token-ipc.js";import{HttpRoomBindingClient as We}from"./room/room-binding-client.js";import{TrustedManagedRoomBinder as je}from"./room/managed-room-binding.js";import{createRoomBindingIpcHandlers as Ke}from"./room/room-binding-ipc.js";import{RoomHostSessionObservationStore as qe}from"./room/host-session-observations.js";import{createRoomHostSessionObservationIpcHandlers as Qe}from"./room/host-session-observation-ipc.js";import{runWorkBuddySessionHook as Xe}from"./integrations/workbuddy-session-hook.js";import{runRoomSessionHook as Ye}from"./integrations/room-session-hook.js";import{RoomJsonlMirror as ee}from"./room/jsonl-mirror.js";import{RoomMirrorSyncService as Ze}from"./room/mirror-sync-service.js";import{RoomActivationService as eo}from"./room/activation-service.js";import{RoomActivationAudit as oo}from"./room/activation-audit.js";import{RoomAttachmentCache as no}from"./room/attachment-cache.js";import{RoomAttachmentUploader as to}from"./room/attachment-upload.js";import{runRoomToolServer as ro}from"./room/tool-server.js";import{RoomAuthorizedActionExecutor as io}from"./room/room-authorized-action-executor.js";import{createRoomOperatorIpcHandlers as so}from"./room/operator-ipc.js";import{openExternalUrl as ao}from"./commands/pair-browser.js";const oe=me(),co=W("daemon.pid");function mo(){try{const o=b.readFileSync(co,"utf-8").trim();if(o.startsWith("{")){const r=JSON.parse(o),c=Number(r.pid);return Number.isInteger(c)&&c>0?c:null}const n=parseInt(o,10);return Number.isInteger(n)&&n>0?n:null}catch{return null}}function lo(o){return o.replace(/^https:\/\//,"wss://").replace(/^http:\/\//,"ws://")}function po(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 ne(o,n=5e3){const r=Date.now();for(;Date.now()-r<n;){try{process.kill(o,0)}catch{return!0}await new Promise(c=>setTimeout(c,100))}try{return process.kill(o,0),!1}catch{return!0}}const t=new ae;t.name("shennian").description("Shennian \u2014 AI Agent Control Plane").version(Z),t.enablePositionalOptions(),ke(t),Ce(t),t.option("--api <url>","Server URL override").option("--name <name>","Machine name",U.hostname()).action(async o=>{const n=a(),r=o.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??n.serverUrl??void 0;await ge(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=W("env.json");try{const e=JSON.parse(b.readFileSync(n,"utf-8"));for(const[s,m]of Object.entries(e))process.env[s]||(process.env[s]=m);Y()}catch{}const r=Le();fe()&&(console.log(`[${new Date().toISOString()}] remote access disabled, service start skipped`),clearInterval(r),process.exit(0));const c=!!(process.env.INVOCATION_ID||process.env.JOURNAL_STREAM||process.env.SHENNIAN_DESKTOP_SERVER_URL),N=async(e,s)=>{console.log(`[${new Date().toISOString()}] ${s} (PID ${e})`),process.kill(e,"SIGTERM"),await ne(e)||(process.kill(e,"SIGKILL"),await ne(e,2e3))};try{const e=mo();if(e&&e!==process.pid)try{process.kill(e,0),c?await N(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 A=ve(process.env,oe)?[]:ue(process.pid);if(A.length>0)if(c)for(const e of A)try{await N(e,"managed start taking over from orphan daemon")}catch{}else console.log(`[${new Date().toISOString()}] daemon already running (PID ${A[0]}), skipping duplicate start`),process.exit(0);const l=he();q(process.pid,l,{version:Z}),Q(process.pid,void 0,l),process.on("exit",()=>{j(process.pid,l),K(l)}),await $e()&&(console.log(`[${new Date().toISOString()}] Rolled back to previous version, restarting...`),process.exit(0));let i=a();const h=o.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??i.serverUrl??O.cn.url,u=X(),D=Ue(),f=D.map(e=>e.type),$=be(D),P=new _e,d=new Be,v=new qe,M=new Ge(d),p=new We(h),re=new no({actionClient:p}),ie=new to({actionClient:p}),L=new ze(h);let E=null;const H=await He({handlers:{...xe({store:P,authorizationClient:L,machineName:U.hostname(),machineOs:`${process.platform} ${U.release()}`,agentList:f,openPublicLoginUrl:ao}),...Fe({registry:d,observations:v,sessionResolver:p,getMachineCredentials:()=>{const e=a();return{machineId:e.machineId,machineToken:e.machineToken}}}),...Qe({store:v,registry:d,getMachineId:()=>a().machineId,onRegistrationIdle:e=>{E?.drain(e).catch(s=>{console.error("[room.activation] idle drain failed",s)})}}),...Je({authority:M,getMachineCredentials:()=>{const e=a();return{machineId:e.machineId,machineToken:e.machineToken}}}),...Ke({authority:M,registry:d,bindingClient:p,getMachineCredentials:()=>{const e=a();return{machineId:e.machineId,machineToken:e.machineToken}},mirrorFactory:e=>new ee(e),attachmentCache:re,attachmentUploader:ie,observations:v}),...so({registry:d,actionClient:p,getMachineCredentials:()=>{const e=a();return{machineId:e.machineId,machineToken:e.machineToken}}})}}),se=new io({registry:d,observations:v,actionClient:p,getMachineCredentials:()=>{const e=a();return{machineId:e.machineId,machineToken:e.machineToken}}}),x=new Ve(P,L,se,({machineId:e,machineToken:s})=>{J({...a(),machineId:e,machineToken:s,serverUrl:h})});let w=!1;const y=()=>{w||(w=!0,H.close().finally(()=>process.exit(0)))};if(process.once("SIGINT",y),process.once("SIGTERM",y),!i.machineToken){for(console.log(`[${new Date().toISOString()}] Waiting for browser device authorization...`);!w&&!i.machineToken;)await x.runOnce(),i=a(),i.machineToken||await new Promise(e=>setTimeout(e,1e3));if(w)return}process.removeListener("SIGINT",y),process.removeListener("SIGTERM",y);const _=setInterval(()=>{x.runOnce().catch(e=>{console.error("[room.authorization] pending action retry failed",e)})},1e3);_.unref?.();const z=`${lo(h)}/relay/machine`,V=i.machineToken;if(!V)return;i.machineId&&Ne(h,i.machineId),console.log(`[${new Date().toISOString()}] Connecting to ${z}... (v${u}) agents: ${f.join(",")}`),S({level:"info",wsEvent:"daemon.start",metadata:{version:u,agents:f}});let R=null,k=null;const B=le(i.autoUpgrade),g=new de({serverUrl:z,machineToken:V,cliVersion:u,agentList:f,autoUpgradePolicy:B,onConnected:()=>{console.log(`[${new Date().toISOString()}] \u2713 Connected`),S({level:"info",wsEvent:"daemon.connected"}),$.some(e=>e.models.length>0)&&g.sendEvent({type:"event",event:"machine.agents",payload:{agentList:f,agents:$}}),Oe(D,{serverUrl:i.serverUrl??h,authToken:i.machineToken??i.accessToken}).then(e=>{JSON.stringify(e)!==JSON.stringify($)&&g.sendEvent({type:"event",event:"machine.agents",payload:{agentList:f,agents:e}})}).catch(()=>{}),import("./upgrade/engine.js").then(({readUpgradeAttempt:e,clearUpgradeAttempt:s})=>{const m=e();s(),m&&(console.log(`[${new Date().toISOString()}] [upgrade] Reporting success: ${m.from} \u2192 ${m.to}`),g.sendEvent({type:"event",event:"upgrade",payload:{state:"success",machineId:"self",from:m.from,to:m.to}}))}).catch(()=>{}),C.start(),R?.handleConnected(),k?.start()},onDisconnected:e=>{k?.stop(),console.log(`[${new Date().toISOString()}] \u26A0 Disconnected, reconnecting... ${po(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}),T.handleReq(e)}});R=Pe()?new Me(g):null,k=new Ze({registry:d,actionClient:p,getMachineCredentials:()=>({machineToken:a().machineToken}),mirrorFactory:e=>new ee(e),onMessages:async(e,s,m)=>{await E?.processMessages(e,s,m)}});const C=new Ee({currentVersion:u,machineId:i.machineId??"unknown-machine",serverUrl:i.serverUrl??h,getIdleState:()=>T.getUpgradeIdleState(),startUpgrade:async e=>{const{handleUpgradeStart:s}=await import("./commands/upgrade.js");await s(g,"auto-upgrade",e,{currentVersion:u})}});C.setPolicy(B);const T=new Te(g,R,u,C,new je({registry:d,observations:v,bindingClient:p,getMachineCredentials:()=>{const e=a();return{machineId:e.machineId,machineToken:e.machineToken}},hostProcessIdentity:()=>`daemon:${l}`}));E=new eo({registry:d,observations:v,activate:e=>T.activateRoomSession(e),audit:new oo}),b.mkdirSync(oe,{recursive:!0}),q(process.pid,l,{version:u}),Q(process.pid,void 0,l),g.connect(),process.stdin.resume();let F=!1;const G=async()=>{if(!F){F=!0,console.log(I.gray(`
2
2
  Disconnecting...`)),S({level:"info",wsEvent:"daemon.stop"}),clearInterval(r),clearInterval(_);try{await T.cleanup(),C.stop(),R?.stop(),k?.stop(),g.disconnect(),await H.close(),j(process.pid,l),K(l)}finally{process.exit(0)}}};process.on("SIGINT",()=>{G()}),process.on("SIGTERM",()=>{G()})}),t.command("workbuddy-session-hook",{hidden:!0}).description("(internal) Receive a trusted WorkBuddy plugin lifecycle event").action(async()=>{await Xe()}),t.command("room-session-hook",{hidden:!0}).description("(internal) Authorize the official Room tool for one trusted Agent Session").argument("<runtime>","Trusted runtime adapter").action(async o=>{o!=="codex"&&o!=="claude_code"&&o!=="workbuddy"||await Ye(o)}),t.command("room-tool-server",{hidden:!0}).description("(internal) Run the official Shennian Room MCP tool").action(async()=>{await ro()});const te=t.command("config").description("View or modify configuration");te.command("show",{isDefault:!0}).description("Show current configuration").action(()=>{console.log(`Config file: ${ce()}`);const n={...a()};if(n.machineToken&&(n.machineToken=n.machineToken.slice(0,12)+"..."),n.serverUrl){const r=De(n.serverUrl);console.log(`Region: ${r} (${O[r].label})`)}console.log(JSON.stringify(n,null,2))}),te.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=a();if(o==="server"){n!=="cn"&&n!=="global"&&(console.error(I.red('\u2717 Value must be "cn" or "global"')),process.exit(1));const c=n;r.serverUrl=Ae(c),J(r),console.log(I.green(`\u2713 Server set to ${O[c].label}`)),console.log(I.yellow(" Restart the background service for this to take effect: shennian stop && shennian start"));return}console.error(I.red(`\u2717 Unknown config key: ${o}. Supported: server`)),process.exit(1)}),pe(t),Ie(t),Se(t),we(t),ye(t),Re(t),t.parse();
@@ -16,6 +16,7 @@ export declare class NativeSessionFusionService {
16
16
  canonicalMessageId?: string | null;
17
17
  sourceSessionKey?: string | null;
18
18
  text: string;
19
+ managedEchoPolicy?: 'suppress_following' | 'import_following';
19
20
  }): void;
20
21
  noteManagedSourceSession(sessionId: string, agentType: string, sourceSessionKey: string | null): void;
21
22
  private pruneState;
@@ -1,2 +1,2 @@
1
- import{randomUUID as y}from"node:crypto";import M from"node:fs";import{listClaudeTranscriptFiles as C,listCodexRolloutFiles as w,listOpenCodeSessionFiles as A,parseClaudeTranscriptChunk as T,parseCodexRolloutChunk as x,parseOpenCodeSessionFile as I}from"./parsers.js";import{loadNativeScannerState as _,saveNativeScannerState as N}from"./state.js";const h=6e4,m=h*2,P=30*6e4,S=20;function g(c){return c.replace(/\r\n/g,`
2
- `).trim()}function K(c){return c.split(/[\\/]+/).includes(".codex")}class F{client;timer=null;scanPromise=null;pendingClaims=new Map;suppressionWindows=new Map;constructor(e){this.client=e}start(){this.timer||(this.timer=setInterval(()=>{this.scanNow().catch(e=>{console.error("[native-fusion] periodic scan failed",e)})},h))}stop(){this.timer&&clearInterval(this.timer),this.timer=null}handleConnected(){this.start(),this.scanNow().catch(e=>{console.error("[native-fusion] initial scan failed",e)})}registerManagedSend(e){if(!e.canonicalMessageId)return;const s=e.sourceAgentType??e.agentType;s!=="codex"&&s!=="claude"&&s!=="opencode"||this.pendingClaims.set(e.sessionId,{sessionId:e.sessionId,agentType:e.agentType,sourceAgentType:s,canonicalMessageId:e.canonicalMessageId,text:g(e.text),createdAt:Date.now(),expiresAt:Date.now()+m,sourceSessionKey:e.sourceSessionKey??null})}noteManagedSourceSession(e,s,t){if(!t)return;const i=this.pendingClaims.get(e);i&&i.sourceAgentType===s&&(i.sourceSessionKey=t)}pruneState(){const e=Date.now();for(const[s,t]of this.pendingClaims.entries())t.expiresAt<=e&&this.pendingClaims.delete(s);for(const[s,t]of this.suppressionWindows.entries())t.endsAt<=e&&this.suppressionWindows.delete(s)}async scanNow(){if(this.client.getState()==="connected")return this.scanPromise?this.scanPromise:(this.scanPromise=this.runScan().finally(()=>{this.scanPromise=null}),this.scanPromise)}async runScan(){this.pruneState();const e=_(),s={...e.files},t=[],i=[...w(),...C(),...A()];for(const n of i){const o=M.statSync(n),r=e.files[n];if(!r){s[n]={offset:o.size,mtimeMs:o.mtimeMs};continue}const d=K(n),a=n.endsWith(".opencode-session.json"),l=a&&r?.mtimeMs!==o.mtimeMs?r.offset:r?.offset??0,f=a?I(n,l):d?x(n,l):T(n,l);s[n]={offset:f.nextOffset,mtimeMs:o.mtimeMs};for(const u of f.events){const p=this.tryClaimManagedEcho(u);if(p){t.push(p);continue}this.isSuppressed(u)||t.push(u)}}for(let n=0;n<t.length;n+=S){const o=t.slice(n,n+S);if(o.length===0)continue;const r=o.map(({repairHint:d,...a})=>a);await this.client.sendBufferedEvent({type:"event",event:"native.session.events",id:`native-fusion-${y()}`,payload:{events:r}},6e4)}e.files=s,N(e)}tryClaimManagedEcho(e){const s=Date.now();for(const[t,i]of this.pendingClaims.entries())if(i.sourceAgentType===e.agentType&&!(i.expiresAt<=s)&&e.role==="user"&&i.sourceSessionKey&&i.sourceSessionKey===e.sourceSessionKey&&!(Math.abs(e.ts-i.createdAt)>m)&&g(e.payload)===i.text)return this.pendingClaims.delete(t),this.suppressionWindows.set(`${e.agentType}:${e.sourceSessionKey}`,{sessionId:t,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,startedAt:e.ts,endsAt:Math.max(s,e.ts)+P}),{...e,sourceMode:"managed_echo_claim",claimCanonicalMessageId:i.canonicalMessageId};return null}isSuppressed(e){const s=`${e.agentType}:${e.sourceSessionKey}`,t=this.suppressionWindows.get(s);return!t||e.ts<t.startedAt?!1:e.ts>t.endsAt?(this.suppressionWindows.delete(s),!1):e.role==="user"?(this.suppressionWindows.delete(s),!1):!0}}export{F as NativeSessionFusionService};
1
+ import{randomUUID as S}from"node:crypto";import w from"node:fs";import{listClaudeTranscriptFiles as M,listCodexRolloutFiles as C,listOpenCodeSessionFiles as A,parseClaudeTranscriptChunk as T,parseCodexRolloutChunk as x,parseOpenCodeSessionFile as _}from"./parsers.js";import{loadNativeScannerState as I,saveNativeScannerState as P}from"./state.js";const h=6e4,m=h*2,E=30*6e4,g=20;function y(c){return c.replace(/\r\n/g,`
2
+ `).trim()}function N(c){return c.split(/[\\/]+/).includes(".codex")}class F{client;timer=null;scanPromise=null;pendingClaims=new Map;suppressionWindows=new Map;constructor(e){this.client=e}start(){this.timer||(this.timer=setInterval(()=>{this.scanNow().catch(e=>{console.error("[native-fusion] periodic scan failed",e)})},h))}stop(){this.timer&&clearInterval(this.timer),this.timer=null}handleConnected(){this.start(),this.scanNow().catch(e=>{console.error("[native-fusion] initial scan failed",e)})}registerManagedSend(e){if(!e.canonicalMessageId)return;const s=e.sourceAgentType??e.agentType;s!=="codex"&&s!=="claude"&&s!=="opencode"||this.pendingClaims.set(e.sessionId,{sessionId:e.sessionId,agentType:e.agentType,sourceAgentType:s,canonicalMessageId:e.canonicalMessageId,text:y(e.text),createdAt:Date.now(),expiresAt:Date.now()+m,sourceSessionKey:e.sourceSessionKey??null,managedEchoPolicy:e.managedEchoPolicy??"suppress_following"})}noteManagedSourceSession(e,s,n){if(!n)return;const t=this.pendingClaims.get(e);t&&t.sourceAgentType===s&&(t.sourceSessionKey=n)}pruneState(){const e=Date.now();for(const[s,n]of this.pendingClaims.entries())n.expiresAt<=e&&this.pendingClaims.delete(s);for(const[s,n]of this.suppressionWindows.entries())n.endsAt<=e&&this.suppressionWindows.delete(s)}async scanNow(){if(this.client.getState()==="connected")return this.scanPromise?this.scanPromise:(this.scanPromise=this.runScan().finally(()=>{this.scanPromise=null}),this.scanPromise)}async runScan(){this.pruneState();const e=I(),s={...e.files},n=[],t=[...C(),...M(),...A()];for(const i of t){const o=w.statSync(i),r=e.files[i];if(!r){s[i]={offset:o.size,mtimeMs:o.mtimeMs};continue}const d=N(i),a=i.endsWith(".opencode-session.json"),l=a&&r?.mtimeMs!==o.mtimeMs?r.offset:r?.offset??0,f=a?_(i,l):d?x(i,l):T(i,l);s[i]={offset:f.nextOffset,mtimeMs:o.mtimeMs};for(const u of f.events){const p=this.tryClaimManagedEcho(u);if(p){n.push(p);continue}this.isSuppressed(u)||n.push(u)}}for(let i=0;i<n.length;i+=g){const o=n.slice(i,i+g);if(o.length===0)continue;const r=o.map(({repairHint:d,...a})=>a);await this.client.sendBufferedEvent({type:"event",event:"native.session.events",id:`native-fusion-${S()}`,payload:{events:r}},6e4)}e.files=s,P(e)}tryClaimManagedEcho(e){const s=Date.now();for(const[n,t]of this.pendingClaims.entries())if(t.sourceAgentType===e.agentType&&!(t.expiresAt<=s)&&e.role==="user"&&t.sourceSessionKey&&t.sourceSessionKey===e.sourceSessionKey&&!(Math.abs(e.ts-t.createdAt)>m)&&y(e.payload)===t.text)return this.pendingClaims.delete(n),t.managedEchoPolicy==="suppress_following"&&this.suppressionWindows.set(`${e.agentType}:${e.sourceSessionKey}`,{sessionId:n,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,startedAt:e.ts,endsAt:Math.max(s,e.ts)+E}),{...e,sourceMode:"managed_echo_claim",claimCanonicalMessageId:t.canonicalMessageId,managedEchoPolicy:t.managedEchoPolicy};return null}isSuppressed(e){const s=`${e.agentType}:${e.sourceSessionKey}`,n=this.suppressionWindows.get(s);return!n||e.ts<n.startedAt?!1:e.ts>n.endsAt?(this.suppressionWindows.delete(s),!1):e.role==="user"?(this.suppressionWindows.delete(s),!1):!0}}export{F as NativeSessionFusionService};
@@ -20,6 +20,7 @@ export type PendingManagedClaim = {
20
20
  createdAt: number;
21
21
  expiresAt: number;
22
22
  sourceSessionKey?: string | null;
23
+ managedEchoPolicy: 'suppress_following' | 'import_following';
23
24
  };
24
25
  export type ManagedSuppressionWindow = {
25
26
  sessionId: string;
@@ -1,5 +1,6 @@
1
1
  import type { AgentType } from '@shennian/wire';
2
2
  import type { HttpRoomBindingClient } from './room-binding-client.js';
3
+ import type { RoomHostSessionObservationStore } from './host-session-observations.js';
3
4
  import type { RoomSessionRegistry } from './session-registry.js';
4
5
  export type ManagedRoomBindInput = {
5
6
  roomId: string;
@@ -28,6 +29,7 @@ export declare class TrustedManagedRoomBinder implements ManagedRoomBinder {
28
29
  private readonly deps;
29
30
  constructor(deps: {
30
31
  registry: RoomSessionRegistry;
32
+ observations: RoomHostSessionObservationStore;
31
33
  bindingClient: Pick<HttpRoomBindingClient, 'create'>;
32
34
  getMachineCredentials: () => {
33
35
  machineId?: string;
@@ -1 +1 @@
1
- class a{deps;constructor(e){this.deps=e}async bind(e){const{machineId:t,machineToken:d}=this.deps.getMachineCredentials();if(!t||!d)throw new Error("Machine is not paired with Shennian.");const s=r(e.agentType),o=this.deps.registry.register({machineId:t,runtimeAdapter:s,sourceSessionKey:e.agentSessionId,nianSessionId:e.nianSessionId,hostProcessIdentity:this.deps.hostProcessIdentity(),capabilities:{canIdentifySession:!0,canBindAutomatically:!0,canOpenEmbeddedRoomPage:!1,canResumeIdleSession:!0,canActivateWhileHostOpen:!0}}),n=await this.deps.bindingClient.create({machineToken:d,bindingIntentId:e.assignmentIntentId,roomId:e.roomId,agentIdentityId:e.agentIdentityId,nianSessionId:e.nianSessionId,runtimeAdapter:s,sessionRegistryKey:o.registryKey,activationCapability:"managed",activationMode:"auto_follow"});return this.deps.registry.setBinding({registryKey:o.registryKey,bindingId:n.binding.id,roomId:n.binding.roomId,agentIdentityId:n.binding.agentIdentityId,agentMembershipId:n.binding.agentMembershipId,status:n.binding.status,activationMode:n.binding.activationMode}),n}}function r(i){if(i==="codex")return"codex";if(i==="claude")return"claude_code";if(i==="workbuddy"||i==="custom:workbuddy")return"workbuddy";throw new Error(`Agent ${i} does not support managed Room binding.`)}export{a as TrustedManagedRoomBinder};
1
+ import c from"node:crypto";class h{deps;constructor(e){this.deps=e}async bind(e){const{machineId:i,machineToken:o}=this.deps.getMachineCredentials();if(!i||!o)throw new Error("Machine is not paired with Shennian.");const s=g(e.agentType),d=this.deps.hostProcessIdentity(),r=this.deps.registry.register({machineId:i,runtimeAdapter:s,sourceSessionKey:e.agentSessionId,nianSessionId:e.nianSessionId,hostProcessIdentity:d,capabilities:{canIdentifySession:!0,canBindAutomatically:!0,canOpenEmbeddedRoomPage:!1,canResumeIdleSession:!0,canActivateWhileHostOpen:!0}});this.deps.observations.observe({machineId:i,runtimeAdapter:s,sourceSessionKey:e.agentSessionId,hostContextHash:a(d),workspaceHash:a(e.workDir),workspacePath:e.workDir,lifecycleState:"active",eventName:"ManagedBind"});const t=await this.deps.bindingClient.create({machineToken:o,bindingIntentId:e.assignmentIntentId,roomId:e.roomId,agentIdentityId:e.agentIdentityId,nianSessionId:e.nianSessionId,runtimeAdapter:s,sessionRegistryKey:r.registryKey,activationCapability:"managed",activationMode:"auto_follow"});return this.deps.registry.setBinding({registryKey:r.registryKey,bindingId:t.binding.id,roomId:t.binding.roomId,agentIdentityId:t.binding.agentIdentityId,agentMembershipId:t.binding.agentMembershipId,status:t.binding.status,activationMode:t.binding.activationMode}),t}}function a(n){return c.createHash("sha256").update(n,"utf8").digest("hex")}function g(n){if(n==="codex")return"codex";if(n==="claude")return"claude_code";if(n==="workbuddy"||n==="custom:workbuddy")return"workbuddy";throw new Error(`Agent ${n} does not support managed Room binding.`)}export{h as TrustedManagedRoomBinder};
@@ -1,2 +1,2 @@
1
- import N from"node:os";import{createAgent as H}from"../../agents/adapter.js";import{buildApprovalPendingPayload as U,buildUserMessagePayload as G}from"@shennian/wire";import{reportLog as w}from"../../log-reporter.js";import{lookupClaudeTranscriptCwd as J}from"../../native-fusion/parsers.js";import{appendMessage as A,recordSession as z}from"../store.js";import{mergeProjectedSessions as Q}from"../projection.js";import{buildManagedAgentEnv as K}from"../../agents/config-status.js";import{materializeRemoteChatAttachments as V}from"../remote-attachments.js";function Y(t){if(!Array.isArray(t))return;const e=t.map(a=>{if(!a||typeof a!="object")return null;const s=a,o=typeof s.path=="string"?s.path:"",u=typeof s.name=="string"?s.name:"",i=typeof s.mimeType=="string"?s.mimeType:"";if(!o||!u||!i)return null;const d=typeof s.previewData=="string"&&s.previewData.trim()?s.previewData.trim():void 0;return{path:o,name:u,mimeType:i,kind:i.startsWith("image/")?"image":"file",...d?{previewData:d}:{}}}).filter(a=>a!=null);return e.length?e:void 0}function X(t){const e=t.indexOf(`
2
- `),a=e>0?Math.min(e,80):Math.min(t.length,80);return t.slice(0,a)}function Z(t,e,a={}){if(t.state==="tool-call"||t.state==="tool-result"){const s={runId:t.runId,sourceSeq:t.seq};return{state:t.state,runId:t.runId,seq:t.seq,sessionId:e,detailRef:s,...t.name?{name:t.name}:{},...t.source?{source:t.source}:{},...t.agentSessionId?{agentSessionId:t.agentSessionId}:{},...a}}return{...t,sessionId:e,...a}}const ee=3e4;function te(t){return t.state==="heartbeat"?t.runPhase??null:t.state==="tool-call"||t.state==="tool-result"?"tool_running":t.state==="approval-pending"?"waiting_approval":t.state==="delta"?t.thinking?"thinking":"streaming_text":t.state==="init"||t.state==="start"?"thinking":null}function ne(t,e){return`Agent send failed: ${e instanceof Error?e.message:String(e)}`}function B(t,e){return t!=="manager"?t:e==="claude"?"claude":"codex"}function _(t,e,a){t.client.sendEvent({type:"event",event:"session.message",payload:{sessionId:e.sessionId,message:e,session:{id:e.sessionId,agentType:a.agentType,agentSessionId:a.agentSessionId??null,modelId:a.modelId??null,workDir:a.workDir,status:"active",externalChannel:null}}})}function ae(t,e){z({sessionId:e.sessionId,agentType:e.agentType,workDir:e.workDir,agentSessionId:e.agentSessionId??null,modelId:e.modelId??null}),t.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:e.sessionId,agentType:e.agentType,agentSessionId:e.agentSessionId??null,modelId:e.modelId??null,workDir:e.workDir,status:"active",externalChannel:null}}})}function we(t){if(!t||typeof t!="object")return null;const e=t;return{configured:e.configured===void 0?void 0:!!e.configured,connected:!!e.connected,type:typeof e.type=="string"?e.type:null,channelId:typeof e.channelId=="string"?e.channelId:null,name:typeof e.name=="string"?e.name:null,canReply:e.canReply===void 0||e.canReply===null?null:!!e.canReply,systemPrompt:typeof e.systemPrompt=="string"?e.systemPrompt:null,wechatRpaSource:typeof e.wechatRpaSource=="string"?e.wechatRpaSource:null,wechatRpaGroups:Array.isArray(e.wechatRpaGroups)?e.wechatRpaGroups.map(a=>({name:String(a?.name||"").trim()})).filter(a=>a.name):null,pollIntervalMs:Number.isFinite(e.pollIntervalMs)?Number(e.pollIntervalMs):null,recentLimit:Number.isFinite(e.recentLimit)?Number(e.recentLimit):null,idleSeconds:Number.isFinite(e.idleSeconds)?Number(e.idleSeconds):null,forceForeground:e.forceForeground===void 0||e.forceForeground===null?null:!!e.forceForeground,noRestore:e.noRestore===void 0||e.noRestore===null?null:!!e.noRestore,downloadAttachments:e.downloadAttachments===void 0||e.downloadAttachments===null?null:!!e.downloadAttachments,downloadAttachmentsDir:typeof e.downloadAttachmentsDir=="string"?e.downloadAttachmentsDir:null}}function Ae(t){return!!(t?.configured??t?.connected)}function L(t){return K(t)}function W(t,e,a){return{}}function re(t,e,a,s,o){t.configure?.({sessionId:e,externalChannel:s??null,env:{...L(a),...W(e,s,o)}})}function Te(t,e,a){return null}function se(t,e,a){if(t!=="claude"||!a)return e;const s=e.trim();return!!s&&s.startsWith("-")&&!s.includes("/")?J(a)??e:e}function oe(t,e,a,s){let o=null;function u(n,r={}){t.client.sendAgentEvent({type:"event",event:"agent",payload:Z(n,e,r),seq:n.seq,id:`agent-evt-${n.runId}-${n.seq}`})}function i(n){n?.heartbeatTimer&&(clearInterval(n.heartbeatTimer),n.heartbeatTimer=null)}function d(n){if(!n.currentRunId||!n.currentRunPhase)return;const r=n.heartbeatSeq++;t.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:e,runId:n.currentRunId,seq:r,runPhase:n.currentRunPhase},seq:r,id:`agent-heartbeat-${n.currentRunId}-${r}-${Date.now()}`})}function h(n){!n||n.heartbeatTimer||(n.heartbeatTimer=setInterval(()=>{d(n)},ee),n.heartbeatTimer.unref?.())}function y(n){const r=n?.pendingTextEvent;!n||!r||!r.text||(u({state:"delta",runId:r.runId,seq:r.seq,text:r.text,thinking:r.thinking||void 0}),n.pendingTextEvent=null)}s.on("agentEvent",n=>{const r=t.sessions.get(e),T=te(n),E=n.state==="final"||n.state==="error"||n.state==="aborted";r&&(r.nextEventSeq=n.seq+1,E||(r.currentRunId=n.runId),!E&&T&&(r.currentRunPhase=T,h(r)),n.agentSessionId&&(r.agentSessionId=n.agentSessionId)),t.managerRuntime?.noteAgentEvent(e,n),n.state!=="delta"&&w({level:"info",sessionId:e,wsEvent:`agent.${n.state}`,wsDirection:"out",metadata:{runId:n.runId,seq:n.seq,agentType:a}}),n.state==="delta"&&n.text&&!n.thinking?A(e,{id:`agent-${n.runId}-${n.seq}`,sessionId:e,role:"agent",ts:Date.now(),payload:n.text}):n.state==="tool-call"||n.state==="tool-result"?A(e,{id:`agent-${n.runId}-${n.seq}`,sessionId:e,role:"agent",ts:Date.now(),payload:JSON.stringify({v:1,type:n.state==="tool-call"?"tool_use":"tool_result",name:n.name,status:n.state==="tool-call"?"running":"completed",detailRef:{runId:n.runId,sourceSeq:n.seq},args:n.args,result:n.result})}):n.state==="approval-pending"?A(e,{id:`agent-${n.runId||"run"}-${n.seq}`,sessionId:e,role:"agent",ts:Date.now(),payload:U(n.approval)}):(n.state==="error"||n.state==="aborted")&&n.message&&A(e,{id:`agent-${n.runId||"run"}-${n.seq}`,sessionId:e,role:"agent",ts:Date.now(),payload:n.message});const m=`${e}:${n.runId}`;if(n.state==="delta"&&!n.thinking&&n.text&&t.runTextAcc.set(m,(t.runTextAcc.get(m)??"")+n.text),n.agentSessionId&&n.agentSessionId!==o&&(o=n.agentSessionId,t.nativeFusion?.noteManagedSourceSession(e,B(a,n.source),n.agentSessionId),r&&z({sessionId:e,agentType:a,workDir:r.workDir,agentSessionId:n.agentSessionId}),t.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:e,agentType:a,agentSessionId:n.agentSessionId}}})),n.state==="delta"){const f=n.text??"";if(!f)return;const R=!!n.thinking;r?.pendingTextEvent&&(r.pendingTextEvent.runId!==n.runId||r.pendingTextEvent.thinking!==R)&&y(r),r&&!r.pendingTextEvent&&(r.pendingTextEvent={runId:n.runId,seq:n.seq,text:"",thinking:R}),r?.pendingTextEvent&&(r.pendingTextEvent.text+=f,r.pendingTextEvent.seq=n.seq);return}let x={};if(n.state==="final"){y(r);const f=t.runTextAcc.get(m)??"";f&&(x={messageSummary:X(f)}),t.runTextAcc.delete(m)}else n.state==="error"||n.state==="aborted"?(y(r),t.runTextAcc.delete(m)):(n.state==="tool-call"||n.state==="tool-result"||n.state==="approval-pending")&&y(r);E&&r?.currentRunId===n.runId&&(r.currentRunId=null,r.currentRunPhase=null,r.nextEventSeq=0,i(r),t.chatQueue?.noteTerminal(e)),u(n,x)}),s.on("error",n=>{console.error(`[chat.send] adapter error sessionId=${e} agentType=${a}: ${n.message}`),t.sessions.delete(e),t.chatQueue?.noteTerminal(e),t.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:e,message:n.message,runId:"",seq:0}})})}function le(t,e){if(t.processedReqIds.add(e),t.processedReqIds.size>1e3){const a=t.processedReqIds.values().next().value;t.processedReqIds.delete(a)}}async function q(t){t.heartbeatTimer&&(clearInterval(t.heartbeatTimer),t.heartbeatTimer=null),t.adapter.removeAllListeners(),await t.adapter.stop().catch(()=>{})}function v(t,e,a,s,o,u){return u?.trim()?t.send(e,a,s,o,u):o?.length?t.send(e,a,s,o):s?t.send(e,a,s):t.send(e,a)}async function ie(t,e,a,s,o,u,i){t.evictIdleSessions();const d=H(a);if(!d)throw new Error(`Unsupported agent: ${a}`);re(d,e,a,u,i),await d.start(e,s,o);const h={adapter:d,workDir:s,agentType:a,agentSessionId:o??null,lastActiveAt:Date.now(),currentRunId:null,currentRunPhase:null,nextEventSeq:0,heartbeatSeq:0,heartbeatTimer:null,pendingTextEvent:null,externalChannel:u??null,externalReplyTarget:i??null,externalChannelEnv:{...L(a),...W(e,u,i)}};return t.sessions.set(e,h),oe(t,e,a,d),h}function de(t,e){const a=t.sessions.get(e),s=a?.currentRunId;if(!a||!s)return;const o=a.nextEventSeq;t.runTextAcc.delete(`${e}:${s}`),a.pendingTextEvent=null,a.currentRunId=null,a.currentRunPhase=null,a.nextEventSeq=0,a.heartbeatTimer&&(clearInterval(a.heartbeatTimer),a.heartbeatTimer=null),t.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"aborted",sessionId:e,runId:s,seq:o},seq:o,id:`agent-evt-${s}-${o}`}),t.chatQueue?.noteTerminal(e)}async function Ee(t,e){if(t.processedReqIds.has(e.id)){t.client.sendRes({type:"res",id:e.id,ok:!0});return}le(t,e.id);const{sessionId:a,text:s,agentType:o,workDir:u,agentSessionId:i,modelId:d,systemPrompt:h,managerDefaultWorkerAgentType:y,managerDefaultWorkerModelId:n,reasoningEffort:r,clientMessageId:T,sessionListProjection:E,waitForDispatch:m,responseId:x}=e.params,f=x||e.id;Q(E);const R=null,O="";if(!a||!s){t.processedReqIds.delete(e.id),t.client.sendRes({type:"res",id:f,ok:!1,error:"sessionId and text are required"});return}const g=o;g==="manager"&&t.managerRuntime?.setManagerWorkerDefaults(a,y??null,n??null);const k=(g==="claude"||g==="codex"||g==="workbuddy")&&typeof r=="string"&&r.trim()?r.trim():void 0,I=t.resolvePath(se(g,u||N.homedir(),i)||N.homedir()),$=I,D=Y(e.params.attachments),p=D?.length?await V({text:s,attachments:D,workDir:I}):{text:s,attachments:D,localized:!1};let l=t.sessions.get(a);if(l){if(l.lastActiveAt=Date.now(),l.agentType!==g||l.workDir!==I||JSON.stringify(l.externalChannel??null)!==JSON.stringify(R??null)){t.sessions.delete(a);try{await q(l)}catch{t.processedReqIds.delete(e.id)}l=void 0}else if(i&&l.agentSessionId!==i)try{await l.adapter.resume(i),l.agentSessionId=i}catch{t.sessions.delete(a);try{await q(l)}catch{t.processedReqIds.delete(e.id)}l=void 0}}if(!l)try{l=await ie(t,a,g,I,i,R,O)}catch(c){const S=c instanceof Error&&c.message.startsWith("Unsupported agent:")?c.message:`Failed to start ${o}: ${c instanceof Error?c.message:String(c)}`;console.error(`[chat.send] start failed reqId=${e.id} sessionId=${a} agentType=${o} workDir=${I} agentSessionId=${i??""}: ${S}`),t.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:a,message:S,runId:"",seq:0}}),t.processedReqIds.delete(e.id),t.client.sendRes({type:"res",id:f,ok:!1,error:S});return}const P={id:T??`user-${e.id}`,sessionId:a,role:"user",ts:Date.now(),payload:G(p.text,p.attachments)};w({level:"info",sessionId:a,wsEvent:"chat.send.start",metadata:{reqId:e.id,agentType:g,modelId:d,reasoningEffort:k}});const C=()=>{ae(t,{sessionId:a,agentType:g,workDir:$,agentSessionId:l.agentSessionId??i??null,modelId:d}),l.currentRunId||(l.nextEventSeq=0),t.nativeFusion?.registerManagedSend({sessionId:a,agentType:g,sourceAgentType:B(g,d),canonicalMessageId:T??null,sourceSessionKey:l.agentSessionId??i??null,text:p.text}),A(a,P),_(t,P,{agentType:g,workDir:$,agentSessionId:l.agentSessionId??i??null,modelId:d})},M=async(c,S)=>{const b=ne(g,c);console.error(`[chat.send] send failed reqId=${e.id} sessionId=${a} agentType=${o} workDir=${I} agentSessionId=${l.agentSessionId??i??""}: ${b}`),t.sessions.delete(a);try{await q(l)}catch{}if(t.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:a,message:b,runId:"",seq:0}}),!S){const F={id:`agent-error-${e.id}-${Date.now()}`,sessionId:a,role:"agent",ts:Date.now(),payload:b};A(a,F),_(t,F,{agentType:g,workDir:$,agentSessionId:l.agentSessionId??i??null,modelId:d})}S&&(t.processedReqIds.delete(e.id),t.client.sendRes({type:"res",id:f,ok:!1,error:b}))};if(m){try{const c=p.attachments;await v(l.adapter,p.text,d,k,c,h),w({level:"info",sessionId:a,wsEvent:"chat.send.done",metadata:{reqId:e.id}})}catch(c){await M(c,!0);return}C(),t.client.sendRes({type:"res",id:f,ok:!0,...p.localized?{payload:{localizedAttachments:!0}}:{}}),w({level:"info",sessionId:a,wsEvent:"chat.send.res",metadata:{reqId:e.id,ok:!0}});return}C(),t.client.sendRes({type:"res",id:f,ok:!0,...p.localized?{payload:{localizedAttachments:!0}}:{}}),w({level:"info",sessionId:a,wsEvent:"chat.send.res",metadata:{reqId:e.id,ok:!0}});const j=p.attachments;v(l.adapter,p.text,d,k,j,h).then(()=>{w({level:"info",sessionId:a,wsEvent:"chat.send.done",metadata:{reqId:e.id}})}).catch(c=>{M(c,!1)})}async function Re(t,e){const{sessionId:a}=e.params,s=t.sessions.get(a);if(s){try{await s.adapter.stop()}catch{}de(t,a),t.activityPublisher?.publish(a,null)}t.client.sendRes({type:"res",id:e.id,ok:!0})}export{Re as handleChatAbort,Ee as handleChatSend};
1
+ import F from"node:os";import{createAgent as H}from"../../agents/adapter.js";import{buildApprovalPendingPayload as U,buildUserMessagePayload as G}from"@shennian/wire";import{reportLog as S}from"../../log-reporter.js";import{lookupClaudeTranscriptCwd as J}from"../../native-fusion/parsers.js";import{appendMessage as A,recordSession as v}from"../store.js";import{mergeProjectedSessions as Q}from"../projection.js";import{buildManagedAgentEnv as K}from"../../agents/config-status.js";import{materializeRemoteChatAttachments as V}from"../remote-attachments.js";function Y(t){if(!Array.isArray(t))return;const e=t.map(a=>{if(!a||typeof a!="object")return null;const s=a,o=typeof s.path=="string"?s.path:"",u=typeof s.name=="string"?s.name:"",d=typeof s.mimeType=="string"?s.mimeType:"";if(!o||!u||!d)return null;const c=typeof s.previewData=="string"&&s.previewData.trim()?s.previewData.trim():void 0;return{path:o,name:u,mimeType:d,kind:d.startsWith("image/")?"image":"file",...c?{previewData:c}:{}}}).filter(a=>a!=null);return e.length?e:void 0}function X(t){const e=t.indexOf(`
2
+ `),a=e>0?Math.min(e,80):Math.min(t.length,80);return t.slice(0,a)}function Z(t,e,a={}){if(t.state==="tool-call"||t.state==="tool-result"){const s={runId:t.runId,sourceSeq:t.seq};return{state:t.state,runId:t.runId,seq:t.seq,sessionId:e,detailRef:s,...t.name?{name:t.name}:{},...t.source?{source:t.source}:{},...t.agentSessionId?{agentSessionId:t.agentSessionId}:{},...a}}return{...t,sessionId:e,...a}}const ee=3e4;function te(t){return t.state==="heartbeat"?t.runPhase??null:t.state==="tool-call"||t.state==="tool-result"?"tool_running":t.state==="approval-pending"?"waiting_approval":t.state==="delta"?t.thinking?"thinking":"streaming_text":t.state==="init"||t.state==="start"?"thinking":null}function ne(t,e){return`Agent send failed: ${e instanceof Error?e.message:String(e)}`}function z(t,e){return t!=="manager"?t:e==="claude"?"claude":"codex"}function N(t,e,a){t.client.sendEvent({type:"event",event:"session.message",payload:{sessionId:e.sessionId,message:e,session:{id:e.sessionId,agentType:a.agentType,agentSessionId:a.agentSessionId??null,modelId:a.modelId??null,workDir:a.workDir,status:"active",externalChannel:null}}})}function ae(t,e){v({sessionId:e.sessionId,agentType:e.agentType,workDir:e.workDir,agentSessionId:e.agentSessionId??null,modelId:e.modelId??null}),t.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:e.sessionId,agentType:e.agentType,agentSessionId:e.agentSessionId??null,modelId:e.modelId??null,workDir:e.workDir,status:"active",externalChannel:null}}})}function Se(t){if(!t||typeof t!="object")return null;const e=t;return{configured:e.configured===void 0?void 0:!!e.configured,connected:!!e.connected,type:typeof e.type=="string"?e.type:null,channelId:typeof e.channelId=="string"?e.channelId:null,name:typeof e.name=="string"?e.name:null,canReply:e.canReply===void 0||e.canReply===null?null:!!e.canReply,systemPrompt:typeof e.systemPrompt=="string"?e.systemPrompt:null,wechatRpaSource:typeof e.wechatRpaSource=="string"?e.wechatRpaSource:null,wechatRpaGroups:Array.isArray(e.wechatRpaGroups)?e.wechatRpaGroups.map(a=>({name:String(a?.name||"").trim()})).filter(a=>a.name):null,pollIntervalMs:Number.isFinite(e.pollIntervalMs)?Number(e.pollIntervalMs):null,recentLimit:Number.isFinite(e.recentLimit)?Number(e.recentLimit):null,idleSeconds:Number.isFinite(e.idleSeconds)?Number(e.idleSeconds):null,forceForeground:e.forceForeground===void 0||e.forceForeground===null?null:!!e.forceForeground,noRestore:e.noRestore===void 0||e.noRestore===null?null:!!e.noRestore,downloadAttachments:e.downloadAttachments===void 0||e.downloadAttachments===null?null:!!e.downloadAttachments,downloadAttachmentsDir:typeof e.downloadAttachmentsDir=="string"?e.downloadAttachmentsDir:null}}function Ae(t){return!!(t?.configured??t?.connected)}function B(t){return K(t)}function L(t,e,a){return{}}function re(t,e,a,s,o){t.configure?.({sessionId:e,externalChannel:s??null,env:{...B(a),...L(e,s,o)}})}function Ee(t,e,a){return null}function se(t,e,a){if(t!=="claude"||!a)return e;const s=e.trim();return!!s&&s.startsWith("-")&&!s.includes("/")?J(a)??e:e}function oe(t,e,a,s){let o=null;function u(n,r={}){t.client.sendAgentEvent({type:"event",event:"agent",payload:Z(n,e,r),seq:n.seq,id:`agent-evt-${n.runId}-${n.seq}`})}function d(n){n?.heartbeatTimer&&(clearInterval(n.heartbeatTimer),n.heartbeatTimer=null)}function c(n){if(!n.currentRunId||!n.currentRunPhase)return;const r=n.heartbeatSeq++;t.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:e,runId:n.currentRunId,seq:r,runPhase:n.currentRunPhase},seq:r,id:`agent-heartbeat-${n.currentRunId}-${r}-${Date.now()}`})}function m(n){!n||n.heartbeatTimer||(n.heartbeatTimer=setInterval(()=>{c(n)},ee),n.heartbeatTimer.unref?.())}function I(n){const r=n?.pendingTextEvent;!n||!r||!r.text||(u({state:"delta",runId:r.runId,seq:r.seq,text:r.text,thinking:r.thinking||void 0}),n.pendingTextEvent=null)}s.on("agentEvent",n=>{const r=t.sessions.get(e),E=te(n),T=n.state==="final"||n.state==="error"||n.state==="aborted";r&&(r.nextEventSeq=n.seq+1,T||(r.currentRunId=n.runId),!T&&E&&(r.currentRunPhase=E,m(r)),n.agentSessionId&&(r.agentSessionId=n.agentSessionId)),t.managerRuntime?.noteAgentEvent(e,n),n.state!=="delta"&&S({level:"info",sessionId:e,wsEvent:`agent.${n.state}`,wsDirection:"out",metadata:{runId:n.runId,seq:n.seq,agentType:a}}),n.state==="delta"&&n.text&&!n.thinking?A(e,{id:`agent-${n.runId}-${n.seq}`,sessionId:e,role:"agent",ts:Date.now(),payload:n.text}):n.state==="tool-call"||n.state==="tool-result"?A(e,{id:`agent-${n.runId}-${n.seq}`,sessionId:e,role:"agent",ts:Date.now(),payload:JSON.stringify({v:1,type:n.state==="tool-call"?"tool_use":"tool_result",name:n.name,status:n.state==="tool-call"?"running":"completed",detailRef:{runId:n.runId,sourceSeq:n.seq},args:n.args,result:n.result})}):n.state==="approval-pending"?A(e,{id:`agent-${n.runId||"run"}-${n.seq}`,sessionId:e,role:"agent",ts:Date.now(),payload:U(n.approval)}):(n.state==="error"||n.state==="aborted")&&n.message&&A(e,{id:`agent-${n.runId||"run"}-${n.seq}`,sessionId:e,role:"agent",ts:Date.now(),payload:n.message});const y=`${e}:${n.runId}`;if(n.state==="delta"&&!n.thinking&&n.text&&t.runTextAcc.set(y,(t.runTextAcc.get(y)??"")+n.text),n.agentSessionId&&n.agentSessionId!==o&&(o=n.agentSessionId,t.nativeFusion?.noteManagedSourceSession(e,z(a,n.source),n.agentSessionId),r&&v({sessionId:e,agentType:a,workDir:r.workDir,agentSessionId:n.agentSessionId}),t.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:e,agentType:a,agentSessionId:n.agentSessionId}}})),n.state==="delta"){const f=n.text??"";if(!f)return;const x=!!n.thinking;r?.pendingTextEvent&&(r.pendingTextEvent.runId!==n.runId||r.pendingTextEvent.thinking!==x)&&I(r),r&&!r.pendingTextEvent&&(r.pendingTextEvent={runId:n.runId,seq:n.seq,text:"",thinking:x}),r?.pendingTextEvent&&(r.pendingTextEvent.text+=f,r.pendingTextEvent.seq=n.seq);return}let R={};if(n.state==="final"){I(r);const f=t.runTextAcc.get(y)??"";f&&(R={messageSummary:X(f)}),t.runTextAcc.delete(y)}else n.state==="error"||n.state==="aborted"?(I(r),t.runTextAcc.delete(y)):(n.state==="tool-call"||n.state==="tool-result"||n.state==="approval-pending")&&I(r);T&&r?.currentRunId===n.runId&&(r.currentRunId=null,r.currentRunPhase=null,r.nextEventSeq=0,d(r),t.chatQueue?.noteTerminal(e)),u(n,R)}),s.on("error",n=>{console.error(`[chat.send] adapter error sessionId=${e} agentType=${a}: ${n.message}`),t.sessions.delete(e),t.chatQueue?.noteTerminal(e),t.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:e,message:n.message,runId:"",seq:0}})})}function le(t,e){if(t.processedReqIds.add(e),t.processedReqIds.size>1e3){const a=t.processedReqIds.values().next().value;t.processedReqIds.delete(a)}}async function D(t){t.heartbeatTimer&&(clearInterval(t.heartbeatTimer),t.heartbeatTimer=null),t.adapter.removeAllListeners(),await t.adapter.stop().catch(()=>{})}function W(t,e,a,s,o,u){return u?.trim()?t.send(e,a,s,o,u):o?.length?t.send(e,a,s,o):s?t.send(e,a,s):t.send(e,a)}async function de(t,e,a,s,o,u,d){t.evictIdleSessions();const c=H(a);if(!c)throw new Error(`Unsupported agent: ${a}`);re(c,e,a,u,d),await c.start(e,s,o);const m={adapter:c,workDir:s,agentType:a,agentSessionId:o??null,lastActiveAt:Date.now(),currentRunId:null,currentRunPhase:null,nextEventSeq:0,heartbeatSeq:0,heartbeatTimer:null,pendingTextEvent:null,externalChannel:u??null,externalReplyTarget:d??null,externalChannelEnv:{...B(a),...L(e,u,d)}};return t.sessions.set(e,m),oe(t,e,a,c),m}function ie(t,e){const a=t.sessions.get(e),s=a?.currentRunId;if(!a||!s)return;const o=a.nextEventSeq;t.runTextAcc.delete(`${e}:${s}`),a.pendingTextEvent=null,a.currentRunId=null,a.currentRunPhase=null,a.nextEventSeq=0,a.heartbeatTimer&&(clearInterval(a.heartbeatTimer),a.heartbeatTimer=null),t.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"aborted",sessionId:e,runId:s,seq:o},seq:o,id:`agent-evt-${s}-${o}`}),t.chatQueue?.noteTerminal(e)}async function Te(t,e){if(t.processedReqIds.has(e.id)){t.client.sendRes({type:"res",id:e.id,ok:!0});return}le(t,e.id);const{sessionId:a,text:s,agentType:o,workDir:u,agentSessionId:d,modelId:c,systemPrompt:m,managerDefaultWorkerAgentType:I,managerDefaultWorkerModelId:n,reasoningEffort:r,clientMessageId:E,sessionListProjection:T,waitForDispatch:y,responseId:R}=e.params,f=R||e.id;Q(T);const x=null,O="";if(!a||!s){t.processedReqIds.delete(e.id),t.client.sendRes({type:"res",id:f,ok:!1,error:"sessionId and text are required"});return}const g=o;g==="manager"&&t.managerRuntime?.setManagerWorkerDefaults(a,I??null,n??null);const k=(g==="claude"||g==="codex"||g==="workbuddy")&&typeof r=="string"&&r.trim()?r.trim():void 0,w=t.resolvePath(se(g,u||F.homedir(),d)||F.homedir()),$=w,q=Y(e.params.attachments),p=q?.length?await V({text:s,attachments:q,workDir:w}):{text:s,attachments:q,localized:!1};let l=t.sessions.get(a);if(l){if(l.lastActiveAt=Date.now(),l.agentType!==g||l.workDir!==w||JSON.stringify(l.externalChannel??null)!==JSON.stringify(x??null)){t.sessions.delete(a);try{await D(l)}catch{t.processedReqIds.delete(e.id)}l=void 0}else if(d&&l.agentSessionId!==d)try{await l.adapter.resume(d),l.agentSessionId=d}catch{t.sessions.delete(a);try{await D(l)}catch{t.processedReqIds.delete(e.id)}l=void 0}}if(!l)try{l=await de(t,a,g,w,d,x,O)}catch(i){const h=i instanceof Error&&i.message.startsWith("Unsupported agent:")?i.message:`Failed to start ${o}: ${i instanceof Error?i.message:String(i)}`;console.error(`[chat.send] start failed reqId=${e.id} sessionId=${a} agentType=${o} workDir=${w} agentSessionId=${d??""}: ${h}`),t.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:a,message:h,runId:"",seq:0}}),t.processedReqIds.delete(e.id),t.client.sendRes({type:"res",id:f,ok:!1,error:h});return}const P={id:E??`user-${e.id}`,sessionId:a,role:"user",ts:Date.now(),payload:G(p.text,p.attachments)};S({level:"info",sessionId:a,wsEvent:"chat.send.start",metadata:{reqId:e.id,agentType:g,modelId:c,reasoningEffort:k}});const M=i=>{ae(t,{sessionId:a,agentType:g,workDir:$,agentSessionId:l.agentSessionId??d??null,modelId:c}),l.currentRunId||(l.nextEventSeq=0),t.nativeFusion?.registerManagedSend({sessionId:a,agentType:g,sourceAgentType:z(g,c),canonicalMessageId:E??null,sourceSessionKey:l.agentSessionId??d??null,text:p.text,managedEchoPolicy:i?.deliveryMode==="external_owner_queue"?"import_following":"suppress_following"}),A(a,P),N(t,P,{agentType:g,workDir:$,agentSessionId:l.agentSessionId??d??null,modelId:c})},_=async(i,h)=>{const b=ne(g,i);console.error(`[chat.send] send failed reqId=${e.id} sessionId=${a} agentType=${o} workDir=${w} agentSessionId=${l.agentSessionId??d??""}: ${b}`),t.sessions.delete(a);try{await D(l)}catch{}if(t.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:a,message:b,runId:"",seq:0}}),!h){const C={id:`agent-error-${e.id}-${Date.now()}`,sessionId:a,role:"agent",ts:Date.now(),payload:b};A(a,C),N(t,C,{agentType:g,workDir:$,agentSessionId:l.agentSessionId??d??null,modelId:c})}h&&(t.processedReqIds.delete(e.id),t.client.sendRes({type:"res",id:f,ok:!1,error:b}))};if(y){let i;try{const h=p.attachments;i=await W(l.adapter,p.text,c,k,h,m),S({level:"info",sessionId:a,wsEvent:"chat.send.done",metadata:{reqId:e.id}})}catch(h){await _(h,!0);return}M(i??void 0),t.client.sendRes({type:"res",id:f,ok:!0,...p.localized||i?.deliveryMode==="external_owner_queue"?{payload:{...p.localized?{localizedAttachments:!0}:{},...i?.deliveryMode==="external_owner_queue"?{queued:!0,deliveryMode:i.deliveryMode,queuedSubmissionId:i.queuedSubmissionId}:{}}}:{}}),S({level:"info",sessionId:a,wsEvent:"chat.send.res",metadata:{reqId:e.id,ok:!0}});return}M(),t.client.sendRes({type:"res",id:f,ok:!0,...p.localized?{payload:{localizedAttachments:!0}}:{}}),S({level:"info",sessionId:a,wsEvent:"chat.send.res",metadata:{reqId:e.id,ok:!0}});const j=p.attachments;W(l.adapter,p.text,c,k,j,m).then(()=>{S({level:"info",sessionId:a,wsEvent:"chat.send.done",metadata:{reqId:e.id}})}).catch(i=>{_(i,!1)})}async function xe(t,e){const{sessionId:a}=e.params,s=t.sessions.get(a);if(s){try{await s.adapter.stop()}catch{}ie(t,a),t.activityPublisher?.publish(a,null)}t.client.sendRes({type:"res",id:e.id,ok:!0})}export{xe as handleChatAbort,Te as handleChatSend};
@@ -1,9 +1,11 @@
1
1
  import type { AgentType } from './session.ts';
2
+ import type { RoomActivationMode } from './room.ts';
2
3
  export type AgentWorkspaceRoomLink = {
3
4
  bindingId: string;
4
5
  status: string;
5
6
  roomId: string;
6
7
  roomName: string;
8
+ activationMode: RoomActivationMode;
7
9
  };
8
10
  export type AgentWorkspaceSession = {
9
11
  id: string;
@@ -1 +1 @@
1
- {"version":3,"file":"agent-workspace.d.ts","sourceRoot":"","sources":["../src/agent-workspace.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAE7C,MAAM,MAAM,sBAAsB,GAAG;IACnC,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG;IAClC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,SAAS,CAAA;IACpB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,OAAO,EAAE,MAAM,CAAA;IACf,cAAc,EAAE,MAAM,CAAA;IACtB,IAAI,EAAE,sBAAsB,GAAG,IAAI,CAAA;CACpC,CAAA;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,MAAM,EAAE;QACN,OAAO,EAAE,MAAM,CAAA;QACf,QAAQ,EAAE,MAAM,CAAA;QAChB,SAAS,EAAE,MAAM,CAAA;QACjB,SAAS,EAAE,MAAM,CAAA;KAClB,GAAG,IAAI,CAAA;IACR,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,cAAc,EAAE,SAAS,GAAG,IAAI,CAAA;IAChC,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,qBAAqB,EAAE,CAAA;CAClC,CAAA"}
1
+ {"version":3,"file":"agent-workspace.d.ts","sourceRoot":"","sources":["../src/agent-workspace.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAC7C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAA;AAEnD,MAAM,MAAM,sBAAsB,GAAG;IACnC,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,cAAc,EAAE,kBAAkB,CAAA;CACnC,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG;IAClC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,SAAS,CAAA;IACpB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,OAAO,EAAE,MAAM,CAAA;IACf,cAAc,EAAE,MAAM,CAAA;IACtB,IAAI,EAAE,sBAAsB,GAAG,IAAI,CAAA;CACpC,CAAA;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,MAAM,EAAE;QACN,OAAO,EAAE,MAAM,CAAA;QACf,QAAQ,EAAE,MAAM,CAAA;QAChB,SAAS,EAAE,MAAM,CAAA;QACjB,SAAS,EAAE,MAAM,CAAA;KAClB,GAAG,IAAI,CAAA;IACR,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,cAAc,EAAE,SAAS,GAAG,IAAI,CAAA;IAChC,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,qBAAqB,EAAE,CAAA;CAClC,CAAA"}
@@ -12,6 +12,7 @@ export type NativeSessionEventPayload = {
12
12
  modelId?: string | null;
13
13
  workDir?: string;
14
14
  claimCanonicalMessageId?: string | null;
15
+ managedEchoPolicy?: 'suppress_following' | 'import_following';
15
16
  terminal?: boolean;
16
17
  };
17
18
  export type NativeSessionImportBatch = {
@@ -1 +1 @@
1
- {"version":3,"file":"native-session.d.ts","sourceRoot":"","sources":["../src/native-session.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,uBAAuB,GAC/B,sBAAsB,GACtB,0BAA0B,GAC1B,yBAAyB,GACzB,oBAAoB,CAAA;AAExB,MAAM,MAAM,yBAAyB,GAAG;IACtC,SAAS,EAAE,OAAO,GAAG,QAAQ,GAAG,UAAU,CAAA;IAC1C,UAAU,EAAE,uBAAuB,CAAA;IACnC,gBAAgB,EAAE,MAAM,CAAA;IACxB,cAAc,EAAE,MAAM,CAAA;IACtB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;IACtB,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,uBAAuB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvC,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,wBAAwB,GAAG;IACrC,MAAM,EAAE,yBAAyB,EAAE,CAAA;CACpC,CAAA"}
1
+ {"version":3,"file":"native-session.d.ts","sourceRoot":"","sources":["../src/native-session.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,uBAAuB,GAC/B,sBAAsB,GACtB,0BAA0B,GAC1B,yBAAyB,GACzB,oBAAoB,CAAA;AAExB,MAAM,MAAM,yBAAyB,GAAG;IACtC,SAAS,EAAE,OAAO,GAAG,QAAQ,GAAG,UAAU,CAAA;IAC1C,UAAU,EAAE,uBAAuB,CAAA;IACnC,gBAAgB,EAAE,MAAM,CAAA;IACxB,cAAc,EAAE,MAAM,CAAA;IACtB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;IACtB,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,uBAAuB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvC,iBAAiB,CAAC,EAAE,oBAAoB,GAAG,kBAAkB,CAAA;IAC7D,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,wBAAwB,GAAG;IACrC,MAAM,EAAE,yBAAyB,EAAE,CAAA;CACpC,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shennian",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "description": "Shennian — AI Agent Control Plane CLI",
5
5
  "type": "module",
6
6
  "bin": {