shennian 0.3.67 → 0.3.69
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/publish-build-manifest.json +16 -16
- package/dist/src/agents/codex.d.ts +0 -1
- package/dist/src/agents/codex.js +8 -8
- package/dist/src/commands/manager.js +1 -1
- package/dist/src/native-fusion/codex-user-input.js +3 -3
- package/dist/src/native-fusion/service.d.ts +2 -0
- package/dist/src/native-fusion/service.js +2 -2
- package/dist/src/session/history-subscriptions.js +1 -1
- package/dist/src/session/index-publisher.js +1 -1
- package/dist/src/session/manager.d.ts +1 -0
- package/dist/src/session/manager.js +1 -1
- package/dist/src/session/store.d.ts +4 -0
- package/dist/src/session/store.js +2 -2
- package/node_modules/@shennian/wire/dist/session.d.ts +2 -2
- package/node_modules/@shennian/wire/dist/session.js +3 -1
- package/node_modules/@shennian/wire/dist/voice-chief.d.ts +1 -0
- package/package.json +1 -1
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
33
|
"file": "src/agents/codex.js",
|
|
34
|
-
"beforeBytes":
|
|
35
|
-
"afterBytes":
|
|
34
|
+
"beforeBytes": 46603,
|
|
35
|
+
"afterBytes": 23409
|
|
36
36
|
},
|
|
37
37
|
{
|
|
38
38
|
"file": "src/agents/command-spec.js",
|
|
@@ -166,8 +166,8 @@
|
|
|
166
166
|
},
|
|
167
167
|
{
|
|
168
168
|
"file": "src/commands/manager.js",
|
|
169
|
-
"beforeBytes":
|
|
170
|
-
"afterBytes":
|
|
169
|
+
"beforeBytes": 6845,
|
|
170
|
+
"afterBytes": 4471
|
|
171
171
|
},
|
|
172
172
|
{
|
|
173
173
|
"file": "src/commands/pair-authorization.js",
|
|
@@ -316,8 +316,8 @@
|
|
|
316
316
|
},
|
|
317
317
|
{
|
|
318
318
|
"file": "src/native-fusion/codex-user-input.js",
|
|
319
|
-
"beforeBytes":
|
|
320
|
-
"afterBytes":
|
|
319
|
+
"beforeBytes": 4583,
|
|
320
|
+
"afterBytes": 2164
|
|
321
321
|
},
|
|
322
322
|
{
|
|
323
323
|
"file": "src/native-fusion/config.js",
|
|
@@ -356,8 +356,8 @@
|
|
|
356
356
|
},
|
|
357
357
|
{
|
|
358
358
|
"file": "src/native-fusion/service.js",
|
|
359
|
-
"beforeBytes":
|
|
360
|
-
"afterBytes":
|
|
359
|
+
"beforeBytes": 30583,
|
|
360
|
+
"afterBytes": 14140
|
|
361
361
|
},
|
|
362
362
|
{
|
|
363
363
|
"file": "src/native-fusion/session-header.js",
|
|
@@ -591,18 +591,18 @@
|
|
|
591
591
|
},
|
|
592
592
|
{
|
|
593
593
|
"file": "src/session/history-subscriptions.js",
|
|
594
|
-
"beforeBytes":
|
|
595
|
-
"afterBytes":
|
|
594
|
+
"beforeBytes": 8974,
|
|
595
|
+
"afterBytes": 4411
|
|
596
596
|
},
|
|
597
597
|
{
|
|
598
598
|
"file": "src/session/index-publisher.js",
|
|
599
|
-
"beforeBytes":
|
|
600
|
-
"afterBytes":
|
|
599
|
+
"beforeBytes": 8089,
|
|
600
|
+
"afterBytes": 3965
|
|
601
601
|
},
|
|
602
602
|
{
|
|
603
603
|
"file": "src/session/manager.js",
|
|
604
|
-
"beforeBytes":
|
|
605
|
-
"afterBytes":
|
|
604
|
+
"beforeBytes": 28817,
|
|
605
|
+
"afterBytes": 14714
|
|
606
606
|
},
|
|
607
607
|
{
|
|
608
608
|
"file": "src/session/native-cleanup-outbox.js",
|
|
@@ -626,8 +626,8 @@
|
|
|
626
626
|
},
|
|
627
627
|
{
|
|
628
628
|
"file": "src/session/store.js",
|
|
629
|
-
"beforeBytes":
|
|
630
|
-
"afterBytes":
|
|
629
|
+
"beforeBytes": 28486,
|
|
630
|
+
"afterBytes": 14629
|
|
631
631
|
},
|
|
632
632
|
{
|
|
633
633
|
"file": "src/session/tool-detail-store.js",
|
package/dist/src/agents/codex.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import{spawnSync as _}from"node:child_process";import{createInterface as S}from"node:readline";import{createHash as A,randomUUID as R}from"node:crypto";import p from"node:path";import{AgentAdapter as P,registerAgent as q}from"./adapter.js";import{resolveBuiltinCommand as
|
|
2
|
-
`)}function
|
|
3
|
-
`)}function
|
|
4
|
-
${u}`.trim();if(c!==0){m(new Error(f||`codex queue exited with code ${c??"unknown"}`));return}const E=f.match(/Queued message\s+([^\s]+)\s+for thread/i)?.[1];m(void 0,E)})})}async resume(e){await this.killProcess(),this.agentSessionId=e,this.
|
|
5
|
-
`,u=>{u&&(clearTimeout(
|
|
1
|
+
import{spawnSync as _}from"node:child_process";import{createInterface as S}from"node:readline";import{createHash as A,randomUUID as R}from"node:crypto";import p from"node:path";import{AgentAdapter as P,registerAgent as q}from"./adapter.js";import{resolveBuiltinCommand as x,spawnAgentCommand as v}from"./command-spec.js";import{buildAgentProcessEnv as I}from"../agent-env.js";import{ensurePlatformInstructionsFile as O}from"./platform-instructions.js";import{CODEX_APP_SERVER_CLIENT_INFO as b,extractAppServerErrorMessage as $,formatCodexErrorMessage as L,formatTransientCodexStatus as D,isCodexCollabAgentItem as F,isCodexCollabAgentToolName as g,isCodexLegacyCollabAgentItem as M,isCodexUnsupportedEffortError as U,isMissingCodexRolloutError as N,isTransientCodexErrorMessage as W,looksLikeCodexInteractiveAuthPrompt as z,looksLikeFatalCodexStderr as B,normalizeCodexModelId as w,normalizeCodexReasoningEffort as G,normalizeCodexStderr as T,normalizeTerminalText as H,safeStringify as k,stripGitDirectiveArtifacts as J}from"./codex-utils.js";import{isCodexUnsupportedEffortError as ge,isMissingCodexRolloutError as xe,normalizeCodexModelId as ve,normalizeCodexReasoningEffort as Ie}from"./codex-utils.js";function V(o){const e=o instanceof Error?o.message:String(o);return/already has an active writer|thread-store conflict/i.test(e)}function K(o){const e=o instanceof Error?o.message:String(o);return/paginated(?:_threads|\s+(?:thread\s+)?history)?.*(?:not supported|unsupported|unavailable)/i.test(e)||/(?:not supported|unsupported|unavailable).*paginated(?:_threads|\s+(?:thread\s+)?history)?/i.test(e)}function Q(o){const e=o instanceof Error?o.message:String(o);return/unknown field [`'"]?excludeTurns/i.test(e)||/excludeTurns.*(?:not supported|unsupported|unavailable)/i.test(e)}function X(o){const e=o instanceof Error?o.message:String(o);return/(?:method not found|unknown method).*(?:project\/list|project\/create)/i.test(e)||/(?:project\/list|project\/create).*(?:not supported|unsupported|unavailable)/i.test(e)}function Y(o,e){const t=p.resolve(e);let s;for(const r of o)if(r.id)for(const i of r.roots??[]){if(!i.path)continue;const n=p.resolve(i.path),a=p.relative(n,t);!(a===""||!a.startsWith("..")&&!p.isAbsolute(a))||n.length<=(s?.rootLength??-1)||(s={id:r.id,rootLength:n.length})}return s?.id}function Z(o,e){const t=e?.flatMap(r=>j(r)).filter(r=>!/^https?:\/\//i.test(r.path)).filter(r=>r.path);return{type:"text",text:te(o,e),text_elements:[],...t?.length?{local_images:t}:{}}}function j(o){if(o.kind==="image")return o.previewData?[{path:o.path,data:o.previewData,mimeType:o.mimeType}]:[{path:o.path}];if(o.mimeType.startsWith("video/")){const e=ee(o.path);return e?[{path:e}]:[]}return[]}function ee(o){if(!o||/^https?:\/\//i.test(o))return null;const e=p.extname(o),s=`${e?o.slice(0,-e.length):o}.preview.png`;return _("ffmpeg",["-y","-ss","0","-i",o,"-frames:v","1","-vf","scale=320:-1",s],{stdio:"ignore",timeout:15e3}).status===0?s:null}function te(o,e){const t=e?.filter(i=>i.kind!=="image").filter(i=>i.path&&!/^https?:\/\//i.test(i.path));if(!t?.length||t.every(i=>o.includes(i.path)))return o;const r=t.map((i,n)=>{const a=i.name||`attachment-${n+1}`,l=i.mimeType?` (${i.mimeType})`:"";return`[${C(a)}${l}](<${i.path}>)`});return[o,"",...r].join(`
|
|
2
|
+
`)}function se(o,e){const t=e?.filter(r=>r.path&&!/^https?:\/\//i.test(r.path)&&!o.includes(r.path));if(!t?.length)return o;const s=t.map((r,i)=>{const n=C(r.name||`attachment-${i+1}`);return r.kind==="image"?``:`[${n}](<${r.path}>)`});return[o,"",...s].join(`
|
|
3
|
+
`)}function re(o,e,t){return["queue","--thread",o,"--message",se(e,t)]}function C(o){return o.replace(/[[\]\\]/g,"\\$&")}class ne extends P{options;type="codex";process=null;workDir=null;seq=0;runId="";pendingUsage;agentSessionId=null;hasEmittedText=!1;textTail="";terminalState="open";forceCloseTimer=null;stderrBuf="";rpcSeq=1;pendingRequests=new Map;appServerReady=null;appServerStartupError=null;activeTurnId=null;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=w(t),a=G(s),l=i?.trim()??"";l!==this.agentSystemPrompt&&(this.agentSystemPrompt=l,this.process&&await this.killProcess()),this.runId=R(),this.seq=0,this.resetRunState();try{await this.ensureAppServer(n)}catch(h){if(!this.agentSessionId||!V(h))throw h;return{deliveryMode:"external_owner_queue",queuedSubmissionId:await this.enqueueForExternalOwner(this.agentSessionId,e,r)}}const d=this.agentSessionId;if(!d)throw new Error("codex app-server did not create a thread");this.emitEvent({state:"start",agentSessionId:d});const u=await this.startTurnWithRecovery(d,e,n,a,r);return this.activeTurnId=u.turn?.id??null,{deliveryMode:"direct"}}async enqueueForExternalOwner(e,t,s){const r=x("codex");if(!r)throw new Error('Command "codex" not found. Is OpenAI Codex CLI installed?');const i=re(e,t,s),n=v(r,i,{cwd:this.workDir??void 0,stdio:["ignore","pipe","pipe"],env:I({NO_COLOR:"1",...this.extraEnv})});return await new Promise((a,l)=>{let d="",u="",h=!1;const m=(c,f)=>{h||(h=!0,clearTimeout(y),c?l(c):a(f))},y=setTimeout(()=>{n.kill("SIGTERM"),m(new Error("Codex Desktop message queue timed out"))},6e4);y.unref?.(),n.stdout?.on("data",c=>{d+=c.toString()}),n.stderr?.on("data",c=>{u+=c.toString()}),n.once("error",c=>m(c)),n.once("close",c=>{const f=`${d}
|
|
4
|
+
${u}`.trim();if(c!==0){m(new Error(f||`codex queue exited with code ${c??"unknown"}`));return}const E=f.match(/Queued message\s+([^\s]+)\s+for thread/i)?.[1];m(void 0,E)})})}async resume(e){await this.killProcess(),this.agentSessionId=e,this.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}))}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=x("codex");if(!t){this.emit("error",new Error('Command "codex" not found. Is OpenAI Codex CLI installed?'));return}const s=v(t,e,{cwd:this.workDir??void 0,stdio:["ignore","pipe","pipe"],env:I(this.extraEnv)});this.process=s,S({input:s.stdout}).on("line",n=>{if(n.trim())try{const a=JSON.parse(n);this.handleStreamEvent(a)}catch{this.emitEvent({state:"delta",text:n})}});let i="";s.stderr?.on("data",n=>{i+=n.toString()}),s.on("close",n=>{this.process===s&&(this.process=null,this.clearForceCloseTimer(),this.handleProcessClose(n,i))}),s.on("error",n=>{this.process===s&&(this.process=null,n.code==="ENOENT"?this.emit("error",new Error('Command "codex" not found. Is OpenAI Codex CLI installed?')):this.emit("error",n))})}spawnAppServer(){const e=x("codex");if(!e){this.emit("error",new Error('Command "codex" not found. Is OpenAI Codex CLI installed?'));return}const t=["app-server","--listen","stdio://"],s=this.options.modelInstructionsFile??O(this.workDir??process.cwd(),this.externalChannel,this.shennianSessionId??void 0,this.agentSystemPrompt);s&&t.push("-c",`model_instructions_file=${JSON.stringify(s)}`);const r=v(e,t,{cwd:this.workDir??void 0,stdio:["pipe","pipe","pipe"],env:I({NO_COLOR:"1",...this.extraEnv})});this.process=r,this.stderrBuf="",this.appServerStartupError=null,S({input:r.stdout}).on("line",n=>{if(!n.trim())return;let a;try{a=JSON.parse(n)}catch{this.handleAppServerNonJsonStdout(n);return}this.handleAppServerMessage(a)}),r.stderr?.on("data",n=>{this.stderrBuf+=n.toString()}),r.on("close",n=>{if(this.process!==r)return;const a=this.activeTurnId!=null;this.process=null,this.appServerReady=null,this.activeTurnId=null,this.rejectAllPending(new Error(T(this.stderrBuf)||`codex app-server exited with code ${n}`)),this.terminalState==="open"&&a&&this.handleProcessClose(n,this.stderrBuf)}),r.on("error",n=>{this.process===r&&(this.process=null,this.rejectAllPending(n),n.code==="ENOENT"?this.emit("error",new Error('Command "codex" not found. Is OpenAI Codex CLI installed?')):this.emit("error",n))})}async ensureAppServer(e){return this.appServerReady?this.appServerReady:(this.spawnAppServer(),this.appServerReady=this.initializeAppServer(e).catch(async t=>{throw this.appServerReady=null,await this.killProcess(),t}),this.appServerReady)}async initializeAppServer(e){const t=w(e);if(await this.sendRpc("initialize",{clientInfo:b,capabilities:{experimentalApi:!0}}),this.agentSessionId){const a={threadId:this.agentSessionId,cwd:this.workDir,approvalPolicy:"never",sandbox:"danger-full-access",persistExtendedHistory:!0,...this.options.hidden?{threadSource:"subagent"}:{},...t?{model:t}:{}};let l;try{l=await this.sendRpc("thread/resume",{...a,excludeTurns:!0})}catch(d){if(!Q(d))throw d;l=await this.sendRpc("thread/resume",a)}this.agentSessionId=l.thread?.id??this.agentSessionId;return}const s=this.options.hidden?void 0:await this.resolveCodexProjectId(),r={cwd:this.workDir,approvalPolicy:"never",sandbox:"danger-full-access",serviceName:"Shennian",threadSource:this.options.hidden?"subagent":"user",experimentalRawEvents:!1,persistExtendedHistory:!0,...s?{projectId:s}:{},...t?{model:t}:{}};let i;if(this.options.hidden)i=await this.sendRpc("thread/start",r);else try{i=await this.sendRpc("thread/start",{...r,historyMode:"paginated"})}catch(a){throw K(a)?new Error("\u5F53\u524D Codex CLI \u4E0D\u652F\u6301\u521B\u5EFA\u53EF\u5728 Codex Desktop \u4E2D\u7EE7\u7EED\u7684\u65B0\u4F1A\u8BDD\uFF0C\u8BF7\u624B\u52A8\u5347\u7EA7 Codex CLI \u540E\u91CD\u8BD5\u3002\u795E\u5FF5\u4E0D\u4F1A\u81EA\u52A8\u5347\u7EA7\uFF0C\u4E5F\u4E0D\u4F1A\u964D\u7EA7\u521B\u5EFA\u4E0D\u53EF\u89C1\u7684 legacy \u4F1A\u8BDD\u3002",{cause:a}):a}const n=i.thread?.id;if(!n)throw new Error("codex app-server thread/start returned no thread id");this.agentSessionId=n}async resolveCodexProjectId(){const e=this.workDir?.trim();if(e)try{const t=[];let s;do{const d=await this.sendRpc("project/list",{limit:100,...s?{cursor:s}:{}});t.push(...d.data??[]),s=d.nextCursor}while(s);const r=Y(t,e);if(r)return r;const i=p.resolve(e),n=`shennian-project-${A("sha256").update(i).digest("hex")}`,l=(await this.sendRpc("project/create",{idempotencyKey:n,name:p.basename(i)||i,roots:[{path:i}],metadata:{createdBy:"Shennian"}})).project?.id;if(!l)throw new Error("codex app-server project/create returned no project id");return l}catch(t){throw X(t)?new Error("\u5F53\u524D Codex CLI \u4E0D\u652F\u6301\u521B\u5EFA\u53EF\u5F52\u5C5E\u5230 Codex Desktop \u9879\u76EE\u7684\u65B0\u4F1A\u8BDD\uFF0C\u8BF7\u624B\u52A8\u5347\u7EA7 Codex CLI \u540E\u91CD\u8BD5\u3002\u795E\u5FF5\u4E0D\u4F1A\u81EA\u52A8\u5347\u7EA7\uFF0C\u4E5F\u4E0D\u4F1A\u76F4\u63A5\u4FEE\u6539 Codex Desktop \u7684\u79C1\u6709\u4FA7\u680F\u7D22\u5F15\u3002",{cause:t}):t}}async startTurnWithRecovery(e,t,s,r,i){try{return await this.startTurn(e,t,s,r,i)}catch(n){if(!N(n))throw n;return await this.killProcess(),await this.ensureAppServer(s),await this.startTurn(e,t,s,r,i)}}async startTurn(e,t,s,r,i){try{return await this.sendRpc("turn/start",{threadId:e,input:[Z(t,i)],approvalPolicy:"never",sandboxPolicy:{type:"dangerFullAccess"},...s?{model:s}:{},...r?{effort:r}:{}})}catch(n){throw r&&U(n)?new Error(`Codex app-server does not accept reasoning effort "${r}" for this turn. Refresh models or upgrade Codex CLI, then retry.`,{cause:n}):n}}async interruptActiveTurn(){const e=this.agentSessionId,t=this.activeTurnId;!e||!t||!this.process||(await this.sendRpc("turn/interrupt",{threadId:e,turnId:t},5e3),this.activeTurnId=null)}sendRpc(e,t,s=6e4){if(this.appServerStartupError)return Promise.reject(this.appServerStartupError);const r=this.process;if(!r?.stdin)return Promise.reject(new Error("codex app-server is not running"));const i=this.rpcSeq++,n=JSON.stringify({id:i,method:e,params:t});return new Promise((a,l)=>{const d=setTimeout(()=>{this.pendingRequests.delete(i),l(new Error(`codex app-server request timed out: ${e}`))},s);this.pendingRequests.set(i,{resolve:a,reject:l,timer:d}),r.stdin.write(`${n}
|
|
5
|
+
`,u=>{u&&(clearTimeout(d),this.pendingRequests.delete(i),l(u))})})}handleAppServerMessage(e){if(e.id!=null){const s=this.pendingRequests.get(e.id);s&&(clearTimeout(s.timer),this.pendingRequests.delete(e.id),e.error?s.reject(new Error(e.error.message||`codex app-server error ${e.error.code??""}`.trim())):s.resolve(e.result))}if(!e.method)return;const t=e.params??{};switch(e.method){case"turn/started":{const s=typeof t.turn=="object"&&t.turn!==null?t.turn:null,r=typeof s?.id=="string"?s.id:null;r&&(this.activeTurnId=r);break}case"thread/status/changed":{const s=typeof t.threadId=="string"?t.threadId:"";if(s&&this.agentSessionId&&s!==this.agentSessionId)break;const r=typeof t.status=="object"&&t.status!==null?t.status:null;r?.type==="active"&&this.emitEvent({state:"heartbeat",runPhase:this.mapThreadStatusToRunPhase(r)});break}case"item/agentMessage/delta":{const s=typeof t.itemId=="string"?t.itemId:"";s&&this.deltaItemIds.add(s),typeof t.delta=="string"&&this.emitText(t.delta);break}case"item/completed":this.handleAppServerCompletedItem(t);break;case"thread/tokenUsage/updated":this.handleAppServerUsage(t);break;case"turn/completed":{const s=typeof t.turn=="object"&&t.turn!==null?t.turn:null,r=typeof s?.id=="string"?s.id:null;(!this.activeTurnId||r===this.activeTurnId)&&(this.activeTurnId=null,this.emitFinalIfOpen());break}case"error":{const s=$(t);if(W(s)){this.emitTransientStatus(s);return}this.emitErrorIfOpen(s);break}}}mapThreadStatusToRunPhase(e){const t=e?.type==="active"&&Array.isArray(e.activeFlags)?e.activeFlags:[];return t.includes("waitingOnApproval")?"waiting_approval":t.includes("waitingOnUserInput")?"waiting_user_input":"thinking"}handleAppServerNonJsonStdout(e){const t=H(e);z(t)&&this.failAppServerStartup(new Error("Codex \u672A\u767B\u5F55\u3002\u8BF7\u5148\u5728\u8FD9\u53F0\u7535\u8111\u7684\u7EC8\u7AEF\u8FD0\u884C `codex`\uFF0C\u6309\u63D0\u793A\u5B8C\u6210 ChatGPT \u767B\u5F55\u6216\u914D\u7F6E API key\uFF0C\u7136\u540E\u518D\u56DE\u5230\u795E\u5FF5\u53D1\u9001\u6D88\u606F\u3002"))}failAppServerStartup(e){this.appServerStartupError||(this.appServerStartupError=e,this.rejectAllPending(e),this.process?.kill("SIGTERM"))}handleAppServerCompletedItem(e){const t=typeof e.item=="object"&&e.item!==null?e.item:null;if(!(!t||typeof t.type!="string")&&!F(t))switch(t.type){case"agentMessage":{const s=typeof t.id=="string"?t.id:"";if(s&&this.deltaItemIds.has(s))return;typeof t.text=="string"&&this.emitText(t.text,"block");break}case"commandExecution":{const s=typeof t.command=="string"?t.command:"",r=typeof t.aggregatedOutput=="string"?t.aggregatedOutput:"";s&&this.emitEvent({state:"tool-call",name:"command_execution",args:{command:s}}),this.emitEvent({state:"tool-result",name:"command_execution",result:[r,t.exitCode!=null?`
|
|
6
6
|
(exit ${t.exitCode})`:""].join("").trim()});break}case"mcpToolCall":case"dynamicToolCall":{const s=typeof t.tool=="string"?t.tool:t.type;if(g(s))return;const r=typeof t.arguments=="object"&&t.arguments!==null?t.arguments:{};this.emitEvent({state:"tool-call",name:s,args:r}),this.emitEvent({state:"tool-result",name:s,result:k(t.error??t.result??t.contentItems??"")});break}case"fileChange":break;case"approvalRequest":case"approval_request":case"permissionRequest":case"permission_request":case"userInputRequired":case"user_input_required":{this.emitApprovalPending(t);break}}}handleAppServerUsage(e){const t=typeof e.tokenUsage=="object"&&e.tokenUsage!==null?e.tokenUsage:null,s=typeof t?.last=="object"&&t.last!==null?t.last:null,r=typeof t?.total=="object"&&t.total!==null?t.total:null,i=s??r;if(!i)return;const n=typeof i.inputTokens=="number"?i.inputTokens:void 0,a=typeof i.outputTokens=="number"?i.outputTokens:void 0;(n!=null||a!=null)&&(this.pendingUsage={inputTokens:n??0,outputTokens:a??0})}handleCodexItemLine(e){const t=e.item;if(t?.type&&!M(t))switch(t.type){case"agent_message":case"reasoning":{e.type==="item.completed"&&t.text&&this.emitText(t.text,"block");break}case"command_execution":{if(e.type==="item.started"&&t.command&&this.emitEvent({state:"tool-call",name:"command_execution",args:{command:t.command}}),e.type==="item.completed"){const s=[t.aggregated_output??"",t.exit_code!=null?`
|
|
7
7
|
(exit ${t.exit_code})`:""];this.emitEvent({state:"tool-result",name:"command_execution",result:s.join("").trim()||String(t.status??"")})}break}case"mcp_tool_call":{if(t.tool&&g(t.tool))return;if(e.type==="item.started"&&this.emitEvent({state:"tool-call",name:t.tool??"mcp_tool",args:{server:t.server,...t.arguments&&typeof t.arguments=="object"?t.arguments:{}}}),e.type==="item.completed"){const s=t.error&&typeof t.error=="object"?t.error.message:void 0;this.emitEvent({state:"tool-result",name:t.tool??"mcp_tool",result:s??k(t.result)})}break}case"file_change":break;case"web_search":{e.type==="item.completed"&&t.query&&this.emitText(`[web_search] ${t.query}`,"block");break}case"todo_list":{if(e.type==="item.completed"&&t.items?.length){const s=t.items.map(r=>`- [${r.completed?"x":" "}] ${r.text}`).join(`
|
|
8
|
-
`);this.emitText(s,"block")}break}case"approval_request":case"approvalRequest":case"permission_request":case"permissionRequest":case"user_input_required":case"userInputRequired":{(e.type==="item.started"||e.type==="item.completed")&&this.emitApprovalPending(t);break}case"error":{e.type==="item.completed"&&t.message&&this.emitEvent({state:"error",message:t.message});break}default:break}}handleStreamEvent(e){const t=e.type??e.event;switch(t?.startsWith("item.")&&this.handleCodexItemLine(e),t){case"turn.completed":{const r=e.usage;if(r){const i=r.input_tokens??r.prompt_tokens,n=r.output_tokens??r.completion_tokens;(i!=null||n!=null)&&(this.pendingUsage={inputTokens:i??0,outputTokens:n??0})}this.emitFinalIfOpen();return}case"turn.failed":{const r=typeof e.error=="object"&&e.error?.message?e.error.message:typeof e.error=="string"?e.error:"turn failed";this.emitErrorIfOpen(r);return}case"error":{const r=typeof e.message=="string"?e.message:"";if(r.includes("Reconnecting"))return;this.emitErrorIfOpen(r||(typeof e.error=="string"?e.error:"stream error"));return}case"thread.started":{const r=e.thread_id??e.session_id??e.id;typeof r=="string"&&r&&(this.agentSessionId=r,this.emitEvent({state:"start",agentSessionId:this.agentSessionId}));return}case"turn.started":return}if(t?.startsWith("item."))return;switch(e.type??e.event){case"message":case"text":case"content":{const r=e.content??e.text;r&&this.emitText(r);break}case"function_call":case"tool_call":{if(this.isApprovalLikeName(e.name)){this.emitApprovalPending(e);break}if(e.name&&g(e.name))return;this.emitEvent({state:"tool-call",name:e.name,args:e.arguments??e.input});break}case"function_call_output":case"tool_result":{if(e.name&&g(e.name))return;this.emitEvent({state:"tool-result",name:e.name,result:e.output??e.result});break}case"approval_request":case"approvalRequest":case"permission_request":case"permissionRequest":case"user_input_required":case"userInputRequired":{this.emitApprovalPending(e);break}case"error":break;case"done":case"completed":{e.usage&&(this.pendingUsage={inputTokens:e.usage.prompt_tokens??e.usage.input_tokens??0,outputTokens:e.usage.completion_tokens??e.usage.output_tokens??0}),this.emitFinalIfOpen();break}}}emitEvent(e){if(this.terminalState!=="open"&&e.state!=="final"&&e.state!=="error"&&e.state!=="aborted")return;const t={...e,runId:this.runId,seq:this.seq++};this.emit("agentEvent",t)}isApprovalLikeName(e){return typeof e!="string"?!1:/approval|permission|confirm|user[_-]?input|request[_-]?user/i.test(e)}emitApprovalPending(e){const t=typeof e.command=="string"?e.command:"",s=typeof e.title=="string"&&e.title.trim()?e.title:"\u9700\u8981\u7528\u6237\u786E\u8BA4",r=typeof e.prompt=="string"?e.prompt:typeof e.reason=="string"?e.reason:typeof e.message=="string"?e.message:t?`Codex \u8BF7\u6C42\u6267\u884C\u547D\u4EE4\uFF1A${t}`:"Codex \u6B63\u5728\u7B49\u5F85\u7528\u6237\u786E\u8BA4\u540E\u7EE7\u7EED\u3002";this.emitEvent({state:"approval-pending",approval:{title:s,content:r,source:"codex",actionHint:"\u5F53\u524D\u795E\u5FF5\u6682\u4E0D\u652F\u6301\u8FDC\u7A0B\u786E\u8BA4\uFF0C\u8BF7\u56DE\u5230\u8FD0\u884C Codex \u7684\u7535\u8111/\u7EC8\u7AEF\u5B8C\u6210\u786E\u8BA4\u3002"}})}async killProcess(){const e=this.process;if(this.appServerReady=null,this.activeTurnId=null,!e){this.rejectAllPending(new Error("codex app-server stopped"));return}this.process=null,this.clearForceCloseTimer(),e.kill("SIGTERM"),await new Promise(t=>{e.on("close",t),setTimeout(()=>{e.kill("SIGKILL"),t()},3e3)}),this.rejectAllPending(new Error("codex app-server stopped"))}rejectAllPending(e){for(const[t,s]of this.pendingRequests.entries())clearTimeout(s.timer),s.reject(e),this.pendingRequests.delete(t)}handleProcessClose(e,t){const s=
|
|
9
|
-
`,thinking:!0}))}resetTextState(){this.hasEmittedText=!1,this.textTail=""}emitText(e,t="delta"){if(!e)return;let s=
|
|
8
|
+
`);this.emitText(s,"block")}break}case"approval_request":case"approvalRequest":case"permission_request":case"permissionRequest":case"user_input_required":case"userInputRequired":{(e.type==="item.started"||e.type==="item.completed")&&this.emitApprovalPending(t);break}case"error":{e.type==="item.completed"&&t.message&&this.emitEvent({state:"error",message:t.message});break}default:break}}handleStreamEvent(e){const t=e.type??e.event;switch(t?.startsWith("item.")&&this.handleCodexItemLine(e),t){case"turn.completed":{const r=e.usage;if(r){const i=r.input_tokens??r.prompt_tokens,n=r.output_tokens??r.completion_tokens;(i!=null||n!=null)&&(this.pendingUsage={inputTokens:i??0,outputTokens:n??0})}this.emitFinalIfOpen();return}case"turn.failed":{const r=typeof e.error=="object"&&e.error?.message?e.error.message:typeof e.error=="string"?e.error:"turn failed";this.emitErrorIfOpen(r);return}case"error":{const r=typeof e.message=="string"?e.message:"";if(r.includes("Reconnecting"))return;this.emitErrorIfOpen(r||(typeof e.error=="string"?e.error:"stream error"));return}case"thread.started":{const r=e.thread_id??e.session_id??e.id;typeof r=="string"&&r&&(this.agentSessionId=r,this.emitEvent({state:"start",agentSessionId:this.agentSessionId}));return}case"turn.started":return}if(t?.startsWith("item."))return;switch(e.type??e.event){case"message":case"text":case"content":{const r=e.content??e.text;r&&this.emitText(r);break}case"function_call":case"tool_call":{if(this.isApprovalLikeName(e.name)){this.emitApprovalPending(e);break}if(e.name&&g(e.name))return;this.emitEvent({state:"tool-call",name:e.name,args:e.arguments??e.input});break}case"function_call_output":case"tool_result":{if(e.name&&g(e.name))return;this.emitEvent({state:"tool-result",name:e.name,result:e.output??e.result});break}case"approval_request":case"approvalRequest":case"permission_request":case"permissionRequest":case"user_input_required":case"userInputRequired":{this.emitApprovalPending(e);break}case"error":break;case"done":case"completed":{e.usage&&(this.pendingUsage={inputTokens:e.usage.prompt_tokens??e.usage.input_tokens??0,outputTokens:e.usage.completion_tokens??e.usage.output_tokens??0}),this.emitFinalIfOpen();break}}}emitEvent(e){if(this.terminalState!=="open"&&e.state!=="final"&&e.state!=="error"&&e.state!=="aborted")return;const t={...e,runId:this.runId,seq:this.seq++};this.emit("agentEvent",t)}isApprovalLikeName(e){return typeof e!="string"?!1:/approval|permission|confirm|user[_-]?input|request[_-]?user/i.test(e)}emitApprovalPending(e){const t=typeof e.command=="string"?e.command:"",s=typeof e.title=="string"&&e.title.trim()?e.title:"\u9700\u8981\u7528\u6237\u786E\u8BA4",r=typeof e.prompt=="string"?e.prompt:typeof e.reason=="string"?e.reason:typeof e.message=="string"?e.message:t?`Codex \u8BF7\u6C42\u6267\u884C\u547D\u4EE4\uFF1A${t}`:"Codex \u6B63\u5728\u7B49\u5F85\u7528\u6237\u786E\u8BA4\u540E\u7EE7\u7EED\u3002";this.emitEvent({state:"approval-pending",approval:{title:s,content:r,source:"codex",actionHint:"\u5F53\u524D\u795E\u5FF5\u6682\u4E0D\u652F\u6301\u8FDC\u7A0B\u786E\u8BA4\uFF0C\u8BF7\u56DE\u5230\u8FD0\u884C Codex \u7684\u7535\u8111/\u7EC8\u7AEF\u5B8C\u6210\u786E\u8BA4\u3002"}})}async killProcess(){const e=this.process;if(this.appServerReady=null,this.activeTurnId=null,!e){this.rejectAllPending(new Error("codex app-server stopped"));return}this.process=null,this.clearForceCloseTimer(),e.kill("SIGTERM"),await new Promise(t=>{e.on("close",t),setTimeout(()=>{e.kill("SIGKILL"),t()},3e3)}),this.rejectAllPending(new Error("codex app-server stopped"))}rejectAllPending(e){for(const[t,s]of this.pendingRequests.entries())clearTimeout(s.timer),s.reject(e),this.pendingRequests.delete(t)}handleProcessClose(e,t){const s=T(t);if(e!==0&&e!==null){const r=s||`codex exited with code ${e}`;this.emitErrorIfOpen(r)}else B(s)?this.emitErrorIfOpen(s):this.emitFinalIfOpen();this.pendingUsage=void 0}emitFinalIfOpen(){this.terminalState==="open"&&(this.terminalState="final",this.emitEvent({state:"final",usage:this.pendingUsage,agentSessionId:this.agentSessionId??void 0}),this.killProcess().catch(()=>{}))}emitErrorIfOpen(e){this.terminalState==="open"&&(this.terminalState="error",this.emitEvent({state:"error",message:L(e)}),this.scheduleProcessClose())}resetRunState(){this.pendingUsage=void 0,this.terminalState="open",this.deltaItemIds.clear(),this.lastTransientStatus="",this.clearForceCloseTimer(),this.resetTextState()}emitTransientStatus(e){const t=D(e);!t||t===this.lastTransientStatus||this.terminalState!=="open"||(this.lastTransientStatus=t,this.emitEvent({state:"delta",text:`${t}
|
|
9
|
+
`,thinking:!0}))}resetTextState(){this.hasEmittedText=!1,this.textTail=""}emitText(e,t="delta"){if(!e)return;let s=J(e);s&&(t==="block"&&this.hasEmittedText&&!s.startsWith(`
|
|
10
10
|
`)&&(this.textTail.endsWith(`
|
|
11
11
|
`)?s=`
|
|
12
12
|
${s}`:this.textTail.endsWith(`
|
|
13
13
|
|
|
14
14
|
`)||(s=`
|
|
15
15
|
|
|
16
|
-
${s}`)),this.hasEmittedText=!0,this.textTail=(this.textTail+s).slice(-4),this.emitEvent({state:"delta",text:s}))}scheduleProcessClose(){const e=this.process;!e||this.forceCloseTimer||(this.forceCloseTimer=setTimeout(()=>{this.forceCloseTimer=null,this.process===e&&(e.kill("SIGTERM"),setTimeout(()=>{this.process===e&&e.kill("SIGKILL")},1e3))},250))}clearForceCloseTimer(){this.forceCloseTimer&&(clearTimeout(this.forceCloseTimer),this.forceCloseTimer=null)}}q("codex",()=>new
|
|
16
|
+
${s}`)),this.hasEmittedText=!0,this.textTail=(this.textTail+s).slice(-4),this.emitEvent({state:"delta",text:s}))}scheduleProcessClose(){const e=this.process;!e||this.forceCloseTimer||(this.forceCloseTimer=setTimeout(()=>{this.forceCloseTimer=null,this.process===e&&(e.kill("SIGTERM"),setTimeout(()=>{this.process===e&&e.kill("SIGKILL")},1e3))},250))}clearForceCloseTimer(){this.forceCloseTimer&&(clearTimeout(this.forceCloseTimer),this.forceCloseTimer=null)}}q("codex",()=>new ne);export{ne as CodexAdapter,re as buildCodexQueueArgs,se as buildCodexQueuedText,V as isCodexActiveWriterError,Q as isCodexExcludeTurnsUnsupportedError,K as isCodexPaginatedHistoryUnsupportedError,X as isCodexProjectApiUnsupportedError,ge as isCodexUnsupportedEffortError,xe as isMissingCodexRolloutError,ve as normalizeCodexModelId,Ie as normalizeCodexReasoningEffort,Y as selectCodexProjectIdForWorkDir};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import l from"node:fs";import p from"chalk";function u(){const o=process.env.SHENNIAN_MANAGER_IPC_URL,a=process.env.SHENNIAN_MANAGER_IPC_TOKEN,s=process.env.SHENNIAN_MANAGER_SESSION_ID;return(!o||!a||!s)&&(console.error(p.red("\u2717 This command must run inside a Shennian Manager Agent session.")),process.exit(1)),{url:o,token:a,managerSessionId:s}}async function t(o,a){const s=u(),r=await fetch(`${s.url}${o}`,{method:"POST",headers:{authorization:`Bearer ${s.token}`,"content-type":"application/json","x-shennian-manager-session-id":s.managerSessionId},body:JSON.stringify({...a,managerSessionId:s.managerSessionId})}),d=await r.json().catch(()=>({ok:!1,error:r.statusText}));if(!r.ok||!d.ok)throw new Error(d.error||`Manager IPC failed: ${r.status}`);return d}function m(o){console.log(JSON.stringify(o,null,2))}function g(o){return o.messageFile?l.readFileSync(o.messageFile,"utf-8"):o.message??""}function I(o){const a=o.command("manager",{hidden:!0}).description("Manager Session local tools"),s=a.command("sessions").description("Manage same-project worker sessions");s.command("list").description("List same-project sessions visible to this Manager").option("--json","Print JSON").action(async e=>{const n=await t("/sessions/list",{});if(e.json)m(n);else{const c=n.sessions??[];for(const i of c)console.log(`${i.sessionId} ${i.agentType} ${i.role??""} ${i.status} ${i.title??i.summary??""}`)}}),s.command("start").description("Start a worker session in the same project").requiredOption("--agent <agent>","Worker agent type, e.g. codex, claude,
|
|
1
|
+
import l from"node:fs";import p from"chalk";function u(){const o=process.env.SHENNIAN_MANAGER_IPC_URL,a=process.env.SHENNIAN_MANAGER_IPC_TOKEN,s=process.env.SHENNIAN_MANAGER_SESSION_ID;return(!o||!a||!s)&&(console.error(p.red("\u2717 This command must run inside a Shennian Manager Agent session.")),process.exit(1)),{url:o,token:a,managerSessionId:s}}async function t(o,a){const s=u(),r=await fetch(`${s.url}${o}`,{method:"POST",headers:{authorization:`Bearer ${s.token}`,"content-type":"application/json","x-shennian-manager-session-id":s.managerSessionId},body:JSON.stringify({...a,managerSessionId:s.managerSessionId})}),d=await r.json().catch(()=>({ok:!1,error:r.statusText}));if(!r.ok||!d.ok)throw new Error(d.error||`Manager IPC failed: ${r.status}`);return d}function m(o){console.log(JSON.stringify(o,null,2))}function g(o){return o.messageFile?l.readFileSync(o.messageFile,"utf-8"):o.message??""}function I(o){const a=o.command("manager",{hidden:!0}).description("Manager Session local tools"),s=a.command("sessions").description("Manage same-project worker sessions");s.command("list").description("List same-project sessions visible to this Manager").option("--json","Print JSON").action(async e=>{const n=await t("/sessions/list",{});if(e.json)m(n);else{const c=n.sessions??[];for(const i of c)console.log(`${i.sessionId} ${i.agentType} ${i.role??""} ${i.status} ${i.title??i.summary??""}`)}}),s.command("start").description("Start a worker session in the same project").requiredOption("--agent <agent>","Worker agent type, e.g. codex, claude, cursor, opencode, pi, or custom:<name>").requiredOption("--workdir <path>","Worker workdir; must match Manager workdir").option("--model <model>","Worker model id").option("--message <text>","Worker prompt").option("--message-file <path>","Read worker prompt from file").option("--json","Print JSON").action(async e=>{const n=await t("/sessions/start",{agentType:e.agent,workDir:e.workdir,modelId:e.model,message:g(e)});e.json?m(n):console.log(n.session?.sessionId??"ok")}),s.command("send").description("Queue a message for a managed worker; runs immediately if idle").requiredOption("--session-id <id>","Worker session id").option("--message <text>","Message text").option("--message-file <path>","Read message from file").option("--direct","Bypass queue and send immediately").action(async e=>{await t("/sessions/send",{sessionId:e.sessionId,message:g(e),enqueue:!e.direct}),console.log("ok")});const r=s.command("queue").description("Inspect and edit pending worker messages");r.command("list").description("List pending messages for a managed worker").requiredOption("--session-id <id>","Worker session id").option("--json","Print JSON").action(async e=>{const n=await t("/sessions/queue",{sessionId:e.sessionId});if(e.json)m(n);else{const c=n.queue?.pending??[];for(const i of c)console.log(`${i.id} ${i.text.replace(/\s+/g," ").slice(0,120)}`)}}),r.command("edit").description("Edit a pending worker message").requiredOption("--session-id <id>","Worker session id").requiredOption("--message-id <id>","Queued message id").option("--message <text>","Replacement message text").option("--message-file <path>","Read replacement message from file").action(async e=>{await t("/sessions/queue/edit",{sessionId:e.sessionId,queueMessageId:e.messageId,message:g(e)}),console.log("ok")}),r.command("delete").description("Delete a pending worker message").requiredOption("--session-id <id>","Worker session id").requiredOption("--message-id <id>","Queued message id").action(async e=>{await t("/sessions/queue/delete",{sessionId:e.sessionId,queueMessageId:e.messageId}),console.log("ok")});const d=async e=>{await t("/sessions/stop",{sessionId:e.sessionId}),console.log("ok")};s.command("stop").aliases(["terminate","kill"]).description("Stop or terminate a running managed worker").requiredOption("--session-id <id>","Worker session id").action(d),s.command("read").description("Read a managed worker transcript").requiredOption("--session-id <id>","Worker session id").option("--limit <n>","Message limit","200").option("--json","Print JSON").action(async e=>{const n=await t("/sessions/read",{sessionId:e.sessionId,limit:Number(e.limit)});e.json?m(n):m(n.messages??[])}),a.command("memory").description("Manager project memory helpers").command("path").description("Print project memory directory path").action(async()=>{const e=await t("/memory/path",{});console.log(e.path)})}export{I as registerManagerCommand};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{normalizeText as
|
|
1
|
+
import{normalizeText as s}from"./parser-common.js";function E(t){return Array.isArray(t)?t.map(e=>{if(typeof e=="string")return e;if(typeof e!="object"||e===null)return"";const n=e;return typeof n.text=="string"?n.text:""}).map(e=>s(e)).filter(Boolean).join(`
|
|
2
2
|
|
|
3
|
-
`):""}function
|
|
3
|
+
`):""}function x(t){return s(t.replace(/<image>\s*<\/image>/gi,""))}const g=/^<in-app-browser-context\b(?=[^>]*\bsource=["']ambient-ui-state["'])[^>]*>[\s\S]*?<\/in-app-browser-context>\s*\n+##\s+My request(?: for Codex)?:\s*([\s\S]*)$/i;function m(t){const e=s(t);if(!e)return{text:"",strippedWrapper:!1};const n=e.match(g);if(n?.[1])return{text:x(n[1]),strippedWrapper:!0};const p=e.match(/My request for Codex:\s*([\s\S]*)$/i);if(p?.[1])return{text:x(p[1]),strippedWrapper:!0};const a=e.match(/^# Files mentioned by the user:\s*[\s\S]*?\n+## My request:\s*([\s\S]*)$/i);return a?.[1]?{text:x(a[1]),strippedWrapper:!0}:{text:e,strippedWrapper:!1}}const y=/^<image\b[^>]*\bpath="([^"]+)"[^>]*>\s*$/i,h=/^<\/image>\s*$/i;function b(t){if(!Array.isArray(t))return{text:"",imagePaths:[],strippedWrapper:!1};const e=new Set,n=[];for(let r=0;r+2<t.length;r+=1){const i=t[r],o=t[r+1],u=t[r+2];if(typeof i!="object"||i===null||typeof o!="object"||o===null||typeof u!="object"||u===null)continue;const c=i,l=o,f=u;if(c.type!=="input_text"||typeof c.text!="string"||l.type!=="input_image"||f.type!=="input_text"||typeof f.text!="string"||!h.test(s(f.text)))continue;const d=s(c.text).match(y)?.[1];d&&(n.push(d),e.add(r),e.add(r+2))}const p=t.map((r,i)=>{if(e.has(i))return"";if(typeof r=="string")return r;if(typeof r!="object"||r===null)return"";const o=r;return o.type==="input_text"&&typeof o.text=="string"?o.text:""}).map(r=>s(r)).filter(Boolean).join(`
|
|
4
4
|
|
|
5
|
-
`);return{...
|
|
5
|
+
`);return{...m(p),imagePaths:n}}function R(t){if(t.namespace!=="codex_app"||typeof t.output!="string")return null;const e=s(t.output);if(!e.startsWith("<codex_delegation>")||!e.endsWith("</codex_delegation>"))return null;const n=e.match(/<input>([\s\S]*?)<\/input>\s*<\/codex_delegation>$/)?.[1];return n?s(n):null}export{R as parseCodexDelegationInput,b as parseCodexResponseUserInput,E as parseCodexTextElements,m as stripCodexUserMessageWrapper};
|
|
@@ -16,9 +16,11 @@ export declare class NativeSessionFusionService {
|
|
|
16
16
|
private pendingClaims;
|
|
17
17
|
private suppressionWindows;
|
|
18
18
|
private externalObservations;
|
|
19
|
+
private externalTerminalHandler;
|
|
19
20
|
constructor(client: CliRelayClient, repository?: NativeFusionLocalRepository);
|
|
20
21
|
start(): void;
|
|
21
22
|
stop(): void;
|
|
23
|
+
setExternalTerminalHandler(handler: ((sessionId: string) => void) | null): void;
|
|
22
24
|
handleConnected(): void;
|
|
23
25
|
registerManagedSend(params: {
|
|
24
26
|
sessionId: string;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import K from"node:fs";import B from"node:crypto";import{publishPendingSessionIndexes as N}from"../session/index-publisher.js";import{NativeFusionFileWatcher as P}from"./file-watcher.js";import{NativeFusionLocalRepository as k}from"./local-repository.js";import{listClaudeTranscriptFiles as O,listCodexRolloutFiles as R,listOpenCodeSessionFiles as D,listNativeTranscriptRoots as L,inspectNativeSessionFile as q,lookupCodexThreadName as W,parseClaudeTranscriptChunk as $,parseCodexRolloutChunk as U,parseOpenCodeSessionFile as z}from"./parsers.js";import{loadNativeScannerState as
|
|
2
|
-
`).trim()}function F(){return new Promise(u=>setImmediate(u))}function ee(u){return u.split(/[\\/]+/).includes(".codex")}class de{client;repository;timer=null;activeTimer=null;scanPromise=null;queuedTargetFiles=new Set;pendingBoundaryFiles=new Set;sourceFiles=new Map;fullScanQueued=!1;watchEnabled=!1;lifecycleRevision=0;watcher=new P(e=>{this.scanNow(e).catch(s=>{console.error("[native-fusion] watched file scan failed",s)})});pendingClaims;suppressionWindows;externalObservations=new Map;constructor(e,s=new k){this.client=e,this.repository=s,this.pendingClaims=new Map((this.repository.listManagedClaims?.()??[]).map(t=>[t.canonicalMessageId,t])),this.suppressionWindows=new Map((this.repository.listManagedSuppressions?.()??[]).map(t=>[T(t.agentType,t.sourceSessionKey),t]))}start(){this.timer||(this.timer=setInterval(()=>{this.scanNow().catch(e=>{console.error("[native-fusion] periodic scan failed",e)})},Q))}stop(){this.lifecycleRevision+=1,this.timer&&clearInterval(this.timer),this.timer=null,this.activeTimer&&clearInterval(this.activeTimer),this.activeTimer=null,this.pendingBoundaryFiles.clear(),this.watchEnabled=!1,this.watcher.stop()}handleConnected(){const e=++this.lifecycleRevision;this.watchEnabled=!0,this.scanNow().catch(s=>{console.error("[native-fusion] initial scan failed",s)}).finally(()=>{this.lifecycleRevision===e&&this.start()})}registerManagedSend(e){if(!e.canonicalMessageId)return;const s=e.sourceAgentType??e.agentType;if(s!=="codex"&&s!=="claude"&&s!=="opencode")return;const t=e.canonicalMessageId,i=this.pendingClaims.get(t),a=_(e.text);if(i&&(i.sessionId!==e.sessionId||i.sourceAgentType!==s||i.textHash!==a))throw new Error(`managed echo claim identity conflict: ${t}`);const l={sessionId:e.sessionId,agentType:e.agentType,sourceAgentType:s,canonicalMessageId:e.canonicalMessageId,textHash:a,createdAt:i?.createdAt??Date.now(),sourceSessionKey:e.sourceSessionKey??i?.sourceSessionKey??null,managedEchoPolicy:e.managedEchoPolicy??"suppress_following",externalRunId:e.externalRunId??null};if(this.repository.upsertManagedClaim?.(l),this.pendingClaims.set(t,l),e.externalRunId&&e.sourceSessionKey){const y=`${s}:${e.sourceSessionKey}`,g=this.externalObservations.get(y)??[];g.push({sessionId:e.sessionId,sourceSessionKey:e.sourceSessionKey,runId:e.externalRunId,canonicalMessageId:e.canonicalMessageId,state:"queued",createdAt:Date.now(),lastPublishedAt:Date.now()}),this.externalObservations.set(y,g),this.emitExternalActivity(g[g.length-1],"queued"),this.ensureActiveScanning()}}cancelManagedSend(e){this.repository.removeManagedClaim?.(e),this.pendingClaims.delete(e)}noteManagedSourceSession(e,s,t){if(t){this.repository.updateManagedClaimSource?.(e,s,t);for(const i of this.pendingClaims.values())i.sessionId===e&&i.sourceAgentType===s&&(i.sourceSessionKey=t)}}handleSessionDeleted(e){for(const[s,t]of this.pendingClaims.entries())t.sessionId===e&&this.pendingClaims.delete(s);for(const[s,t]of this.suppressionWindows.entries())t.sessionId===e&&this.suppressionWindows.delete(s);for(const[s,t]of this.externalObservations.entries()){const i=t.filter(a=>a.sessionId!==e);i.length===0?this.externalObservations.delete(s):this.externalObservations.set(s,i)}this.repository.purgeSession(e)}ensureActiveScanning(e=!0){this.activeTimer||(this.activeTimer=setInterval(()=>{if(this.externalObservations.size===0&&this.pendingBoundaryFiles.size===0){this.activeTimer&&clearInterval(this.activeTimer),this.activeTimer=null;return}this.scanActiveTargets()},X),e&&this.scanActiveTargets())}scanActiveTargets(){const e=this.activeScanTargets();e===void 0&&this.scanPromise||this.scanNow(e).catch(s=>{console.error("[native-fusion] active observation scan failed",s)})}activeScanTargets(){const e=[...this.externalObservations.keys()];if(!e.some(s=>!this.sourceFiles.has(s)))return[...new Set([...this.pendingBoundaryFiles,...e.flatMap(s=>{const t=this.sourceFiles.get(s);return t?[t]:[]})])]}emitExternalActivity(e,s){this.client.sendAgentEvent({type:"event",event:"agent",id:`external-owner-${e.runId}-${s}-${e.lastPublishedAt}`,payload:{state:s==="queued"?"start":"heartbeat",sessionId:e.sessionId,runId:e.runId,seq:s==="queued"?0:1,runPhase:"thinking",canStop:!1,owner:"external",ownerLabel:"Codex Desktop",lifecycle:s}})}emitExternalTerminal(e,s="completed"){const t=s==="completed"?"final":s==="aborted"?"aborted":"error";this.client.sendAgentEvent({type:"event",event:"agent",id:`external-owner-${e.runId}-${t}`,payload:{state:t,sessionId:e.sessionId,runId:e.runId,seq:2,...t==="error"?{message:"Codex Desktop turn failed"}:{},owner:"external",ownerLabel:"Codex Desktop"}})}pruneState(){const e=Date.now();for(const[s,t]of this.suppressionWindows.entries())t.endsAt>e||(this.repository.removeManagedSuppression?.(t.agentType,t.sourceSessionKey),this.suppressionWindows.delete(s))}async scanNow(e){if(this.client.getState()==="connected"){if(this.scanPromise){const s=this.scanPromise;if(e)for(const t of e)this.queuedTargetFiles.add(t);else this.fullScanQueued=!0;if(await s,this.fullScanQueued)return this.fullScanQueued=!1,this.queuedTargetFiles.clear(),this.scanNow();if(this.queuedTargetFiles.size>0){const t=[...this.queuedTargetFiles];return this.queuedTargetFiles.clear(),this.scanNow(t)}return}return this.scanPromise=this.runScanLoop(e).finally(()=>{this.scanPromise=null}),this.scanPromise}}async runScanLoop(e){let s=e?[...new Set(e)]:void 0;for(;;){if(await this.runScan(s),this.fullScanQueued){this.fullScanQueued=!1,this.queuedTargetFiles.clear(),s=void 0;continue}if(this.queuedTargetFiles.size>0){s=[...this.queuedTargetFiles],this.queuedTargetFiles.clear();continue}return}}async runScan(e){this.pruneState(),this.refreshExternalObservations();const s=V(),t={...s.files},i=[],a=new Map,l=new Map,y=new Map,g=e?[...e]:[...R(),...O(),...D()];!e&&this.watchEnabled&&this.watcher.refresh(g,L());for(let o=0;o<g.length;o+=1){const n=g[o];if(o>0&&o%G===0&&await F(),!K.existsSync(n)){this.watchEnabled&&this.watcher.untrackFile(n),this.pendingBoundaryFiles.delete(n);for(const[r,p]of this.sourceFiles.entries())p===n&&this.sourceFiles.delete(r);continue}this.watchEnabled&&this.watcher.trackFile(n);const d=K.statSync(n),h=ee(n),f=n.endsWith(".opencode-session.json");let c=s.files[n];const I=`${String(d.dev)}:${String(d.ino)}:${String(d.birthtimeMs)}`;let x=!c;if(c&&!f&&(d.size<c.offset||c.fileIdentity&&c.fileIdentity!==I)&&(c=void 0,x=!0),!!(c&&c.fileIdentity===I&&c.mtimeMs===d.mtimeMs&&c.offset>=d.size&&c.sources?.length)&&c){for(const r of c.sources??[])this.sourceFiles.set(T(r.agentType,r.sourceSessionKey),n);c.classification==="live"&&c.awaitingUserBoundary?this.pendingBoundaryFiles.add(n):this.pendingBoundaryFiles.delete(n);continue}const m=c?.sources?.length&&!f?{status:"ready",createdAtMs:c.sessionCreatedAtMs??d.birthtimeMs,sources:c.sources}:q(n);if(m.status==="pending"){d.birthtimeMs>=s.fusionEpochMs&&this.pendingBoundaryFiles.add(n);continue}for(const r of m.sources)this.sourceFiles.set(T(r.agentType,r.sourceSessionKey),n);if(!f&&m.sources.length>0&&m.sources.every(r=>this.repository.isSourceDeleted?.(r.agentType,r.sourceSessionKey))){t[n]={...c??{daemonInstallationId:s.daemonInstallationId,classification:m.createdAtMs>=s.fusionEpochMs?"live":"historical",awaitingUserBoundary:!0,sessionCreatedAtMs:m.createdAtMs,sourceBoundaryEstablished:{}},offset:d.size,mtimeMs:d.mtimeMs,fileIdentity:I};continue}if(!c)c={daemonInstallationId:s.daemonInstallationId,offset:0,mtimeMs:d.mtimeMs,classification:m.createdAtMs>=s.fusionEpochMs?"live":"historical",awaitingUserBoundary:!0,sessionCreatedAtMs:m.createdAtMs,sourceBoundaryEstablished:{}};else if(f&&c.mtimeMs===d.mtimeMs)continue;const E=f?0:c.offset,w=f?z(n,E):h?U(n,E):$(n,E);for(const r of w.events)this.repository.isSourceDeleted?.(r.agentType,r.sourceSessionKey)||r.title?.trim()&&r.titleSource==="agent_native"&&y.set(`${r.agentType}:${r.sourceSessionKey}`,r);let M,S;if(f){const r=te(w.events,c,s.fusionEpochMs,x,d.mtimeMs,w.nextOffset);M=r.events,S=r.cursor}else if(x&&c.classification==="historical")M=[],S={...c,offset:w.nextOffset,mtimeMs:d.mtimeMs,awaitingUserBoundary:!0};else{const r=se(w.events,c,w.nextOffset,d.mtimeMs);M=r.events,S=r.cursor}S.fileIdentity=I,S.sources=m.sources,t[n]=S,S.classification==="live"&&S.awaitingUserBoundary?this.pendingBoundaryFiles.add(n):this.pendingBoundaryFiles.delete(n);for(const r of M){if(this.repository.isSourceDeleted?.(r.agentType,r.sourceSessionKey))continue;const p=this.tryClaimManagedEcho(r);if(p){i.push(p.event),a.set(p.claimKey,p.suppression??null),p.suppression&&l.set(T(p.suppression.agentType,p.suppression.sourceSessionKey),{window:p.suppression,claimKey:p.claimKey});continue}const b=this.suppressionFor(r,l,a);if(b){i.push({...r,claimSessionId:b.sessionId,localDisposition:"managed_echo_suppressed"});continue}i.push(r)}}const v=[...i].sort((o,n)=>o.ts-n.ts);v.length===0&&this.repository.commitEvents([]);for(let o=0;o<v.length;o+=A)this.repository.commitEvents(v.slice(o,o+A)),o+A<v.length&&await F();for(const[o,n]of a)this.repository.completeManagedClaim?.(o,n??void 0),this.pendingClaims.delete(o),n&&this.suppressionWindows.set(T(n.agentType,n.sourceSessionKey),n);for(const o of y.values())this.repository.observeNativeTitle(o.agentType,o.sourceSessionKey,o.title??"");for(const o of this.repository.listSourceBindings()){if(o.agentType!=="codex")continue;const n=W(o.sourceSessionKey);n&&this.repository.observeNativeTitle("codex",o.sourceSessionKey,n)}for(const o of v)this.observeExternalLifecycle(o);s.files=t,H(s),this.pendingBoundaryFiles.size>0&&this.ensureActiveScanning(!1),this.repository.repairMissingUserBoundaries?.(),await N(this.client).catch(o=>{console.error("[native-fusion] index projection sync failed; retained for retry",o)})}refreshExternalObservations(){const e=Date.now();for(const[s,t]of this.externalObservations.entries()){const i=t.filter(l=>e-l.createdAt<C);if(i.length===0){this.externalObservations.delete(s);continue}this.externalObservations.set(s,i);const a=i[0];e-a.lastPublishedAt<J||(a.lastPublishedAt=e,this.emitExternalActivity(a,a.state))}}tryClaimManagedEcho(e){const s=Date.now();for(const[t,i]of this.pendingClaims.entries()){if(i.sourceAgentType!==e.agentType||e.role!=="user"||!i.sourceSessionKey||i.sourceSessionKey!==e.sourceSessionKey)continue;const a=i.externalRunId?C:Y;if(Math.abs(e.ts-i.createdAt)>a||_(e.payload)!==i.textHash)continue;const l=i.managedEchoPolicy==="suppress_following"?{sessionId:i.sessionId,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,claimSourceEventKey:e.sourceEventKey,startedAt:e.ts,endsAt:Math.max(s,e.ts)+j}:void 0;return{claimKey:t,suppression:l,event:{...e,sourceMode:"managed_echo_claim",claimCanonicalMessageId:i.canonicalMessageId,claimSessionId:i.sessionId,managedEchoPolicy:i.managedEchoPolicy}}}return null}observeExternalLifecycle(e){const s=`${e.agentType}:${e.sourceSessionKey}`,t=this.externalObservations.get(s);if(!t?.length)return;const i=t[0];if(e.claimCanonicalMessageId===i.canonicalMessageId&&i.state==="queued"&&(this.repository.updateMessageDeliveryState(i.sessionId,i.canonicalMessageId,"executing"),i.state="running",i.lastPublishedAt=Date.now(),this.emitExternalActivity(i,"running")),!e.terminal||i.state!=="running")return;const a=e.terminalState??"completed";this.repository.updateMessageDeliveryState(i.sessionId,i.canonicalMessageId,a==="aborted"?"cancelled":a==="failed"?"failed":"completed"),this.emitExternalTerminal(i,a),t.shift(),t.length===0?this.externalObservations.delete(s):(this.externalObservations.set(s,t),t[0].lastPublishedAt=Date.now(),this.emitExternalActivity(t[0],"queued"))}suppressionFor(e,s,t){const i=T(e.agentType,e.sourceSessionKey),a=s.get(i),l=a?.window??this.suppressionWindows.get(i);return!l||e.ts<l.startedAt?null:e.ts>l.endsAt?(this.clearSuppression(i,l,a,s,t),null):e.role==="user"?(e.sourceEventKey===l.claimSourceEventKey||this.clearSuppression(i,l,a,s,t),null):l}clearSuppression(e,s,t,i,a){if(t){i.delete(e),a.set(t.claimKey,null);return}this.repository.removeManagedSuppression?.(s.agentType,s.sourceSessionKey),this.suppressionWindows.delete(e)}}function _(u){return B.createHash("sha256").update(Z(u)).digest("hex")}function T(u,e){return`${u}:${e}`}function se(u,e,s,t){if(!e.awaitingUserBoundary)return{events:u,cursor:{...e,offset:s,mtimeMs:t}};if(u.length===0)return{events:[],cursor:{...e,offset:s,mtimeMs:t,awaitingUserBoundary:!0}};const i=u.findIndex(a=>a.role==="user");return i<0?{events:[],cursor:{...e,mtimeMs:t,awaitingUserBoundary:!0}}:{events:u.slice(i),cursor:{...e,offset:s,mtimeMs:t,awaitingUserBoundary:!1}}}function te(u,e,s,t,i,a){const l={...e.sourceBoundaryEstablished??{}},y=e.eventWatermarkTs??-1,g=new Set(e.eventKeysAtWatermark??[]),v=t?u:u.filter(h=>h.ts>y||h.ts===y&&!g.has(h.sourceEventKey)),o=[];for(const h of v){const f=h.sourceSessionKey;if(l[f]){o.push(h);continue}!(!t||(h.sourceSessionCreatedAtMs??0)>=s)||h.role!=="user"||(l[f]=!0,o.push(h))}const n=u.reduce((h,f)=>Math.max(h,f.ts),y),d=new Set(n===y?e.eventKeysAtWatermark??[]:[]);for(const h of u)h.ts===n&&d.add(h.sourceEventKey);return{events:o,cursor:{...e,offset:a,mtimeMs:i,eventWatermarkTs:n>=0?n:void 0,eventKeysAtWatermark:n>=0?[...d]:void 0,sourceBoundaryEstablished:l}}}export{de as NativeSessionFusionService};
|
|
1
|
+
import K from"node:fs";import B from"node:crypto";import{publishPendingSessionIndexes as N}from"../session/index-publisher.js";import{NativeFusionFileWatcher as P}from"./file-watcher.js";import{NativeFusionLocalRepository as k}from"./local-repository.js";import{listClaudeTranscriptFiles as O,listCodexRolloutFiles as R,listOpenCodeSessionFiles as D,listNativeTranscriptRoots as L,inspectNativeSessionFile as q,lookupCodexThreadName as W,parseClaudeTranscriptChunk as $,parseCodexRolloutChunk as U,parseOpenCodeSessionFile as z}from"./parsers.js";import{loadNativeScannerState as H,saveNativeScannerState as V}from"./state.js";const Q=5*6e4,Y=2*6e4,j=30*6e4,X=2e3,G=32,A=16,J=3e4,C=240*6e4;function Z(u){return u.replace(/\r\n/g,`
|
|
2
|
+
`).trim()}function F(){return new Promise(u=>setImmediate(u))}function ee(u){return u.split(/[\\/]+/).includes(".codex")}class de{client;repository;timer=null;activeTimer=null;scanPromise=null;queuedTargetFiles=new Set;pendingBoundaryFiles=new Set;sourceFiles=new Map;fullScanQueued=!1;watchEnabled=!1;lifecycleRevision=0;watcher=new P(e=>{this.scanNow(e).catch(s=>{console.error("[native-fusion] watched file scan failed",s)})});pendingClaims;suppressionWindows;externalObservations=new Map;externalTerminalHandler=null;constructor(e,s=new k){this.client=e,this.repository=s,this.pendingClaims=new Map((this.repository.listManagedClaims?.()??[]).map(t=>[t.canonicalMessageId,t])),this.suppressionWindows=new Map((this.repository.listManagedSuppressions?.()??[]).map(t=>[w(t.agentType,t.sourceSessionKey),t]))}start(){this.timer||(this.timer=setInterval(()=>{this.scanNow().catch(e=>{console.error("[native-fusion] periodic scan failed",e)})},Q))}stop(){this.lifecycleRevision+=1,this.timer&&clearInterval(this.timer),this.timer=null,this.activeTimer&&clearInterval(this.activeTimer),this.activeTimer=null,this.pendingBoundaryFiles.clear(),this.watchEnabled=!1,this.watcher.stop()}setExternalTerminalHandler(e){this.externalTerminalHandler=e}handleConnected(){const e=++this.lifecycleRevision;this.watchEnabled=!0,this.scanNow().catch(s=>{console.error("[native-fusion] initial scan failed",s)}).finally(()=>{this.lifecycleRevision===e&&this.start()})}registerManagedSend(e){if(!e.canonicalMessageId)return;const s=e.sourceAgentType??e.agentType;if(s!=="codex"&&s!=="claude"&&s!=="opencode")return;const t=e.canonicalMessageId,i=this.pendingClaims.get(t),a=_(e.text);if(i&&(i.sessionId!==e.sessionId||i.sourceAgentType!==s||i.textHash!==a))throw new Error(`managed echo claim identity conflict: ${t}`);const l={sessionId:e.sessionId,agentType:e.agentType,sourceAgentType:s,canonicalMessageId:e.canonicalMessageId,textHash:a,createdAt:i?.createdAt??Date.now(),sourceSessionKey:e.sourceSessionKey??i?.sourceSessionKey??null,managedEchoPolicy:e.managedEchoPolicy??"suppress_following",externalRunId:e.externalRunId??null};if(this.repository.upsertManagedClaim?.(l),this.pendingClaims.set(t,l),e.externalRunId&&e.sourceSessionKey){const y=`${s}:${e.sourceSessionKey}`,g=this.externalObservations.get(y)??[];g.push({sessionId:e.sessionId,sourceSessionKey:e.sourceSessionKey,runId:e.externalRunId,canonicalMessageId:e.canonicalMessageId,state:"queued",createdAt:Date.now(),lastPublishedAt:Date.now()}),this.externalObservations.set(y,g),this.emitExternalActivity(g[g.length-1],"queued"),this.ensureActiveScanning()}}cancelManagedSend(e){this.repository.removeManagedClaim?.(e),this.pendingClaims.delete(e)}noteManagedSourceSession(e,s,t){if(t){this.repository.updateManagedClaimSource?.(e,s,t);for(const i of this.pendingClaims.values())i.sessionId===e&&i.sourceAgentType===s&&(i.sourceSessionKey=t)}}handleSessionDeleted(e){for(const[s,t]of this.pendingClaims.entries())t.sessionId===e&&this.pendingClaims.delete(s);for(const[s,t]of this.suppressionWindows.entries())t.sessionId===e&&this.suppressionWindows.delete(s);for(const[s,t]of this.externalObservations.entries()){const i=t.filter(a=>a.sessionId!==e);i.length===0?this.externalObservations.delete(s):this.externalObservations.set(s,i)}this.repository.purgeSession(e)}ensureActiveScanning(e=!0){this.activeTimer||(this.activeTimer=setInterval(()=>{if(this.externalObservations.size===0&&this.pendingBoundaryFiles.size===0){this.activeTimer&&clearInterval(this.activeTimer),this.activeTimer=null;return}this.scanActiveTargets()},X),e&&this.scanActiveTargets())}scanActiveTargets(){const e=this.activeScanTargets();e===void 0&&this.scanPromise||this.scanNow(e).catch(s=>{console.error("[native-fusion] active observation scan failed",s)})}activeScanTargets(){const e=[...this.externalObservations.keys()];if(!e.some(s=>!this.sourceFiles.has(s)))return[...new Set([...this.pendingBoundaryFiles,...e.flatMap(s=>{const t=this.sourceFiles.get(s);return t?[t]:[]})])]}emitExternalActivity(e,s){this.client.sendAgentEvent({type:"event",event:"agent",id:`external-owner-${e.runId}-${s}-${e.lastPublishedAt}`,payload:{state:s==="queued"?"start":"heartbeat",sessionId:e.sessionId,runId:e.runId,seq:s==="queued"?0:1,runPhase:"thinking",canStop:!1,owner:"external",ownerLabel:"Codex Desktop",lifecycle:s}})}emitExternalTerminal(e,s="completed"){const t=s==="completed"?"final":s==="aborted"?"aborted":"error";this.client.sendAgentEvent({type:"event",event:"agent",id:`external-owner-${e.runId}-${t}`,payload:{state:t,sessionId:e.sessionId,runId:e.runId,seq:2,...t==="error"?{message:"Codex Desktop turn failed"}:{},owner:"external",ownerLabel:"Codex Desktop"}}),this.externalTerminalHandler?.(e.sessionId)}pruneState(){const e=Date.now();for(const[s,t]of this.suppressionWindows.entries())t.endsAt>e||(this.repository.removeManagedSuppression?.(t.agentType,t.sourceSessionKey),this.suppressionWindows.delete(s))}async scanNow(e){if(this.client.getState()==="connected"){if(this.scanPromise){const s=this.scanPromise;if(e)for(const t of e)this.queuedTargetFiles.add(t);else this.fullScanQueued=!0;if(await s,this.fullScanQueued)return this.fullScanQueued=!1,this.queuedTargetFiles.clear(),this.scanNow();if(this.queuedTargetFiles.size>0){const t=[...this.queuedTargetFiles];return this.queuedTargetFiles.clear(),this.scanNow(t)}return}return this.scanPromise=this.runScanLoop(e).finally(()=>{this.scanPromise=null}),this.scanPromise}}async runScanLoop(e){let s=e?[...new Set(e)]:void 0;for(;;){if(await this.runScan(s),this.fullScanQueued){this.fullScanQueued=!1,this.queuedTargetFiles.clear(),s=void 0;continue}if(this.queuedTargetFiles.size>0){s=[...this.queuedTargetFiles],this.queuedTargetFiles.clear();continue}return}}async runScan(e){this.pruneState(),this.refreshExternalObservations();const s=H(),t={...s.files},i=[],a=new Map,l=new Map,y=new Map,g=e?[...e]:[...R(),...O(),...D()];!e&&this.watchEnabled&&this.watcher.refresh(g,L());for(let o=0;o<g.length;o+=1){const n=g[o];if(o>0&&o%G===0&&await F(),!K.existsSync(n)){this.watchEnabled&&this.watcher.untrackFile(n),this.pendingBoundaryFiles.delete(n);for(const[r,p]of this.sourceFiles.entries())p===n&&this.sourceFiles.delete(r);continue}this.watchEnabled&&this.watcher.trackFile(n);const d=K.statSync(n),h=ee(n),f=n.endsWith(".opencode-session.json");let c=s.files[n];const I=`${String(d.dev)}:${String(d.ino)}:${String(d.birthtimeMs)}`;let x=!c;if(c&&!f&&(d.size<c.offset||c.fileIdentity&&c.fileIdentity!==I)&&(c=void 0,x=!0),!!(c&&c.fileIdentity===I&&c.mtimeMs===d.mtimeMs&&c.offset>=d.size&&c.sources?.length)&&c){for(const r of c.sources??[])this.sourceFiles.set(w(r.agentType,r.sourceSessionKey),n);c.classification==="live"&&c.awaitingUserBoundary?this.pendingBoundaryFiles.add(n):this.pendingBoundaryFiles.delete(n);continue}const m=c?.sources?.length&&!f?{status:"ready",createdAtMs:c.sessionCreatedAtMs??d.birthtimeMs,sources:c.sources}:q(n);if(m.status==="pending"){d.birthtimeMs>=s.fusionEpochMs&&this.pendingBoundaryFiles.add(n);continue}for(const r of m.sources)this.sourceFiles.set(w(r.agentType,r.sourceSessionKey),n);if(!f&&m.sources.length>0&&m.sources.every(r=>this.repository.isSourceDeleted?.(r.agentType,r.sourceSessionKey))){t[n]={...c??{daemonInstallationId:s.daemonInstallationId,classification:m.createdAtMs>=s.fusionEpochMs?"live":"historical",awaitingUserBoundary:!0,sessionCreatedAtMs:m.createdAtMs,sourceBoundaryEstablished:{}},offset:d.size,mtimeMs:d.mtimeMs,fileIdentity:I};continue}if(!c)c={daemonInstallationId:s.daemonInstallationId,offset:0,mtimeMs:d.mtimeMs,classification:m.createdAtMs>=s.fusionEpochMs?"live":"historical",awaitingUserBoundary:!0,sessionCreatedAtMs:m.createdAtMs,sourceBoundaryEstablished:{}};else if(f&&c.mtimeMs===d.mtimeMs)continue;const E=f?0:c.offset,T=f?z(n,E):h?U(n,E):$(n,E);for(const r of T.events)this.repository.isSourceDeleted?.(r.agentType,r.sourceSessionKey)||r.title?.trim()&&r.titleSource==="agent_native"&&y.set(`${r.agentType}:${r.sourceSessionKey}`,r);let M,S;if(f){const r=te(T.events,c,s.fusionEpochMs,x,d.mtimeMs,T.nextOffset);M=r.events,S=r.cursor}else if(x&&c.classification==="historical")M=[],S={...c,offset:T.nextOffset,mtimeMs:d.mtimeMs,awaitingUserBoundary:!0};else{const r=se(T.events,c,T.nextOffset,d.mtimeMs);M=r.events,S=r.cursor}S.fileIdentity=I,S.sources=m.sources,t[n]=S,S.classification==="live"&&S.awaitingUserBoundary?this.pendingBoundaryFiles.add(n):this.pendingBoundaryFiles.delete(n);for(const r of M){if(this.repository.isSourceDeleted?.(r.agentType,r.sourceSessionKey))continue;const p=this.tryClaimManagedEcho(r);if(p){i.push(p.event),a.set(p.claimKey,p.suppression??null),p.suppression&&l.set(w(p.suppression.agentType,p.suppression.sourceSessionKey),{window:p.suppression,claimKey:p.claimKey});continue}const b=this.suppressionFor(r,l,a);if(b){i.push({...r,claimSessionId:b.sessionId,localDisposition:"managed_echo_suppressed"});continue}i.push(r)}}const v=[...i].sort((o,n)=>o.ts-n.ts);v.length===0&&this.repository.commitEvents([]);for(let o=0;o<v.length;o+=A)this.repository.commitEvents(v.slice(o,o+A)),o+A<v.length&&await F();for(const[o,n]of a)this.repository.completeManagedClaim?.(o,n??void 0),this.pendingClaims.delete(o),n&&this.suppressionWindows.set(w(n.agentType,n.sourceSessionKey),n);for(const o of y.values())this.repository.observeNativeTitle(o.agentType,o.sourceSessionKey,o.title??"");for(const o of this.repository.listSourceBindings()){if(o.agentType!=="codex")continue;const n=W(o.sourceSessionKey);n&&this.repository.observeNativeTitle("codex",o.sourceSessionKey,n)}for(const o of v)this.observeExternalLifecycle(o);s.files=t,V(s),this.pendingBoundaryFiles.size>0&&this.ensureActiveScanning(!1),this.repository.repairMissingUserBoundaries?.(),await N(this.client).catch(o=>{console.error("[native-fusion] index projection sync failed; retained for retry",o)})}refreshExternalObservations(){const e=Date.now();for(const[s,t]of this.externalObservations.entries()){const i=t.filter(l=>e-l.createdAt<C);if(i.length===0){this.externalObservations.delete(s);continue}this.externalObservations.set(s,i);const a=i[0];e-a.lastPublishedAt<J||(a.lastPublishedAt=e,this.emitExternalActivity(a,a.state))}}tryClaimManagedEcho(e){const s=Date.now();for(const[t,i]of this.pendingClaims.entries()){if(i.sourceAgentType!==e.agentType||e.role!=="user"||!i.sourceSessionKey||i.sourceSessionKey!==e.sourceSessionKey)continue;const a=i.externalRunId?C:Y;if(Math.abs(e.ts-i.createdAt)>a||_(e.payload)!==i.textHash)continue;const l=i.managedEchoPolicy==="suppress_following"?{sessionId:i.sessionId,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,claimSourceEventKey:e.sourceEventKey,startedAt:e.ts,endsAt:Math.max(s,e.ts)+j}:void 0;return{claimKey:t,suppression:l,event:{...e,sourceMode:"managed_echo_claim",claimCanonicalMessageId:i.canonicalMessageId,claimSessionId:i.sessionId,managedEchoPolicy:i.managedEchoPolicy}}}return null}observeExternalLifecycle(e){const s=`${e.agentType}:${e.sourceSessionKey}`,t=this.externalObservations.get(s);if(!t?.length)return;const i=t[0];if(e.claimCanonicalMessageId===i.canonicalMessageId&&i.state==="queued"&&(this.repository.updateMessageDeliveryState(i.sessionId,i.canonicalMessageId,"executing"),i.state="running",i.lastPublishedAt=Date.now(),this.emitExternalActivity(i,"running")),!e.terminal||i.state!=="running")return;const a=e.terminalState??"completed";this.repository.updateMessageDeliveryState(i.sessionId,i.canonicalMessageId,a==="aborted"?"cancelled":a==="failed"?"failed":"completed"),this.emitExternalTerminal(i,a),t.shift(),t.length===0?this.externalObservations.delete(s):(this.externalObservations.set(s,t),t[0].lastPublishedAt=Date.now(),this.emitExternalActivity(t[0],"queued"))}suppressionFor(e,s,t){const i=w(e.agentType,e.sourceSessionKey),a=s.get(i),l=a?.window??this.suppressionWindows.get(i);return!l||e.ts<l.startedAt?null:e.ts>l.endsAt?(this.clearSuppression(i,l,a,s,t),null):e.role==="user"?(e.sourceEventKey===l.claimSourceEventKey||this.clearSuppression(i,l,a,s,t),null):l}clearSuppression(e,s,t,i,a){if(t){i.delete(e),a.set(t.claimKey,null);return}this.repository.removeManagedSuppression?.(s.agentType,s.sourceSessionKey),this.suppressionWindows.delete(e)}}function _(u){return B.createHash("sha256").update(Z(u)).digest("hex")}function w(u,e){return`${u}:${e}`}function se(u,e,s,t){if(!e.awaitingUserBoundary)return{events:u,cursor:{...e,offset:s,mtimeMs:t}};if(u.length===0)return{events:[],cursor:{...e,offset:s,mtimeMs:t,awaitingUserBoundary:!0}};const i=u.findIndex(a=>a.role==="user");return i<0?{events:[],cursor:{...e,mtimeMs:t,awaitingUserBoundary:!0}}:{events:u.slice(i),cursor:{...e,offset:s,mtimeMs:t,awaitingUserBoundary:!1}}}function te(u,e,s,t,i,a){const l={...e.sourceBoundaryEstablished??{}},y=e.eventWatermarkTs??-1,g=new Set(e.eventKeysAtWatermark??[]),v=t?u:u.filter(h=>h.ts>y||h.ts===y&&!g.has(h.sourceEventKey)),o=[];for(const h of v){const f=h.sourceSessionKey;if(l[f]){o.push(h);continue}!(!t||(h.sourceSessionCreatedAtMs??0)>=s)||h.role!=="user"||(l[f]=!0,o.push(h))}const n=u.reduce((h,f)=>Math.max(h,f.ts),y),d=new Set(n===y?e.eventKeysAtWatermark??[]:[]);for(const h of u)h.ts===n&&d.add(h.sourceEventKey);return{events:o,cursor:{...e,offset:a,mtimeMs:i,eventWatermarkTs:n>=0?n:void 0,eventKeysAtWatermark:n>=0?[...d]:void 0,sourceBoundaryEstablished:l}}}export{de as NativeSessionFusionService};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{SESSION_HISTORY_LIMITS as d}from"@shennian/wire";import{getCanonicalSessionStore as b,listSessionRecords as f}from"./store.js";import{recordPersonalSyncLocalMetric as n,setPersonalSyncLocalSubscriptions as a}from"../personal-sync/metrics.js";import{assertPersonalSyncIdentity as l,getPersonalSyncIdentity as c}from"../personal-sync/epoch.js";class m{client;store;hasLocalSession;subscriptions=new Map;unsubscribeStore;constructor(i,s=b(),o=e=>f().some(t=>t.sessionId===e)){this.client=i,this.store=s,this.hasLocalSession=o,this.unsubscribeStore=s.subscribe(e=>this.handleMutation(e))}handleOpen(i){const s=i.params;if(l(s),u(s.sessionId,s.subscriptionId),!this.hasLocalSession(s.sessionId)&&!this.store.getManifest(s.sessionId))throw new Error("history_unavailable");if(this.subscriptions.has(s.subscriptionId))throw new Error("subscription already exists");if([...this.subscriptions.values()].filter(t=>t.sessionId===s.sessionId).length>=d.maxSubscriptionsPerSession)
|
|
1
|
+
import{SESSION_HISTORY_LIMITS as d}from"@shennian/wire";import{getCanonicalSessionStore as b,listSessionRecords as f}from"./store.js";import{recordPersonalSyncLocalMetric as n,setPersonalSyncLocalSubscriptions as a}from"../personal-sync/metrics.js";import{assertPersonalSyncIdentity as l,getPersonalSyncIdentity as c}from"../personal-sync/epoch.js";class m{client;store;hasLocalSession;subscriptions=new Map;unsubscribeStore;constructor(i,s=b(),o=e=>f().some(t=>t.sessionId===e)){this.client=i,this.store=s,this.hasLocalSession=o,this.unsubscribeStore=s.subscribe(e=>this.handleMutation(e))}handleOpen(i){const s=i.params;if(l(s),u(s.sessionId,s.subscriptionId),!this.hasLocalSession(s.sessionId)&&!this.store.getManifest(s.sessionId))throw new Error("history_unavailable");if(this.subscriptions.has(s.subscriptionId))throw new Error("subscription already exists");if([...this.subscriptions.values()].filter(t=>t.sessionId===s.sessionId).length>=d.maxSubscriptionsPerSession){this.client.sendRes({type:"res",id:i.id,ok:!1,error:"subscription_limit_exceeded",rejection:{code:"subscription_limit_exceeded",retryable:!0,requiredAction:"retry"}});return}const e={sessionId:s.sessionId,subscriptionId:s.subscriptionId,snapshotRevision:0,lastSentRevision:0,active:!1,buffered:[]};this.subscriptions.set(s.subscriptionId,e),a(this.subscriptions.size);try{const t=this.store.readPage(s.sessionId,{limit:p(s.limit,d.defaultSnapshotMessages),...s.beforeSequence===void 0?{}:{beforeSequence:s.beforeSequence}});e.snapshotRevision=t.bodyRevision,e.lastSentRevision=t.bodyRevision,this.client.sendRes({type:"res",id:i.id,ok:!0,payload:{...c(this.store.daemonInstallationId),sessionId:s.sessionId,subscriptionId:s.subscriptionId,storageMode:"machine_local",snapshotRevision:t.bodyRevision,firstAvailableSeq:t.firstAvailableSeq,lastAvailableSeq:t.lastAvailableSeq,hasOlder:t.hasOlder,messages:t.entries}}),n("historySnapshots"),n("historyBytesServed",Buffer.byteLength(JSON.stringify(t.entries),"utf8")),e.active=!0;for(const h of e.buffered)this.publishMutation(e,h);e.buffered=[]}catch(t){throw this.subscriptions.delete(s.subscriptionId),a(this.subscriptions.size),t}}handlePage(i){const s=i.params;l(s);const o=this.requireSubscription(s.sessionId,s.subscriptionId);let e;try{e=this.store.readPageAtRevision(s.sessionId,{beforeSequence:s.beforeSequence,limit:p(s.limit,d.defaultPageMessages),revision:o.snapshotRevision})}catch{throw new Error("snapshot_compacted")}this.client.sendRes({type:"res",id:i.id,ok:!0,payload:{...c(this.store.daemonInstallationId),sessionId:s.sessionId,subscriptionId:s.subscriptionId,snapshotRevision:o.snapshotRevision,firstAvailableSeq:e.firstAvailableSeq,hasOlder:e.hasOlder,messages:e.entries}}),n("historyPages"),n("historyBytesServed",Buffer.byteLength(JSON.stringify(e.entries),"utf8"))}handleClose(i){const s=i.params;l(s),u(s.sessionId,s.subscriptionId),this.subscriptions.get(s.subscriptionId)?.sessionId===s.sessionId&&(this.subscriptions.delete(s.subscriptionId),a(this.subscriptions.size)),this.client.sendRes({type:"res",id:i.id,ok:!0,payload:{...c(this.store.daemonInstallationId),sessionId:s.sessionId,subscriptionId:s.subscriptionId,closed:!0}})}closeAll(){this.subscriptions.clear(),a(0),this.unsubscribeStore()}closeSession(i){for(const[s,o]of this.subscriptions.entries())o.sessionId===i&&this.subscriptions.delete(s);a(this.subscriptions.size)}handleMutation(i){for(const s of this.subscriptions.values())s.sessionId===i.sessionId&&(s.active?this.publishMutation(s,i):s.buffered.push(i))}publishMutation(i,s){s.revision<=i.lastSentRevision||(this.client.sendEvent({type:"event",event:"session.body.delta",payload:{...c(this.store.daemonInstallationId),sessionId:i.sessionId,subscriptionId:i.subscriptionId,fromRevision:i.lastSentRevision,toRevision:s.revision,mutations:[{...s,subscriptionId:i.subscriptionId}]}}),n("historyDeltas"),n("historyBytesServed",Buffer.byteLength(JSON.stringify(s),"utf8")),i.lastSentRevision=s.revision)}requireSubscription(i,s){u(i,s);const o=this.subscriptions.get(s);if(!o||o.sessionId!==i)throw new Error("subscription_not_found");return o}}function u(r,i){if(typeof r!="string"||!r||typeof i!="string"||!i)throw new Error("invalid history subscription identity")}function p(r,i){const s=r??i;if(!Number.isSafeInteger(s)||s<1||s>d.maxPageMessages)throw new Error("invalid history limit");return s}export{m as PersonalHistorySubscriptions};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import h from"node:crypto";import{acknowledgeSessionIndexUpdate as R,applyGeneratedSessionTitle as q,claimSessionTitleGenerationCandidates as w,deferSessionTitleGeneration as _,listPendingSessionIndexUpdates as A,listSessionRecords as E,purgeCanonicalSession as k,purgeSessionRecord as D,recordSessionIndexRejection as u}from"./store.js";import{recordPersonalSyncLocalMetric as d}from"../personal-sync/metrics.js";import{SessionSyncRejectedError as T}from"../relay/client.js";import{NativeSourceDeletionStore as $}from"../native-fusion/native-source-deletions.js";import{getDaemonInstallationId as b}from"../personal-sync/identity.js";import{purgeToolDetails as M}from"./tool-detail-store.js";import{purgeQueuedSessionData as N}from"./queue.js";import{getPersonalSyncIdentity as P}from"../personal-sync/epoch.js";const c=new WeakMap,p=new WeakSet;function U(n){const i=n,o=c.get(i);if(o)return p.add(i),o;const e=j(n).finally(()=>{c.get(i)===e&&c.delete(i),p.delete(i)&&I(n)});return c.set(i,e),e}async function j(n){const i=A(),o=[];for(const e of i)try{const s=await n.sendBufferedEvent({type:"event",event:"session.index.updated",id:`session-index-${e.daemonInstallationId}-${e.session.id}-${e.session.indexRevision}`,payload:e},6e4);s&&F(e,s),R(e.session.id,e.session.indexRevision),d("indexAcknowledged")}catch(s){const r=G(e,s,n);r==="retry"&&o.push(s),d("indexFailed"),r==="retry"&&d("indexDirtyRetained")}if(o.length>0)throw new AggregateError(o,"one or more session indexes failed");for(const e of w())try{const s=await n.sendReq({type:"req",id:`session-title-generate-${h.randomUUID()}`,method:"session.title.generate",params:{...P(),...e}},15e3);if(!s.ok){f(n,e.sessionId);continue}const r=s.payload;r?.generated&&q(e.sessionId,r)?p.add(n):r?.generated||f(n,e.sessionId)}catch(s){f(n,e.sessionId),console.warn(`[session-title] generation attempt ended sessionId=${e.sessionId} requestId=${e.requestId}: ${s instanceof Error?s.message:String(s)}`)}}function f(n,i){const o=_(i);if(!o)return;const e=Math.max(0,Date.parse(o)-Date.now());setTimeout(()=>I(n),e).unref?.()}function F(n,i){if(!i)return;const o=i.payload;if(!o||o.daemonInstallationId!==n.daemonInstallationId||o.dataEpoch!==n.dataEpoch||o.sessionId!==n.session.id||o.indexRevision!==n.session.indexRevision)throw new Error("invalid session index acknowledgement identity")}function G(n,i,o){const e=n.session.id,s=n.session.indexRevision;if(i instanceof T){const{rejection:t}=i;if(t.requiredAction==="purge_session"&&t.code==="session_deleted"){const a=E().find(v=>v.sessionId===e),g=Math.max(1,a?.deletionRevision??0,t.authoritativeRevision??0),y=new $({daemonInstallationId:b()});return y.recordSession({sessionId:e,deletionRevision:g}),a?.agentSessionId&&y.record({sessionId:e,deletionRevision:g,agentType:a.agentType,sourceSessionKey:a.agentSessionId}),N(e),M(e),k(e),D(e),"purged"}if(t.retryable&&t.requiredAction==="retry")return d("transientRetries"),u({sessionId:e,indexRevision:s,code:t.code,disposition:"retry"});const x=t.requiredAction==="quarantine_record"||t.code==="room_authority_conflict"||t.code==="invalid_payload"||t.code==="invalid_identity",m=u({sessionId:e,indexRevision:s,code:t.code,disposition:x?"quarantined":"terminal"});return d(m==="quarantined"?"quarantinedRecords":"permanentStops"),(t.requiredAction==="refresh_machine_ownership"||t.requiredAction==="upgrade_client"||t.requiredAction==="reset_epoch"||t.requiredAction==="stop")&&o.disconnect?.(),m}const r=i instanceof Error?i.message:String(i),l=/offline|not connected|disconnect|timed out|timeout|temporar|unavailable|ECONN|ENOTFOUND/i.test(r),S=u({sessionId:e,indexRevision:s,code:l?"transport_unavailable":"unstructured_rejection",disposition:l?"retry":"terminal"});return d(l?"transientRetries":"permanentStops"),S}function I(n){U(n).catch(i=>{console.error("[session-index] sync failed; dirty revisions retained for retry",i)})}export{U as publishPendingSessionIndexes,I as requestSessionIndexPublish};
|
|
@@ -55,6 +55,7 @@ export declare class SessionManager {
|
|
|
55
55
|
sessionId: string;
|
|
56
56
|
}): Promise<void>;
|
|
57
57
|
private publishSessionActivity;
|
|
58
|
+
private handleExternalTerminal;
|
|
58
59
|
private publishManagedActivitySnapshots;
|
|
59
60
|
private reloadCustomAgents;
|
|
60
61
|
handleReq(req: ReqFrame): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{SESSION_SYNC_PROTOCOL_VERSION as u,SESSION_SYNC_DATA_EPOCH as m,isAvailableAgentType as S}from"@shennian/wire";import h from"node:crypto";import{getRegisteredAgents as R,unregisterAgent as k}from"../agents/adapter.js";import{loadConfig as I}from"../config/index.js";import{handleUpgradeStart as A,handleUpgradeStatus as T}from"../commands/upgrade.js";import{handleAgentsRefresh as C,handleModelsRefresh as P}from"./handlers/agents.js";import{handleAgentCapabilitiesList as D}from"./handlers/agent-capabilities.js";import{handleAgentConfigClear as E,handleAgentConfigGet as O,handleAgentConfigTest as M,handleAgentConfigUpsert as x}from"./handlers/agent-config.js";import{handleChatAbort as p,handleChatSend as f}from"./handlers/chat.js";import{handleSessionRefresh as F}from"./handlers/session-refresh.js";import{handleSessionToolDetail as _}from"./handlers/tool-detail.js";import{handleSessionTitleSet as N}from"./handlers/title.js";import{handleSessionMessageStatus as U}from"./handlers/message-status.js";import{handleVoiceSessionBodyWindow as z}from"./handlers/voice-window.js";import{cleanupPendingTransfers as Q,handleFsLs as V,handleFsRead as W,handleFsRename as $,handleFsWrite as j,handleFsTransferAbort as B,handleFsTransferChunk as L,handleFsTransferFinish as Y,handleFsTransferStart as G,handleFsExportMarkdownPdf as H,handleFsExportMarkdownPdfSetup as K,handleFsArchiveZip as X}from"./handlers/fs.js";import{handleRegionProbe as Z,handleRegionSwitch as J,handleUpgradeSetPolicy as q}from"./handlers/control.js";import{ManagerRuntimeService as ee,setManagerRuntimeService as g}from"../manager/runtime.js";import{ChatQueueManager as se}from"./queue.js";import{createAuthorizedFsRoot as te,resolveAuthorizedPath as ie,resolveSessionWorkDir as ne}from"../fs/boundary.js";import"../agents/claude.js";import"../agents/codex.js";import"../agents/workbuddy.js";import"../agents/gemini.js";import"../agents/cursor.js";import"../agents/opencode.js";import"../agents/manager.js";import{registerCustomAgent as ae}from"../agents/custom.js";import{handleManagedRoomBind as re,handleManagedRoomRestart as oe}from"./handlers/room-managed.js";import{getDaemonInstallationId as v,listSessionRecords as b,purgeCanonicalSession as ce,purgeSessionRecord as le}from"./store.js";import{PersonalHistorySubscriptions as he}from"./history-subscriptions.js";import{sweepAttachmentTransfers as de}from"./attachment-transfer-store.js";import{NativeSourceDeletionStore as ue}from"../native-fusion/native-source-deletions.js";import{purgeToolDetails as me}from"./tool-detail-store.js";import{readDeletionSyncRevision as pe,writeDeletionSyncRevision as y}from"./deletion-sync-state.js";import{assertDeletionSyncRunnable as fe,clearDeletionSyncFailure as ge,DeletionSyncRetryWaitError as ve,DeletionSyncTerminalError as be,recordDeletionSyncFailure as d}from"./deletion-sync-state.js";import{NativeSessionCleanupOutbox as ye}from"./native-cleanup-outbox.js";import{assertPersonalSyncIdentity as c}from"../personal-sync/epoch.js";const we=50;function Se(o,e){if(!o||!Number.isSafeInteger(o.baselineRevision)||o.baselineRevision<e||!Number.isSafeInteger(o.latestRevision)||o.latestRevision<o.baselineRevision||!Array.isArray(o.deletions))throw new Error("invalid deletion sync response");let s=o.baselineRevision;for(const t of o.deletions){if(!t?.sessionId||!Number.isSafeInteger(t.deletionRevision)||t.deletionRevision!==s+1||!Array.isArray(t.sources)||Number.isNaN(Date.parse(t.deletedAt))||t.nativeCleanupPolicy!==void 0&&t.nativeCleanupPolicy!=="none"&&t.nativeCleanupPolicy!=="auto")throw new Error("invalid session deletion command");for(const i of t.sources)if(!i?.sourceSessionKey||!S(i.agentType))throw new Error("invalid native source deletion command");s=t.deletionRevision}if(s!==o.latestRevision)throw new Error("invalid deletion sync revision range")}import{resolveSessionWorkDir as cs}from"../fs/boundary.js";class as{client;nativeFusion;cliVersion;upgradePolicyController;managedRoomBinder;sessions=new Map;processedReqIds=new Set;runTextAcc=new Map;pendingTransfers=new Map;managerRuntime;chatQueue;historySubscriptions;nativeCleanupOutbox;activityProbeTimer=null;attachmentCleanupTimer=null;activeRequests=0;constructor(e,s=null,t,i=null,n=null){this.client=e,this.nativeFusion=s,this.cliVersion=t,this.upgradePolicyController=i,this.managedRoomBinder=n,this.managerRuntime=new ee({getRuntime:()=>this.getRuntime(),dispatchReq:r=>this.handleReq(r)}),this.chatQueue=new se({getRuntime:()=>this.getRuntime(),dispatchReq:r=>this.handleReq(r)}),this.historySubscriptions=new he(this.client),this.nativeCleanupOutbox=new ye({logger:console.info}),this.nativeCleanupOutbox.start(),g(this.managerRuntime),this.managerRuntime.start(),this.reloadCustomAgents(),this.activityProbeTimer=setInterval(()=>{this.publishManagedActivitySnapshots().catch(r=>{console.error("[session.activity] managed probe failed",r)})},15e3),this.activityProbeTimer.unref?.(),this.attachmentCleanupTimer=setInterval(()=>{const r=new Set([...b().map(a=>a.workDir),...Array.from(this.pendingTransfers.values()).map(a=>a.rootPath)]);for(const a of r)try{de(this.resolvePath(a))}catch(l){console.error("[attachment.cleanup] failed",l instanceof Error?l.message:String(l))}},6e4),this.attachmentCleanupTimer.unref?.()}getRuntime(){return{client:this.client,pendingTransfers:this.pendingTransfers,processedReqIds:this.processedReqIds,reloadCustomAgents:()=>this.reloadCustomAgents(),resolvePath:e=>this.resolvePath(e),resolveAuthorizedPath:(e,s)=>this.resolveAuthorizedPath(e,s),runTextAcc:this.runTextAcc,sessions:this.sessions,evictIdleSessions:()=>this.evictIdleSessions(),nativeFusion:this.nativeFusion,managerRuntime:this.managerRuntime,chatQueue:this.chatQueue,activityPublisher:{publish:(e,s)=>this.publishSessionActivity(e,s)},upgradePolicyController:this.upgradePolicyController}}getUpgradeIdleState(){const e=[];return[...this.sessions.values()].some(s=>s.currentRunId!==null)&&e.push("active-agent-turn"),this.pendingTransfers.size>0&&e.push("file-transfer"),this.activeRequests>0&&e.push("active-daemon-request"),{idle:e.length===0,reasons:e}}async synchronizeSessionDeletions(){fe();try{await this.synchronizeSessionDeletionsOnce(),ge()}catch(e){if(e instanceof ve||e instanceof be)throw e;const s=e instanceof Error?e.message:String(e),t=/offline|not connected|disconnect|timed out|timeout|temporar|unavailable|ECONN|ENOTFOUND|EIO|EBUSY/i.test(s);throw d({code:t?"transport_unavailable":"invalid_deletion_sync_response",retryable:t}),e}}async synchronizeSessionDeletionsOnce(){const e=v(),s=pe(),t=await this.client.sendReq({type:"req",id:`session-deletions-sync-${h.randomUUID()}`,method:"session.deletions.sync",params:{protocolVersion:u,dataEpoch:m,daemonInstallationId:e,afterRevision:s}});if(!t.ok){const a=t.rejection;throw a?(d({code:a.code,retryable:a.retryable&&a.requiredAction==="retry"}),new Error(a.code)):(d({code:"unstructured_rejection",retryable:!1}),new Error(t.error??"unstructured_rejection"))}const i=t.payload;c(i),Se(i,s);let n=i.baselineRevision;n>s&&y(n);for(const a of i.deletions)a.deletionRevision<=n||(await this.applySessionDeletion(a),n=a.deletionRevision,y(n));if(n!==i.latestRevision)throw new Error(`deletion revision gap: applied ${n}, latest ${i.latestRevision}`);if(n===0)return;const r=await this.client.sendReq({type:"req",id:`session-deletions-ack-${h.randomUUID()}`,method:"session.deletions.ack",params:{protocolVersion:u,dataEpoch:m,daemonInstallationId:e,deletionRevision:n}});if(!r.ok)throw new Error(r.error??"session deletion acknowledgement failed")}async applySessionDeletion(e){const s=new ue({daemonInstallationId:v()});s.recordSession(e);for(const i of e.sources)s.record({...e,...i});const t=this.sessions.get(e.sessionId);if(t){try{await t.adapter.stop()}catch{}this.sessions.delete(e.sessionId)}this.historySubscriptions.closeSession(e.sessionId),this.nativeFusion?.handleSessionDeleted(e.sessionId),this.chatQueue.purgeSession(e.sessionId),me(e.sessionId),ce(e.sessionId),le(e.sessionId),this.runTextAcc.delete(e.sessionId),this.publishSessionActivity(e.sessionId,null),this.nativeCleanupOutbox.enqueue(e),this.nativeCleanupOutbox.kick()}async activateRoomSession(e){const s=b().find(r=>r.sessionId===e.sessionId),t=s?.agentType===e.agentType?s.modelId??void 0:void 0,i={value:null},n=new Proxy(this.client,{get:(r,a)=>{if(a==="sendRes")return w=>{i.value=w};const l=Reflect.get(r,a,r);return typeof l=="function"?l.bind(r):l}});return await f({...this.getRuntime(),client:n},{type:"req",id:e.clientMessageId,method:"chat.send",params:{...e,modelId:t,sessionMode:s?.sessionMode??(s?.agentType==="manager"?"manager":"standard"),managerConfig:s?.managerConfig??null,managerDefaultWorkerAgentType:s?.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:s?.managerDefaultWorkerModelId??null,waitForDispatch:!0,suppressExternalOwnerEcho:!0}},{source:"room_activation"}),{externalOwnerQueued:i.value?.ok===!0&&i.value.payload?.deliveryMode==="external_owner_queue"}}async abortRoomSession(e){const s=new Proxy(this.client,{get:(t,i)=>{if(i==="sendRes")return()=>{};const n=Reflect.get(t,i,t);return typeof n=="function"?n.bind(t):n}});await p({...this.getRuntime(),client:s},{type:"req",id:`room-activation-abort-${h.randomUUID()}`,method:"chat.abort",params:{sessionId:e.sessionId}})}publishSessionActivity(e,s){this.client.sendEvent({type:"event",event:"session.activity",payload:{sessionId:e,activity:s}})}async publishManagedActivitySnapshots(){for(const[e,s]of this.sessions.entries()){if(s.heartbeatTimer||!s.currentRunId||!s.adapter.getStatus)continue;const t=await s.adapter.getStatus().catch(()=>null);if(!t?.active||!t.runPhase)continue;const i=new Date().toISOString();this.publishSessionActivity(e,{sessionId:e,runId:t.runId||s.currentRunId,runPhase:t.runPhase,startedAt:new Date(s.lastActiveAt).toISOString(),updatedAt:i,canStop:t.canStop??!0});const n=s.heartbeatSeq++;this.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:e,runId:t.runId||s.currentRunId,seq:n,runPhase:t.runPhase,canStop:t.canStop??!0},seq:n,id:`agent-status-${t.runId||s.currentRunId}-${Date.now()}`})}}reloadCustomAgents(){for(const s of R())s.startsWith("custom:")&&k(s);const e=I();for(const[s,t]of Object.entries(e.customAgents??{}))ae(s,t)}async handleReq(e){const s=this.getRuntime();this.activeRequests++;try{switch(e.method){case"chat.send":c(e.params),await f(s,e);break;case"chat.enqueue":c(e.params),await this.chatQueue.handleEnqueue(e);break;case"chat.queue.get":c(e.params),await this.chatQueue.handleGet(e);break;case"chat.queue.edit":c(e.params),await this.chatQueue.handleEdit(e);break;case"chat.queue.delete":c(e.params),await this.chatQueue.handleDelete(e);break;case"chat.abort":c(e.params),await p(s,e);break;case"session.refresh":c(e.params),await F(s,e);break;case"session.history.open":this.historySubscriptions.handleOpen(e);break;case"session.history.page":this.historySubscriptions.handlePage(e);break;case"session.history.close":this.historySubscriptions.handleClose(e);break;case"session.history":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"upgrade_required"});break;case"session.message.status":U(s,e);break;case"session.tool.detail":await _(s,e);break;case"voice.session.body.window":z(s,e);break;case"session.title.set":await N(s,e);break;case"room.agent.bind-managed":await re(s,this.managedRoomBinder,e);break;case"room.agent.restart-managed":await oe(s,this.managedRoomBinder,e);break;case"fs.ls":await V(s,e);break;case"fs.read":await W(s,e);break;case"fs.write":await j(s,e);break;case"fs.export.markdown-pdf":await H(s,e);break;case"fs.export.markdown-pdf.setup":await K(s,e);break;case"fs.archive.zip":await X(s,e);break;case"fs.rename":await $(s,e);break;case"fs.transfer.start":await G(s,e);break;case"fs.transfer.chunk":await L(s,e);break;case"fs.transfer.finish":await Y(s,e);break;case"fs.transfer.abort":await B(s,e);break;case"region.probe":await Z(s,e);break;case"region.switch":await J(s,e);break;case"upgrade.start":await A(this.client,e.id,e.params.version,{currentVersion:this.cliVersion,confirmedMajor:e.params.confirmedMajor===!0});break;case"upgrade.status":await T(this.client,e.id,{currentVersion:this.cliVersion});break;case"upgrade.set-policy":await q(s,e);break;case"agents.refresh":await C(s,e);break;case"models.refresh":await P(s,e);break;case"agent.config.get":await O(s,e);break;case"agent.config.upsert":await x(s,e);break;case"agent.config.clear":await E(s,e);break;case"agent.config.test":await M(s,e);break;case"agent.capabilities.list":await D(s,e);break;case"skill.list":case"skill.install":case"skill.doctor":case"skill.setup":case"skill.use":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;case"session.wechat-rpa.channel.get":case"session.wechat-rpa.channel.upsert":case"session.wechat-rpa.channel.sync":case"manager.wechat-rpa.channel.get":case"manager.wechat-rpa.channel.upsert":case"manager.wechat-rpa.channel.sync":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;default:this.client.sendRes({type:"res",id:e.id,ok:!1,error:`Unknown method: ${e.method}`})}}catch(t){this.client.sendRes({type:"res",id:e.id,ok:!1,error:t instanceof Error?t.message:String(t)})}finally{this.activeRequests--}}evictIdleSessions(){if(this.sessions.size<we)return;let e=null;for(const[s,t]of this.sessions)(!e||t.lastActiveAt<e.session.lastActiveAt)&&(e={key:s,session:t});e&&(e.session.heartbeatTimer&&(clearInterval(e.session.heartbeatTimer),e.session.heartbeatTimer=null),e.session.adapter.removeAllListeners(),e.session.adapter.stop().catch(()=>{}),this.sessions.delete(e.key))}resolvePath(e){return ne(e)}resolveAuthorizedPath(e,s){return ie(e,te(s))}async cleanup(){this.nativeCleanupOutbox.stop(),this.historySubscriptions.closeAll(),this.activityProbeTimer&&(clearInterval(this.activityProbeTimer),this.activityProbeTimer=null),this.attachmentCleanupTimer&&(clearInterval(this.attachmentCleanupTimer),this.attachmentCleanupTimer=null);for(const[,e]of this.sessions)e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),await e.adapter.stop().catch(()=>{});this.sessions.clear(),this.runTextAcc.clear(),Q(this.getRuntime()),await this.managerRuntime.stop(),g(null)}}export{as as SessionManager,cs as resolveSessionWorkDir};
|
|
1
|
+
import{SESSION_SYNC_PROTOCOL_VERSION as u,SESSION_SYNC_DATA_EPOCH as m,isAvailableAgentType as S}from"@shennian/wire";import h from"node:crypto";import{getRegisteredAgents as R,unregisterAgent as k}from"../agents/adapter.js";import{loadConfig as I}from"../config/index.js";import{handleUpgradeStart as A,handleUpgradeStatus as T}from"../commands/upgrade.js";import{handleAgentsRefresh as C,handleModelsRefresh as P}from"./handlers/agents.js";import{handleAgentCapabilitiesList as D}from"./handlers/agent-capabilities.js";import{handleAgentConfigClear as E,handleAgentConfigGet as x,handleAgentConfigTest as O,handleAgentConfigUpsert as F}from"./handlers/agent-config.js";import{handleChatAbort as p,handleChatSend as f}from"./handlers/chat.js";import{handleSessionRefresh as M}from"./handlers/session-refresh.js";import{handleSessionToolDetail as _}from"./handlers/tool-detail.js";import{handleSessionTitleSet as N}from"./handlers/title.js";import{handleSessionMessageStatus as U}from"./handlers/message-status.js";import{handleVoiceSessionBodyWindow as Q}from"./handlers/voice-window.js";import{cleanupPendingTransfers as z,handleFsLs as V,handleFsRead as W,handleFsRename as $,handleFsWrite as j,handleFsTransferAbort as B,handleFsTransferChunk as L,handleFsTransferFinish as H,handleFsTransferStart as Y,handleFsExportMarkdownPdf as G,handleFsExportMarkdownPdfSetup as K,handleFsArchiveZip as X}from"./handlers/fs.js";import{handleRegionProbe as Z,handleRegionSwitch as J,handleUpgradeSetPolicy as q}from"./handlers/control.js";import{ManagerRuntimeService as ee,setManagerRuntimeService as g}from"../manager/runtime.js";import{ChatQueueManager as te}from"./queue.js";import{createAuthorizedFsRoot as se,resolveAuthorizedPath as ie,resolveSessionWorkDir as ae}from"../fs/boundary.js";import"../agents/claude.js";import"../agents/codex.js";import"../agents/workbuddy.js";import"../agents/gemini.js";import"../agents/cursor.js";import"../agents/opencode.js";import"../agents/manager.js";import{registerCustomAgent as ne}from"../agents/custom.js";import{handleManagedRoomBind as re,handleManagedRoomRestart as oe}from"./handlers/room-managed.js";import{getDaemonInstallationId as v,listSessionRecords as b,purgeCanonicalSession as ce,purgeSessionRecord as le}from"./store.js";import{PersonalHistorySubscriptions as he}from"./history-subscriptions.js";import{sweepAttachmentTransfers as de}from"./attachment-transfer-store.js";import{NativeSourceDeletionStore as ue}from"../native-fusion/native-source-deletions.js";import{purgeToolDetails as me}from"./tool-detail-store.js";import{readDeletionSyncRevision as pe,writeDeletionSyncRevision as y}from"./deletion-sync-state.js";import{assertDeletionSyncRunnable as fe,clearDeletionSyncFailure as ge,DeletionSyncRetryWaitError as ve,DeletionSyncTerminalError as be,recordDeletionSyncFailure as d}from"./deletion-sync-state.js";import{NativeSessionCleanupOutbox as ye}from"./native-cleanup-outbox.js";import{assertPersonalSyncIdentity as c}from"../personal-sync/epoch.js";const we=50;function Se(o,e){if(!o||!Number.isSafeInteger(o.baselineRevision)||o.baselineRevision<e||!Number.isSafeInteger(o.latestRevision)||o.latestRevision<o.baselineRevision||!Array.isArray(o.deletions))throw new Error("invalid deletion sync response");let t=o.baselineRevision;for(const s of o.deletions){if(!s?.sessionId||!Number.isSafeInteger(s.deletionRevision)||s.deletionRevision!==t+1||!Array.isArray(s.sources)||Number.isNaN(Date.parse(s.deletedAt))||s.nativeCleanupPolicy!==void 0&&s.nativeCleanupPolicy!=="none"&&s.nativeCleanupPolicy!=="auto")throw new Error("invalid session deletion command");for(const i of s.sources)if(!i?.sourceSessionKey||!S(i.agentType))throw new Error("invalid native source deletion command");t=s.deletionRevision}if(t!==o.latestRevision)throw new Error("invalid deletion sync revision range")}import{resolveSessionWorkDir as ct}from"../fs/boundary.js";class nt{client;nativeFusion;cliVersion;upgradePolicyController;managedRoomBinder;sessions=new Map;processedReqIds=new Set;runTextAcc=new Map;pendingTransfers=new Map;managerRuntime;chatQueue;historySubscriptions;nativeCleanupOutbox;activityProbeTimer=null;attachmentCleanupTimer=null;activeRequests=0;constructor(e,t=null,s,i=null,a=null){this.client=e,this.nativeFusion=t,this.cliVersion=s,this.upgradePolicyController=i,this.managedRoomBinder=a,this.managerRuntime=new ee({getRuntime:()=>this.getRuntime(),dispatchReq:n=>this.handleReq(n)}),this.chatQueue=new te({getRuntime:()=>this.getRuntime(),dispatchReq:n=>this.handleReq(n)}),this.historySubscriptions=new he(this.client),this.nativeCleanupOutbox=new ye({logger:console.info}),this.nativeFusion?.setExternalTerminalHandler?.(n=>{this.handleExternalTerminal(n)}),this.nativeCleanupOutbox.start(),g(this.managerRuntime),this.managerRuntime.start(),this.reloadCustomAgents(),this.activityProbeTimer=setInterval(()=>{this.publishManagedActivitySnapshots().catch(n=>{console.error("[session.activity] managed probe failed",n)})},15e3),this.activityProbeTimer.unref?.(),this.attachmentCleanupTimer=setInterval(()=>{const n=new Set([...b().map(r=>r.workDir),...Array.from(this.pendingTransfers.values()).map(r=>r.rootPath)]);for(const r of n)try{de(this.resolvePath(r))}catch(l){console.error("[attachment.cleanup] failed",l instanceof Error?l.message:String(l))}},6e4),this.attachmentCleanupTimer.unref?.()}getRuntime(){return{client:this.client,pendingTransfers:this.pendingTransfers,processedReqIds:this.processedReqIds,reloadCustomAgents:()=>this.reloadCustomAgents(),resolvePath:e=>this.resolvePath(e),resolveAuthorizedPath:(e,t)=>this.resolveAuthorizedPath(e,t),runTextAcc:this.runTextAcc,sessions:this.sessions,evictIdleSessions:()=>this.evictIdleSessions(),nativeFusion:this.nativeFusion,managerRuntime:this.managerRuntime,chatQueue:this.chatQueue,activityPublisher:{publish:(e,t)=>this.publishSessionActivity(e,t)},upgradePolicyController:this.upgradePolicyController}}getUpgradeIdleState(){const e=[];return[...this.sessions.values()].some(t=>t.currentRunId!==null)&&e.push("active-agent-turn"),this.pendingTransfers.size>0&&e.push("file-transfer"),this.activeRequests>0&&e.push("active-daemon-request"),{idle:e.length===0,reasons:e}}async synchronizeSessionDeletions(){fe();try{await this.synchronizeSessionDeletionsOnce(),ge()}catch(e){if(e instanceof ve||e instanceof be)throw e;const t=e instanceof Error?e.message:String(e),s=/offline|not connected|disconnect|timed out|timeout|temporar|unavailable|ECONN|ENOTFOUND|EIO|EBUSY/i.test(t);throw d({code:s?"transport_unavailable":"invalid_deletion_sync_response",retryable:s}),e}}async synchronizeSessionDeletionsOnce(){const e=v(),t=pe(),s=await this.client.sendReq({type:"req",id:`session-deletions-sync-${h.randomUUID()}`,method:"session.deletions.sync",params:{protocolVersion:u,dataEpoch:m,daemonInstallationId:e,afterRevision:t}});if(!s.ok){const r=s.rejection;throw r?(d({code:r.code,retryable:r.retryable&&r.requiredAction==="retry"}),new Error(r.code)):(d({code:"unstructured_rejection",retryable:!1}),new Error(s.error??"unstructured_rejection"))}const i=s.payload;c(i),Se(i,t);let a=i.baselineRevision;a>t&&y(a);for(const r of i.deletions)r.deletionRevision<=a||(await this.applySessionDeletion(r),a=r.deletionRevision,y(a));if(a!==i.latestRevision)throw new Error(`deletion revision gap: applied ${a}, latest ${i.latestRevision}`);if(a===0)return;const n=await this.client.sendReq({type:"req",id:`session-deletions-ack-${h.randomUUID()}`,method:"session.deletions.ack",params:{protocolVersion:u,dataEpoch:m,daemonInstallationId:e,deletionRevision:a}});if(!n.ok)throw new Error(n.error??"session deletion acknowledgement failed")}async applySessionDeletion(e){const t=new ue({daemonInstallationId:v()});t.recordSession(e);for(const i of e.sources)t.record({...e,...i});const s=this.sessions.get(e.sessionId);if(s){try{await s.adapter.stop()}catch{}this.sessions.delete(e.sessionId)}this.historySubscriptions.closeSession(e.sessionId),this.nativeFusion?.handleSessionDeleted(e.sessionId),this.chatQueue.purgeSession(e.sessionId),me(e.sessionId),ce(e.sessionId),le(e.sessionId),this.runTextAcc.delete(e.sessionId),this.publishSessionActivity(e.sessionId,null),this.nativeCleanupOutbox.enqueue(e),this.nativeCleanupOutbox.kick()}async activateRoomSession(e){const t=b().find(n=>n.sessionId===e.sessionId),s=t?.agentType===e.agentType?t.modelId??void 0:void 0,i={value:null},a=new Proxy(this.client,{get:(n,r)=>{if(r==="sendRes")return w=>{i.value=w};const l=Reflect.get(n,r,n);return typeof l=="function"?l.bind(n):l}});return await f({...this.getRuntime(),client:a},{type:"req",id:e.clientMessageId,method:"chat.send",params:{...e,modelId:s,sessionMode:t?.sessionMode??(t?.agentType==="manager"?"manager":"standard"),managerConfig:t?.managerConfig??null,managerDefaultWorkerAgentType:t?.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:t?.managerDefaultWorkerModelId??null,waitForDispatch:!0,suppressExternalOwnerEcho:!0}},{source:"room_activation"}),{externalOwnerQueued:i.value?.ok===!0&&i.value.payload?.deliveryMode==="external_owner_queue"}}async abortRoomSession(e){const t=new Proxy(this.client,{get:(s,i)=>{if(i==="sendRes")return()=>{};const a=Reflect.get(s,i,s);return typeof a=="function"?a.bind(s):a}});await p({...this.getRuntime(),client:t},{type:"req",id:`room-activation-abort-${h.randomUUID()}`,method:"chat.abort",params:{sessionId:e.sessionId}})}publishSessionActivity(e,t){this.client.sendEvent({type:"event",event:"session.activity",payload:{sessionId:e,activity:t}})}async handleExternalTerminal(e){const t=this.sessions.get(e);t&&(this.sessions.delete(e),t.heartbeatTimer&&clearInterval(t.heartbeatTimer),t.adapter.removeAllListeners(),await t.adapter.stop().catch(()=>{})),this.chatQueue.noteTerminal(e)}async publishManagedActivitySnapshots(){for(const[e,t]of this.sessions.entries()){if(t.heartbeatTimer||!t.currentRunId||!t.adapter.getStatus)continue;const s=await t.adapter.getStatus().catch(()=>null);if(!s?.active||!s.runPhase)continue;const i=new Date().toISOString();this.publishSessionActivity(e,{sessionId:e,runId:s.runId||t.currentRunId,runPhase:s.runPhase,startedAt:new Date(t.lastActiveAt).toISOString(),updatedAt:i,canStop:s.canStop??!0});const a=t.heartbeatSeq++;this.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:e,runId:s.runId||t.currentRunId,seq:a,runPhase:s.runPhase,canStop:s.canStop??!0},seq:a,id:`agent-status-${s.runId||t.currentRunId}-${Date.now()}`})}}reloadCustomAgents(){for(const t of R())t.startsWith("custom:")&&k(t);const e=I();for(const[t,s]of Object.entries(e.customAgents??{}))ne(t,s)}async handleReq(e){const t=this.getRuntime();this.activeRequests++;try{switch(e.method){case"chat.send":c(e.params),await f(t,e);break;case"chat.enqueue":c(e.params),await this.chatQueue.handleEnqueue(e);break;case"chat.queue.get":c(e.params),await this.chatQueue.handleGet(e);break;case"chat.queue.edit":c(e.params),await this.chatQueue.handleEdit(e);break;case"chat.queue.delete":c(e.params),await this.chatQueue.handleDelete(e);break;case"chat.abort":c(e.params),await p(t,e);break;case"session.refresh":c(e.params),await M(t,e);break;case"session.history.open":this.historySubscriptions.handleOpen(e);break;case"session.history.page":this.historySubscriptions.handlePage(e);break;case"session.history.close":this.historySubscriptions.handleClose(e);break;case"session.history":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"upgrade_required"});break;case"session.message.status":U(t,e);break;case"session.tool.detail":await _(t,e);break;case"voice.session.body.window":Q(t,e);break;case"session.title.set":await N(t,e);break;case"room.agent.bind-managed":await re(t,this.managedRoomBinder,e);break;case"room.agent.restart-managed":await oe(t,this.managedRoomBinder,e);break;case"fs.ls":await V(t,e);break;case"fs.read":await W(t,e);break;case"fs.write":await j(t,e);break;case"fs.export.markdown-pdf":await G(t,e);break;case"fs.export.markdown-pdf.setup":await K(t,e);break;case"fs.archive.zip":await X(t,e);break;case"fs.rename":await $(t,e);break;case"fs.transfer.start":await Y(t,e);break;case"fs.transfer.chunk":await L(t,e);break;case"fs.transfer.finish":await H(t,e);break;case"fs.transfer.abort":await B(t,e);break;case"region.probe":await Z(t,e);break;case"region.switch":await J(t,e);break;case"upgrade.start":await A(this.client,e.id,e.params.version,{currentVersion:this.cliVersion,confirmedMajor:e.params.confirmedMajor===!0});break;case"upgrade.status":await T(this.client,e.id,{currentVersion:this.cliVersion});break;case"upgrade.set-policy":await q(t,e);break;case"agents.refresh":await C(t,e);break;case"models.refresh":await P(t,e);break;case"agent.config.get":await x(t,e);break;case"agent.config.upsert":await F(t,e);break;case"agent.config.clear":await E(t,e);break;case"agent.config.test":await O(t,e);break;case"agent.capabilities.list":await D(t,e);break;case"skill.list":case"skill.install":case"skill.doctor":case"skill.setup":case"skill.use":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;case"session.wechat-rpa.channel.get":case"session.wechat-rpa.channel.upsert":case"session.wechat-rpa.channel.sync":case"manager.wechat-rpa.channel.get":case"manager.wechat-rpa.channel.upsert":case"manager.wechat-rpa.channel.sync":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;default:this.client.sendRes({type:"res",id:e.id,ok:!1,error:`Unknown method: ${e.method}`})}}catch(s){this.client.sendRes({type:"res",id:e.id,ok:!1,error:s instanceof Error?s.message:String(s)})}finally{this.activeRequests--}}evictIdleSessions(){if(this.sessions.size<we)return;let e=null;for(const[t,s]of this.sessions)(!e||s.lastActiveAt<e.session.lastActiveAt)&&(e={key:t,session:s});e&&(e.session.heartbeatTimer&&(clearInterval(e.session.heartbeatTimer),e.session.heartbeatTimer=null),e.session.adapter.removeAllListeners(),e.session.adapter.stop().catch(()=>{}),this.sessions.delete(e.key))}resolvePath(e){return ae(e)}resolveAuthorizedPath(e,t){return ie(e,se(t))}async cleanup(){this.nativeFusion?.setExternalTerminalHandler?.(null),this.nativeCleanupOutbox.stop(),this.historySubscriptions.closeAll(),this.activityProbeTimer&&(clearInterval(this.activityProbeTimer),this.activityProbeTimer=null),this.attachmentCleanupTimer&&(clearInterval(this.attachmentCleanupTimer),this.attachmentCleanupTimer=null);for(const[,e]of this.sessions)e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),await e.adapter.stop().catch(()=>{});this.sessions.clear(),this.runTextAcc.clear(),z(this.getRuntime()),await this.managerRuntime.stop(),g(null)}}export{nt as SessionManager,ct as resolveSessionWorkDir};
|
|
@@ -50,6 +50,9 @@ export type LocalSessionRecord = {
|
|
|
50
50
|
indexQuarantineCode?: string | null;
|
|
51
51
|
/** Local attempt marker only. The ephemeral titleSeed is never stored. */
|
|
52
52
|
titleGenerationAttemptedAt: string | null;
|
|
53
|
+
/** Local retry schedule for a request that did not produce a title. */
|
|
54
|
+
titleGenerationNextAttemptAt?: string | null;
|
|
55
|
+
titleGenerationAttempts?: number;
|
|
53
56
|
};
|
|
54
57
|
export { getDaemonInstallationId };
|
|
55
58
|
export declare function recordSession(input: {
|
|
@@ -105,6 +108,7 @@ export type SessionTitleGenerationCandidate = {
|
|
|
105
108
|
* written; the bounded seed returned to the caller never enters local JSON.
|
|
106
109
|
*/
|
|
107
110
|
export declare function claimSessionTitleGenerationCandidates(nowMs?: number, graceMs?: number): SessionTitleGenerationCandidate[];
|
|
111
|
+
export declare function deferSessionTitleGeneration(sessionId: string, nowMs?: number): string | null;
|
|
108
112
|
export declare function applyGeneratedSessionTitle(sessionId: string, result: Extract<SessionTitleGenerateResponse, {
|
|
109
113
|
generated: true;
|
|
110
114
|
}>): boolean;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import M from"node:crypto";import d from"node:fs";import x from"node:path";import{SESSION_SYNC_PROTOCOL_VERSION as I,SESSION_SYNC_DATA_EPOCH as A,SESSION_TITLE_SOURCE_PRIORITY as y,sessionPreviewFromPayload as b}from"@shennian/wire";import{PERSONAL_SYNC_V1_ROOT_NAME as k,resolvePersonalSyncV1Path as D}from"../personal-sync/paths.js";import{CanonicalSessionStore as _}from"./canonical-store.js";import{getDaemonInstallationId as c}from"../personal-sync/identity.js";import{NativeSourceDeletionStore as B}from"../native-fusion/native-source-deletions.js";let w=null,C=null;function p(){return D()}function u(){const t=x.join(p(),"canonical");return(!w||C!==t)&&(w=new _(t,c()),C=t),w}function P(){return x.join(p(),"session-index.json")}function g(){const t=P();if(!d.existsSync(t))return{};const e=JSON.parse(d.readFileSync(t,"utf8"));if(!e||typeof e!="object"||Array.isArray(e))throw new Error(`invalid personal session index: ${t}`);const n=c();for(const s of Object.values(e))if(s.daemonInstallationId!==n)throw new Error(`personal session index belongs to another daemon installation: ${t}`);return e}function S(t){V(P(),t)}function O(t){return b(t.payload,120)}function m(t){if(new B({rootDir:p(),daemonInstallationId:c()}).hasSession(t))throw new Error(`session_deleted:${t}`)}function T(t){m(t.sessionId),c();const e=g(),n=e[t.sessionId],s=new Date().toISOString(),i=u().getManifest(t.sessionId)?.bodyRevision??n?.bodyRevision??0,o=u().getManifest(t.sessionId),l=q(n,t),a=t.attentionId?.trim()||null,r=Array.from(new Set([...n?.seenAttentionIds??[],...n?.lastAttentionId?[n.lastAttentionId]:[]])),v=!!(a&&!r.includes(a)),f=t.lastMessagePreview===null?{preview:null,messageId:null,at:null}:t.lastMessagePreview!==void 0&&t.lastMessagePreviewMessageId&&t.lastMessagePreviewAt?{preview:t.lastMessagePreview,messageId:t.lastMessagePreviewMessageId,at:t.lastMessagePreviewAt}:{preview:n?.lastMessagePreview??null,messageId:n?.lastMessagePreviewMessageId??null,at:n?.lastMessagePreviewAt??null},R={daemonInstallationId:c(),sessionId:t.sessionId,agentType:t.agentType,sessionMode:t.sessionMode??n?.sessionMode,managerConfig:t.managerConfig??n?.managerConfig??null,workDir:t.workDir,agentSessionId:t.agentSessionId??n?.agentSessionId??null,modelId:t.modelId??n?.modelId??null,managerDefaultWorkerAgentType:t.managerDefaultWorkerAgentType??n?.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:t.managerDefaultWorkerModelId??n?.managerDefaultWorkerModelId??null,status:t.status??n?.status??"active",createdAt:n?.createdAt??s,updatedAt:s,lastActivityAt:t.lastActivityAt??n?.lastActivityAt??s,lastMessagePreview:f.preview,lastMessagePreviewMessageId:f.messageId,lastMessagePreviewAt:f.at,localReady:n?.localReady===!0||t.localReady===!0,firstUserMessageId:n?.firstUserMessageId??t.firstUserMessageId??null,firstUserBodyRevision:n?.firstUserBodyRevision??t.firstUserBodyRevision??null,bodyRevision:i,indexRevision:(n?.indexRevision??0)+1,...l,firstAvailableSeq:o?.firstAvailableSeq??n?.firstAvailableSeq??null,visibleMessageCount:o?.visibleMessageCount??n?.visibleMessageCount??0,lastVisibleMessageId:o?.lastVisibleMessageId??n?.lastVisibleMessageId??null,attentionCount:(n?.attentionCount??0)+(v?1:0),lastAttentionId:v?a:n?.lastAttentionId??null,seenAttentionIds:v&&a?[...r,a]:r,lastBodyCommittedAt:o?.lastBodyCommittedAt??n?.lastBodyCommittedAt??null,deletionRevision:n?.deletionRevision??0,deletedAt:t.deletedAt??n?.deletedAt??null,syncedIndexRevision:n?.syncedIndexRevision??0,indexSyncState:"pending",indexSyncStateRevision:(n?.indexRevision??0)+1,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null,indexQuarantineCode:n?.indexQuarantineCode??null,titleGenerationAttemptedAt:n?.titleGenerationAttemptedAt??null};return e[t.sessionId]=R,S(e),R}function U(){return Object.values(g())}function H(t){if(!t)throw new Error("sessionId is required");const e=g();e[t]&&(delete e[t],S(e))}function z(t){u().purgeSession(t)}function K(){const t=c();return L(),U().filter(e=>{const n=e.indexSyncStateRevision===e.indexRevision;return e.indexQuarantineCode||n&&(e.indexSyncState==="quarantined"||e.indexSyncState==="terminal")||n&&e.indexSyncNextAttemptAt&&Date.parse(e.indexSyncNextAttemptAt)>Date.now()?!1:e.indexRevision>e.syncedIndexRevision&&e.localReady&&!!e.firstUserMessageId&&e.firstUserBodyRevision!=null}).sort((e,n)=>e.indexRevision-n.indexRevision).map(e=>{const n=!!(e.lastMessagePreview&&e.lastMessagePreviewMessageId&&e.lastMessagePreviewAt);return{protocolVersion:I,dataEpoch:A,daemonInstallationId:t,session:{id:e.sessionId,agentType:e.agentType,sessionMode:e.sessionMode,managerConfig:e.managerConfig,agentSessionId:e.agentSessionId??null,modelId:e.modelId??null,managerDefaultWorkerAgentType:e.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:e.managerDefaultWorkerModelId??null,title:e.title,titleSource:e.titleSource,titleRevision:e.titleRevision,titleLockedByUser:e.titleLockedByUser,titleOriginEventId:e.titleOriginEventId,status:e.status??"active",workDir:e.workDir,lastMessagePreview:n?e.lastMessagePreview:null,lastMessagePreviewMessageId:n?e.lastMessagePreviewMessageId:null,lastMessagePreviewAt:n?e.lastMessagePreviewAt:null,bodyStorageMode:"machine_local",localReady:!0,firstUserBoundary:{role:"user",messageId:e.firstUserMessageId,bodyRevision:e.firstUserBodyRevision},bodyRevision:e.bodyRevision,indexRevision:e.indexRevision,firstAvailableSeq:e.firstAvailableSeq,visibleMessageCount:e.visibleMessageCount,lastVisibleMessageId:e.lastVisibleMessageId,attentionCount:e.attentionCount??0,lastAttentionId:e.lastAttentionId??null,lastBodyCommittedAt:e.lastBodyCommittedAt,deletionRevision:e.deletionRevision,lastActivityAt:e.lastActivityAt,createdAt:e.createdAt,updatedAt:e.updatedAt,deletedAt:e.deletedAt}}})}function X(t,e){const n=g(),s=n[t];if(!(!s||e<=s.syncedIndexRevision)){if(e>s.indexRevision)throw new Error(`cannot acknowledge future index revision ${e} for ${t}`);n[t]={...s,syncedIndexRevision:e,indexSyncState:"applied",indexSyncStateRevision:e,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null},S(n)}}function Z(){const t=g();let e=0;const n=new Date().toISOString();for(const[s,i]of Object.entries(t)){if(i.deletedAt||i.indexQuarantineCode||!i.localReady)continue;const o=i.indexRevision+1;t[s]={...i,indexRevision:o,indexSyncState:"pending",indexSyncStateRevision:o,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null,updatedAt:n},e+=1}return e>0&&S(t),e}function ee(t){const e=g(),n=e[t.sessionId];if(!n||n.indexRevision!==t.indexRevision)return"terminal";const i=(n.indexSyncStateRevision===t.indexRevision?n.indexSyncAttempts??0:0)+1;let o=t.disposition;o==="retry"&&i>=(t.maxAttempts??8)&&(o="terminal");const l=t.now??Date.now(),a=Math.min(5e3*2**Math.max(0,i-1),360*60*1e3),r=Math.round(a*.2*(Math.random()*2-1));return e[t.sessionId]={...n,indexSyncState:o==="retry"?"pending":o,indexSyncStateRevision:t.indexRevision,indexSyncAttempts:i,indexSyncNextAttemptAt:o==="retry"?new Date(l+a+r).toISOString():null,indexSyncErrorCode:t.code,indexQuarantineCode:o==="quarantined"?t.code:n.indexQuarantineCode??null},S(e),o}const N=2*6e4;function te(t=Date.now(),e=N){const n=g(),s=[],i=new Date(t).toISOString();for(const o of Object.values(n)){if(o.titleSource!=="first_user_fallback"||o.titleLockedByUser||o.titleGenerationAttemptedAt||!o.title.trim())continue;const l=o.status==="completed"||o.status==="failed",a=o.visibleMessageCount>=2;let r=null;if(o.agentType==="claude"&&a?r="agent_native_unavailable":l?r="terminal":a&&t-Date.parse(o.createdAt)>=e&&(r="grace_elapsed"),!r)continue;const v=b(o.title,240);if(!v)continue;const f=M.randomUUID();n[o.sessionId]={...o,titleGenerationAttemptedAt:i},s.push({sessionId:o.sessionId,requestId:f,titleSeed:v,baseTitleRevision:o.titleRevision,eligibility:r})}return s.length>0&&S(n),s}function ne(t,e){const s=g()[t];return!s||s.titleLockedByUser||y[s.titleSource]>=y.shennian_generated||e.titleRevision<=s.titleRevision?!1:(T({sessionId:t,agentType:s.agentType,sessionMode:s.sessionMode,managerConfig:s.managerConfig,workDir:s.workDir,agentSessionId:s.agentSessionId,modelId:s.modelId,status:s.status,title:e.title,titleSource:"shennian_generated",titleRevision:e.titleRevision,titleLockedByUser:!1,titleOriginEventId:e.titleOriginEventId}),!0)}function ie(t,e,n){m(t),c();const s=u().append(t,e,n);return E(t,e,s.bodyRevision),s.bodyRevision}function se(t,e,n){m(t),c();const s=u().upsert(t,e,n);return E(t,e,s.bodyRevision),s.bodyRevision}function oe(t,e){const n=u().getMessageState(t,e);return!n||n.tombstoned||!n.message||!n.deliveryState?{protocolVersion:I,dataEpoch:A,daemonInstallationId:c(),state:"not_found",sessionId:t,clientMessageId:e}:{protocolVersion:I,dataEpoch:A,daemonInstallationId:c(),state:n.deliveryState,sessionId:t,clientMessageId:e,messageId:n.message.id,deliveryState:n.deliveryState,bodyRevision:n.lastRevision,committedAt:new Date(n.message.ts).toISOString()}}function ae(t,e){m(t),c();const n=u().tombstone(t,e),s=g(),i=s[t];if(i){const o=e===i.lastMessagePreviewMessageId?h(t):{preview:i.lastMessagePreview??null,messageId:i.lastMessagePreviewMessageId??null,at:i.lastMessagePreviewAt??null};s[t]={...i,bodyRevision:n.bodyRevision,indexRevision:i.indexRevision+1,indexSyncState:"pending",indexSyncStateRevision:i.indexRevision+1,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null,updatedAt:new Date().toISOString(),lastMessagePreview:o.preview,lastMessagePreviewMessageId:o.messageId,lastMessagePreviewAt:o.at},S(s)}return n.bodyRevision}function E(t,e,n){const s=g(),i=s[t];if(!i)return;const o=O(e),l=o?{lastMessagePreview:o,lastMessagePreviewMessageId:e.id,lastMessagePreviewAt:new Date(e.ts).toISOString()}:null,a=u().getManifest(t),r=e.role==="user"&&o&&!i.title?{title:o,titleSource:"first_user_fallback",titleRevision:i.titleRevision+1,titleLockedByUser:!1,titleOriginEventId:e.id}:null,v=e.role==="user"&&!i.localReady?{localReady:!0,firstUserMessageId:e.id,firstUserBodyRevision:n}:null;s[t]={...i,...r??{},...v??{},bodyRevision:n,indexRevision:i.indexRevision+1,indexSyncState:"pending",indexSyncStateRevision:i.indexRevision+1,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null,updatedAt:new Date().toISOString(),lastActivityAt:new Date(e.ts).toISOString(),...l??{},firstAvailableSeq:a?.firstAvailableSeq??i.firstAvailableSeq,visibleMessageCount:a?.visibleMessageCount??i.visibleMessageCount,lastVisibleMessageId:a?.lastVisibleMessageId??i.lastVisibleMessageId,attentionCount:i.attentionCount??0,lastAttentionId:i.lastAttentionId??null,lastBodyCommittedAt:a?.lastBodyCommittedAt??i.lastBodyCommittedAt},S(s)}function h(t){let e;do{const n=u().readPage(t,{limit:500,beforeSequence:e});for(let s=n.entries.length-1;s>=0;s-=1){const i=n.entries[s],o=O(i.message);if(o)return{preview:o,messageId:i.message.id,at:new Date(i.message.ts).toISOString()}}if(!n.hasOlder||n.entries.length===0)break;e=n.entries[0].sequence}while(e!==void 0);return{preview:null,messageId:null,at:null}}function L(){const t=g();let e=!1;const n=new Date().toISOString();for(const[s,i]of Object.entries(t)){if(Object.prototype.hasOwnProperty.call(i,"lastMessagePreviewMessageId")&&Object.prototype.hasOwnProperty.call(i,"lastMessagePreviewAt"))continue;const o=h(s);t[s]={...i,lastMessagePreview:o.preview,lastMessagePreviewMessageId:o.messageId,lastMessagePreviewAt:o.at,indexRevision:i.indexRevision+1,indexSyncState:"pending",indexSyncStateRevision:i.indexRevision+1,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null,updatedAt:n},e=!0}e&&S(t)}function q(t,e){const n={title:t?.title??"",titleSource:t?.titleSource??"first_user_fallback",titleRevision:t?.titleRevision??0,titleLockedByUser:t?.titleLockedByUser??!1,titleOriginEventId:t?.titleOriginEventId??null},s=e.title?.trim()??"";if(!s)return n;if(!e.titleSource)throw new Error("titleSource is required with title");const i=e.titleSource==="shennian_manual";if(e.titleLockedByUser!==void 0&&e.titleLockedByUser!==i)throw new Error("titleLockedByUser conflicts with titleSource");const o=s===n.title&&e.titleSource===n.titleSource,l=e.titleRevision??n.titleRevision+(o?0:1);if(!Number.isSafeInteger(l)||l<0)throw new Error("invalid titleRevision");const a=y[e.titleSource],r=y[n.titleSource];return a<r||l<n.titleRevision||o&&l===n.titleRevision||l===n.titleRevision&&!o?n:{title:s,titleSource:e.titleSource,titleRevision:l,titleLockedByUser:i,titleOriginEventId:e.titleOriginEventId??n.titleOriginEventId}}function le(t,e){const n=e?.limit&&e.limit>0?Math.min(e.limit,500):500;let i=u().readRecent(t,n).messages;return e?.before!==void 0&&(i=i.filter(o=>o.ts<e.before)),[...i].sort((o,l)=>l.ts-o.ts)}function re(){return u().listSessionIds()}function de(){return u()}function V(t,e){const n=x.dirname(t);d.mkdirSync(n,{recursive:!0,mode:448});const s=`${t}.tmp-${process.pid}-${M.randomBytes(6).toString("hex")}`;let i;try{if(i=d.openSync(s,"wx",384),d.writeFileSync(i,`${JSON.stringify(e,null,2)}
|
|
2
|
-
`,"utf8"),d.fsyncSync(
|
|
1
|
+
import M from"node:crypto";import d from"node:fs";import A from"node:path";import{SESSION_SYNC_PROTOCOL_VERSION as x,SESSION_SYNC_DATA_EPOCH as I,SESSION_TITLE_SOURCE_PRIORITY as m,sessionPreviewFromPayload as b}from"@shennian/wire";import{PERSONAL_SYNC_V1_ROOT_NAME as D,resolvePersonalSyncV1Path as E}from"../personal-sync/paths.js";import{CanonicalSessionStore as _}from"./canonical-store.js";import{getDaemonInstallationId as c}from"../personal-sync/identity.js";import{NativeSourceDeletionStore as T}from"../native-fusion/native-source-deletions.js";let p=null,C=null;function w(){return E()}function u(){const t=A.join(w(),"canonical");return(!p||C!==t)&&(p=new _(t,c()),C=t),p}function P(){return A.join(w(),"session-index.json")}function g(){const t=P();if(!d.existsSync(t))return{};const e=JSON.parse(d.readFileSync(t,"utf8"));if(!e||typeof e!="object"||Array.isArray(e))throw new Error(`invalid personal session index: ${t}`);const n=c();for(const i of Object.values(e))if(i.daemonInstallationId!==n)throw new Error(`personal session index belongs to another daemon installation: ${t}`);return e}function S(t){q(P(),t)}function O(t){return b(t.payload,120)}function y(t){if(new T({rootDir:w(),daemonInstallationId:c()}).hasSession(t))throw new Error(`session_deleted:${t}`)}function B(t){y(t.sessionId),c();const e=g(),n=e[t.sessionId],i=new Date().toISOString(),o=u().getManifest(t.sessionId)?.bodyRevision??n?.bodyRevision??0,s=u().getManifest(t.sessionId),a=L(n,t),l=t.attentionId?.trim()||null,r=Array.from(new Set([...n?.seenAttentionIds??[],...n?.lastAttentionId?[n.lastAttentionId]:[]])),f=!!(l&&!r.includes(l)),v=t.lastMessagePreview===null?{preview:null,messageId:null,at:null}:t.lastMessagePreview!==void 0&&t.lastMessagePreviewMessageId&&t.lastMessagePreviewAt?{preview:t.lastMessagePreview,messageId:t.lastMessagePreviewMessageId,at:t.lastMessagePreviewAt}:{preview:n?.lastMessagePreview??null,messageId:n?.lastMessagePreviewMessageId??null,at:n?.lastMessagePreviewAt??null},R={daemonInstallationId:c(),sessionId:t.sessionId,agentType:t.agentType,sessionMode:t.sessionMode??n?.sessionMode,managerConfig:t.managerConfig??n?.managerConfig??null,workDir:t.workDir,agentSessionId:t.agentSessionId??n?.agentSessionId??null,modelId:t.modelId??n?.modelId??null,managerDefaultWorkerAgentType:t.managerDefaultWorkerAgentType??n?.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:t.managerDefaultWorkerModelId??n?.managerDefaultWorkerModelId??null,status:t.status??n?.status??"active",createdAt:n?.createdAt??i,updatedAt:i,lastActivityAt:t.lastActivityAt??n?.lastActivityAt??i,lastMessagePreview:v.preview,lastMessagePreviewMessageId:v.messageId,lastMessagePreviewAt:v.at,localReady:n?.localReady===!0||t.localReady===!0,firstUserMessageId:n?.firstUserMessageId??t.firstUserMessageId??null,firstUserBodyRevision:n?.firstUserBodyRevision??t.firstUserBodyRevision??null,bodyRevision:o,indexRevision:(n?.indexRevision??0)+1,...a,firstAvailableSeq:s?.firstAvailableSeq??n?.firstAvailableSeq??null,visibleMessageCount:s?.visibleMessageCount??n?.visibleMessageCount??0,lastVisibleMessageId:s?.lastVisibleMessageId??n?.lastVisibleMessageId??null,attentionCount:(n?.attentionCount??0)+(f?1:0),lastAttentionId:f?l:n?.lastAttentionId??null,seenAttentionIds:f&&l?[...r,l]:r,lastBodyCommittedAt:s?.lastBodyCommittedAt??n?.lastBodyCommittedAt??null,deletionRevision:n?.deletionRevision??0,deletedAt:t.deletedAt??n?.deletedAt??null,syncedIndexRevision:n?.syncedIndexRevision??0,indexSyncState:"pending",indexSyncStateRevision:(n?.indexRevision??0)+1,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null,indexQuarantineCode:null,titleGenerationAttemptedAt:n?.titleGenerationAttemptedAt&&n.titleGenerationNextAttemptAt!==void 0?n.titleGenerationAttemptedAt:null,titleGenerationNextAttemptAt:n?.titleGenerationNextAttemptAt??null,titleGenerationAttempts:n?.titleGenerationAttempts??0};return e[t.sessionId]=R,S(e),R}function U(){return Object.values(g())}function H(t){if(!t)throw new Error("sessionId is required");const e=g();e[t]&&(delete e[t],S(e))}function z(t){u().purgeSession(t)}function K(){const t=c();return G(),U().filter(e=>{const n=e.indexSyncStateRevision===e.indexRevision;return e.indexQuarantineCode||n&&(e.indexSyncState==="quarantined"||e.indexSyncState==="terminal")||n&&e.indexSyncNextAttemptAt&&Date.parse(e.indexSyncNextAttemptAt)>Date.now()?!1:e.indexRevision>e.syncedIndexRevision&&e.localReady&&!!e.firstUserMessageId&&e.firstUserBodyRevision!=null}).sort((e,n)=>e.indexRevision-n.indexRevision).map(e=>{const n=!!(e.lastMessagePreview&&e.lastMessagePreviewMessageId&&e.lastMessagePreviewAt);return{protocolVersion:x,dataEpoch:I,daemonInstallationId:t,session:{id:e.sessionId,agentType:e.agentType,sessionMode:e.sessionMode,managerConfig:e.managerConfig,agentSessionId:e.agentSessionId??null,modelId:e.modelId??null,managerDefaultWorkerAgentType:e.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:e.managerDefaultWorkerModelId??null,title:e.title,titleSource:e.titleSource,titleRevision:e.titleRevision,titleLockedByUser:e.titleLockedByUser,titleOriginEventId:e.titleOriginEventId,status:e.status??"active",workDir:e.workDir,lastMessagePreview:n?e.lastMessagePreview:null,lastMessagePreviewMessageId:n?e.lastMessagePreviewMessageId:null,lastMessagePreviewAt:n?e.lastMessagePreviewAt:null,bodyStorageMode:"machine_local",localReady:!0,firstUserBoundary:{role:"user",messageId:e.firstUserMessageId,bodyRevision:e.firstUserBodyRevision},bodyRevision:e.bodyRevision,indexRevision:e.indexRevision,firstAvailableSeq:e.firstAvailableSeq,visibleMessageCount:e.visibleMessageCount,lastVisibleMessageId:e.lastVisibleMessageId,attentionCount:e.attentionCount??0,lastAttentionId:e.lastAttentionId??null,lastBodyCommittedAt:e.lastBodyCommittedAt,deletionRevision:e.deletionRevision,lastActivityAt:e.lastActivityAt,createdAt:e.createdAt,updatedAt:e.updatedAt,deletedAt:e.deletedAt}}})}function X(t,e){const n=g(),i=n[t];if(!(!i||e<=i.syncedIndexRevision)){if(e>i.indexRevision)throw new Error(`cannot acknowledge future index revision ${e} for ${t}`);n[t]={...i,syncedIndexRevision:e,indexSyncState:"applied",indexSyncStateRevision:e,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null},S(n)}}function Z(){const t=g();let e=0;const n=new Date().toISOString();for(const[i,o]of Object.entries(t)){if(o.deletedAt||o.indexQuarantineCode||!o.localReady)continue;const s=o.indexRevision+1;t[i]={...o,indexRevision:s,indexSyncState:"pending",indexSyncStateRevision:s,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null,updatedAt:n},e+=1}return e>0&&S(t),e}function ee(t){const e=g(),n=e[t.sessionId];if(!n||n.indexRevision!==t.indexRevision)return"terminal";const o=(n.indexSyncStateRevision===t.indexRevision?n.indexSyncAttempts??0:0)+1;let s=t.disposition;s==="retry"&&o>=(t.maxAttempts??8)&&(s="terminal");const a=t.now??Date.now(),l=Math.min(5e3*2**Math.max(0,o-1),360*60*1e3),r=Math.round(l*.2*(Math.random()*2-1));return e[t.sessionId]={...n,indexSyncState:s==="retry"?"pending":s,indexSyncStateRevision:t.indexRevision,indexSyncAttempts:o,indexSyncNextAttemptAt:s==="retry"?new Date(a+l+r).toISOString():null,indexSyncErrorCode:t.code,indexQuarantineCode:s==="quarantined"?t.code:n.indexQuarantineCode??null},S(e),s}const N=2*6e4;function te(t=Date.now(),e=N){const n=g(),i=[],o=new Date(t).toISOString();for(const s of Object.values(n)){if(s.titleSource!=="first_user_fallback"||s.titleLockedByUser||s.titleGenerationAttemptedAt||!s.title.trim()||s.syncedIndexRevision<s.indexRevision||s.titleGenerationNextAttemptAt&&Date.parse(s.titleGenerationNextAttemptAt)>t)continue;const a=s.status==="completed"||s.status==="failed",l=s.visibleMessageCount>=2;let r=null;if(s.agentType==="claude"&&l?r="agent_native_unavailable":a?r="terminal":l&&t-Date.parse(s.createdAt)>=e&&(r="grace_elapsed"),!r)continue;const f=b(s.title,240);if(!f)continue;const v=M.randomUUID();n[s.sessionId]={...s,titleGenerationAttemptedAt:o,titleGenerationNextAttemptAt:null,titleGenerationAttempts:(s.titleGenerationAttempts??0)+1},i.push({sessionId:s.sessionId,requestId:v,titleSeed:f,baseTitleRevision:s.titleRevision,eligibility:r})}return i.length>0&&S(n),i}function ne(t,e=Date.now()){const n=g(),i=n[t];if(!i?.titleGenerationAttemptedAt||i.titleSource!=="first_user_fallback")return null;const o=Math.max(1,i.titleGenerationAttempts??1),s=Math.min(10*6e4,3e4*2**Math.min(o-1,5)),a=new Date(e+s).toISOString();return n[t]={...i,titleGenerationAttemptedAt:null,titleGenerationNextAttemptAt:a},S(n),a}function ie(t,e){const i=g()[t];return!i||i.titleLockedByUser||m[i.titleSource]>=m.shennian_generated||e.titleRevision<=i.titleRevision?!1:(B({sessionId:t,agentType:i.agentType,sessionMode:i.sessionMode,managerConfig:i.managerConfig,workDir:i.workDir,agentSessionId:i.agentSessionId,modelId:i.modelId,status:i.status,title:e.title,titleSource:"shennian_generated",titleRevision:e.titleRevision,titleLockedByUser:!1,titleOriginEventId:e.titleOriginEventId}),!0)}function se(t,e,n){y(t),c();const i=u().append(t,e,n);return h(t,e,i.bodyRevision),i.bodyRevision}function oe(t,e,n){y(t),c();const i=u().upsert(t,e,n);return h(t,e,i.bodyRevision),i.bodyRevision}function ae(t,e){const n=u().getMessageState(t,e);return!n||n.tombstoned||!n.message||!n.deliveryState?{protocolVersion:x,dataEpoch:I,daemonInstallationId:c(),state:"not_found",sessionId:t,clientMessageId:e}:{protocolVersion:x,dataEpoch:I,daemonInstallationId:c(),state:n.deliveryState,sessionId:t,clientMessageId:e,messageId:n.message.id,deliveryState:n.deliveryState,bodyRevision:n.lastRevision,committedAt:new Date(n.message.ts).toISOString()}}function le(t,e){y(t),c();const n=u().tombstone(t,e),i=g(),o=i[t];if(o){const s=e===o.lastMessagePreviewMessageId?k(t):{preview:o.lastMessagePreview??null,messageId:o.lastMessagePreviewMessageId??null,at:o.lastMessagePreviewAt??null};i[t]={...o,bodyRevision:n.bodyRevision,indexRevision:o.indexRevision+1,indexSyncState:"pending",indexSyncStateRevision:o.indexRevision+1,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null,updatedAt:new Date().toISOString(),lastMessagePreview:s.preview,lastMessagePreviewMessageId:s.messageId,lastMessagePreviewAt:s.at},S(i)}return n.bodyRevision}function h(t,e,n){const i=g(),o=i[t];if(!o)return;const s=O(e),a=s?{lastMessagePreview:s,lastMessagePreviewMessageId:e.id,lastMessagePreviewAt:new Date(e.ts).toISOString()}:null,l=u().getManifest(t),r=e.role==="user"&&s&&!o.title?{title:s,titleSource:"first_user_fallback",titleRevision:o.titleRevision+1,titleLockedByUser:!1,titleOriginEventId:e.id}:null,f=e.role==="user"&&!o.localReady?{localReady:!0,firstUserMessageId:e.id,firstUserBodyRevision:n}:null;i[t]={...o,...r??{},...f??{},bodyRevision:n,indexRevision:o.indexRevision+1,indexSyncState:"pending",indexSyncStateRevision:o.indexRevision+1,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null,updatedAt:new Date().toISOString(),lastActivityAt:new Date(e.ts).toISOString(),...a??{},firstAvailableSeq:l?.firstAvailableSeq??o.firstAvailableSeq,visibleMessageCount:l?.visibleMessageCount??o.visibleMessageCount,lastVisibleMessageId:l?.lastVisibleMessageId??o.lastVisibleMessageId,attentionCount:o.attentionCount??0,lastAttentionId:o.lastAttentionId??null,lastBodyCommittedAt:l?.lastBodyCommittedAt??o.lastBodyCommittedAt},S(i)}function k(t){let e;do{const n=u().readPage(t,{limit:500,beforeSequence:e});for(let i=n.entries.length-1;i>=0;i-=1){const o=n.entries[i],s=O(o.message);if(s)return{preview:s,messageId:o.message.id,at:new Date(o.message.ts).toISOString()}}if(!n.hasOlder||n.entries.length===0)break;e=n.entries[0].sequence}while(e!==void 0);return{preview:null,messageId:null,at:null}}function G(){const t=g();let e=!1;const n=new Date().toISOString();for(const[i,o]of Object.entries(t)){if(Object.prototype.hasOwnProperty.call(o,"lastMessagePreviewMessageId")&&Object.prototype.hasOwnProperty.call(o,"lastMessagePreviewAt"))continue;const s=k(i);t[i]={...o,lastMessagePreview:s.preview,lastMessagePreviewMessageId:s.messageId,lastMessagePreviewAt:s.at,indexRevision:o.indexRevision+1,indexSyncState:"pending",indexSyncStateRevision:o.indexRevision+1,indexSyncAttempts:0,indexSyncNextAttemptAt:null,indexSyncErrorCode:null,updatedAt:n},e=!0}e&&S(t)}function L(t,e){const n={title:t?.title??"",titleSource:t?.titleSource??"first_user_fallback",titleRevision:t?.titleRevision??0,titleLockedByUser:t?.titleLockedByUser??!1,titleOriginEventId:t?.titleOriginEventId??null},i=e.title?.trim()??"";if(!i)return n;if(!e.titleSource)throw new Error("titleSource is required with title");if(n.title&&n.titleSource==="first_user_fallback"&&e.titleSource==="first_user_fallback")return n;const o=e.titleSource==="shennian_manual";if(e.titleLockedByUser!==void 0&&e.titleLockedByUser!==o)throw new Error("titleLockedByUser conflicts with titleSource");const s=i===n.title&&e.titleSource===n.titleSource,a=e.titleRevision??n.titleRevision+(s?0:1);if(!Number.isSafeInteger(a)||a<0)throw new Error("invalid titleRevision");const l=m[e.titleSource],r=m[n.titleSource];return l<r||a<n.titleRevision||s&&a===n.titleRevision||a===n.titleRevision&&!s?n:{title:i,titleSource:e.titleSource,titleRevision:a,titleLockedByUser:o,titleOriginEventId:e.titleOriginEventId??n.titleOriginEventId}}function re(t,e){const n=e?.limit&&e.limit>0?Math.min(e.limit,500):500;let o=u().readRecent(t,n).messages;return e?.before!==void 0&&(o=o.filter(s=>s.ts<e.before)),[...o].sort((s,a)=>a.ts-s.ts)}function de(){return u().listSessionIds()}function ce(){return u()}function q(t,e){const n=A.dirname(t);d.mkdirSync(n,{recursive:!0,mode:448});const i=`${t}.tmp-${process.pid}-${M.randomBytes(6).toString("hex")}`;let o;try{if(o=d.openSync(i,"wx",384),d.writeFileSync(o,`${JSON.stringify(e,null,2)}
|
|
2
|
+
`,"utf8"),d.fsyncSync(o),d.closeSync(o),o=void 0,d.renameSync(i,t),process.platform!=="win32"){const s=d.openSync(n,"r");try{d.fsyncSync(s)}finally{d.closeSync(s)}}}finally{o!==void 0&&d.closeSync(o);try{d.unlinkSync(i)}catch{}}}export{D as PERSONAL_SYNC_V1_ROOT_NAME,X as acknowledgeSessionIndexUpdate,se as appendMessage,ie as applyGeneratedSessionTitle,te as claimSessionTitleGenerationCandidates,ne as deferSessionTitleGeneration,ce as getCanonicalSessionStore,c as getDaemonInstallationId,ae as getMessageDeliveryStatus,K as listPendingSessionIndexUpdates,U as listSessionRecords,de as listSessions,Z as markAllSessionIndexesForRepublish,z as purgeCanonicalSession,H as purgeSessionRecord,re as readMessages,B as recordSession,ee as recordSessionIndexRejection,le as tombstoneMessage,oe as upsertMessage};
|
|
@@ -3,8 +3,8 @@ import type { PersonalAttachmentReference, PersonalSyncIdentity, SessionBodyStor
|
|
|
3
3
|
export type BuiltinAgentType = 'claude' | 'codex' | 'workbuddy' | 'gemini' | 'cursor' | 'openclaw' | 'opencode' | 'pi' | 'manager';
|
|
4
4
|
export type CustomAgentType = `custom:${string}`;
|
|
5
5
|
export type AgentType = BuiltinAgentType | CustomAgentType;
|
|
6
|
-
export declare const DISABLED_BUILTIN_AGENT_TYPES: readonly ["openclaw"];
|
|
7
|
-
export declare const AVAILABLE_BUILTIN_AGENT_TYPES: readonly ["claude", "codex", "workbuddy", "
|
|
6
|
+
export declare const DISABLED_BUILTIN_AGENT_TYPES: readonly ["gemini", "openclaw"];
|
|
7
|
+
export declare const AVAILABLE_BUILTIN_AGENT_TYPES: readonly ["claude", "codex", "workbuddy", "cursor", "opencode", "pi"];
|
|
8
8
|
export type ModelInfo = {
|
|
9
9
|
id: string;
|
|
10
10
|
name: string;
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
export const DISABLED_BUILTIN_AGENT_TYPES = [
|
|
2
|
+
'gemini',
|
|
2
3
|
'openclaw',
|
|
3
4
|
];
|
|
4
5
|
export const AVAILABLE_BUILTIN_AGENT_TYPES = [
|
|
5
6
|
'claude',
|
|
6
7
|
'codex',
|
|
7
8
|
'workbuddy',
|
|
8
|
-
|
|
9
|
+
// Gemini CLI is intentionally hidden from new sessions. Keep the historical
|
|
10
|
+
// AgentType and adapter intact so persisted sessions remain readable.
|
|
9
11
|
'cursor',
|
|
10
12
|
'opencode',
|
|
11
13
|
'pi',
|