shennian 0.4.59 → 0.4.61
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 +10 -10
- package/dist/src/manager/runtime.d.ts +1 -0
- package/dist/src/manager/runtime.js +4 -4
- package/dist/src/session/handlers/chat-recovery.js +1 -1
- package/dist/src/session/handlers/chat.d.ts +9 -1
- package/dist/src/session/handlers/chat.js +1 -1
- package/dist/src/session/queue.js +1 -1
- package/dist/src/session/queued-chat-dispatch.d.ts +1 -0
- package/dist/src/session/queued-chat-dispatch.js +1 -1
- package/package.json +1 -1
|
@@ -471,8 +471,8 @@
|
|
|
471
471
|
},
|
|
472
472
|
{
|
|
473
473
|
"file": "src/manager/runtime.js",
|
|
474
|
-
"beforeBytes":
|
|
475
|
-
"afterBytes":
|
|
474
|
+
"beforeBytes": 28698,
|
|
475
|
+
"afterBytes": 14642
|
|
476
476
|
},
|
|
477
477
|
{
|
|
478
478
|
"file": "src/manager/worker-ipc.js",
|
|
@@ -786,8 +786,8 @@
|
|
|
786
786
|
},
|
|
787
787
|
{
|
|
788
788
|
"file": "src/session/handlers/chat-recovery.js",
|
|
789
|
-
"beforeBytes":
|
|
790
|
-
"afterBytes":
|
|
789
|
+
"beforeBytes": 6752,
|
|
790
|
+
"afterBytes": 3526
|
|
791
791
|
},
|
|
792
792
|
{
|
|
793
793
|
"file": "src/session/handlers/chat-session-lifecycle.js",
|
|
@@ -796,8 +796,8 @@
|
|
|
796
796
|
},
|
|
797
797
|
{
|
|
798
798
|
"file": "src/session/handlers/chat.js",
|
|
799
|
-
"beforeBytes":
|
|
800
|
-
"afterBytes":
|
|
799
|
+
"beforeBytes": 33565,
|
|
800
|
+
"afterBytes": 14399
|
|
801
801
|
},
|
|
802
802
|
{
|
|
803
803
|
"file": "src/session/handlers/control.js",
|
|
@@ -906,13 +906,13 @@
|
|
|
906
906
|
},
|
|
907
907
|
{
|
|
908
908
|
"file": "src/session/queue.js",
|
|
909
|
-
"beforeBytes":
|
|
910
|
-
"afterBytes":
|
|
909
|
+
"beforeBytes": 45091,
|
|
910
|
+
"afterBytes": 20004
|
|
911
911
|
},
|
|
912
912
|
{
|
|
913
913
|
"file": "src/session/queued-chat-dispatch.js",
|
|
914
|
-
"beforeBytes":
|
|
915
|
-
"afterBytes":
|
|
914
|
+
"beforeBytes": 2850,
|
|
915
|
+
"afterBytes": 1594
|
|
916
916
|
},
|
|
917
917
|
{
|
|
918
918
|
"file": "src/session/run-recovery/barrier.js",
|
|
@@ -55,6 +55,7 @@ export declare class ManagerRuntimeService {
|
|
|
55
55
|
getManagedAgentPolicyForWorker(managerSessionId: string, workDir: string): CompiledManagedAgentPolicy | null;
|
|
56
56
|
isNativeSkillManager(managerSessionId: string, workDir: string): boolean;
|
|
57
57
|
isManagedWorkerSession(sessionId: string): boolean;
|
|
58
|
+
isManagedRoomBoundManager(sessionId: string): boolean;
|
|
58
59
|
setManagerWorkerDefaults(sessionId: string, agentType?: AgentType | null, modelId?: string | null): void;
|
|
59
60
|
getManagerWorkerDefaults(sessionId: string): {
|
|
60
61
|
agentType: AgentType | null;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import _ from"node:http";import{createHash as N,randomBytes as x,randomUUID as f}from"node:crypto";import
|
|
2
|
-
`).trim()}function $(o){return o==="worker.final"?"completed":o==="worker.error"?"failed":o==="worker.aborted"?"stopped":"progress"}function U(o,e){return`manager-inbox:${N("sha256").update(o).update("\0").update(e.map(r=>r.id).join("\0")).digest("hex").slice(0,32)}`}async function j(o){const e=[];let t=0;for await(const r of o){const n=Buffer.isBuffer(r)?r:Buffer.from(r);if(t+=n.length,t>H)throw new Error("Request body too large");e.push(n)}return e.length===0?{}:JSON.parse(Buffer.concat(e).toString("utf8"))}function G(o,e,t,r){o.client.sendRes({type:"res",id:e,ok:t,...t?{payload:r}:{error:String(r.error||"unknown error")}})}function S(){return F()?y.join(v.tmpdir(),"shennian-vitest-runtime",String(process.pid),"manager-ipc.json"):P("runtime","manager-ipc.json")}function F(){return(process.env.VITEST==="true"||!!process.env.VITEST_WORKER_ID)&&!process.env.SHENNIAN_HOME?.trim()}class ue{opts;managedPolicies=new Map;registry=new T;channelRuntime;server=null;ipcUrl=null;ipcToken=x(24).toString("hex");healthTimer=null;startPromise=null;workerTextAcc=new Map;constructor(e){this.opts=e,this.channelRuntime=e.channelRuntime??new q}async start(){return this.startPromise?this.startPromise:(this.startPromise=this.doStart(),this.startPromise)}async doStart(){if(this.server)return;this.server=_.createServer((t,r)=>{this.handleIpc(t,r)}),this.server.requestTimeout=m,this.server.timeout=m,this.server.keepAliveTimeout=m,this.server.headersTimeout=m+5e3,await new Promise((t,r)=>{this.server.once("error",r),this.server.listen(0,"127.0.0.1",()=>t())});const e=this.server.address();typeof e=="object"&&e&&(this.ipcUrl=`http://127.0.0.1:${e.port}`,this.writeIpcRuntimeFile()),this.server.unref(),await this.channelRuntime.start(),this.healthTimer=setInterval(()=>this.scanWorkerHealth(),6e4),this.healthTimer.unref(),this.recoverPersistedInboxDeliveries()}async ready(){await this.start()}async stop(){this.healthTimer&&clearInterval(this.healthTimer),this.healthTimer=null,await this.channelRuntime.stop(),await new Promise(e=>{if(!this.server)return e();this.server.close(()=>e())}),this.server=null,this.ipcUrl=null,this.removeIpcRuntimeFile()}writeIpcRuntimeFile(){if(this.ipcUrl)try{const e=S();
|
|
1
|
+
import _ from"node:http";import{createHash as N,randomBytes as x,randomUUID as f}from"node:crypto";import g from"node:fs";import v from"node:os";import y from"node:path";import{ManagerRegistry as T}from"./registry.js";import{appendMessage as D}from"../session/store.js";import{requestSessionIndexPublish as W}from"../session/index-publisher.js";import{RetiredChannelRuntime as q}from"./retired-external-channel-compat.js";import{resolveShennianPath as P}from"../config/index.js";import{buildExternalChannelInstructions as B}from"../agents/external-channel-instructions.js";import{getPersonalSyncIdentity as I}from"../personal-sync/epoch.js";import{handleWorkerIpcRoute as E}from"./worker-ipc.js";import{deriveWorkerTitle as O,formatWorkerMarkdown as C}from"./worker-markdown.js";const H=Number(process.env.SHENNIAN_MANAGER_IPC_BODY_MAX_BYTES||2*1024*1024),m=12*6e4;let R=null;function ae(o){R=o}function oe(){return R}function h(o){return y.resolve(o||v.homedir())}function c(o,e,t){o.writeHead(e,{"content-type":"application/json; charset=utf-8"}),o.end(JSON.stringify(t))}function M(o){return o.replace(/\r\n/g,`
|
|
2
|
+
`).trim()}function $(o){return o==="worker.final"?"completed":o==="worker.error"?"failed":o==="worker.aborted"?"stopped":"progress"}function U(o,e){return`manager-inbox:${N("sha256").update(o).update("\0").update(e.map(r=>r.id).join("\0")).digest("hex").slice(0,32)}`}async function j(o){const e=[];let t=0;for await(const r of o){const n=Buffer.isBuffer(r)?r:Buffer.from(r);if(t+=n.length,t>H)throw new Error("Request body too large");e.push(n)}return e.length===0?{}:JSON.parse(Buffer.concat(e).toString("utf8"))}function G(o,e,t,r){o.client.sendRes({type:"res",id:e,ok:t,...t?{payload:r}:{error:String(r.error||"unknown error")}})}function S(){return F()?y.join(v.tmpdir(),"shennian-vitest-runtime",String(process.pid),"manager-ipc.json"):P("runtime","manager-ipc.json")}function F(){return(process.env.VITEST==="true"||!!process.env.VITEST_WORKER_ID)&&!process.env.SHENNIAN_HOME?.trim()}class ue{opts;managedPolicies=new Map;registry=new T;channelRuntime;server=null;ipcUrl=null;ipcToken=x(24).toString("hex");healthTimer=null;startPromise=null;workerTextAcc=new Map;constructor(e){this.opts=e,this.channelRuntime=e.channelRuntime??new q}async start(){return this.startPromise?this.startPromise:(this.startPromise=this.doStart(),this.startPromise)}async doStart(){if(this.server)return;this.server=_.createServer((t,r)=>{this.handleIpc(t,r)}),this.server.requestTimeout=m,this.server.timeout=m,this.server.keepAliveTimeout=m,this.server.headersTimeout=m+5e3,await new Promise((t,r)=>{this.server.once("error",r),this.server.listen(0,"127.0.0.1",()=>t())});const e=this.server.address();typeof e=="object"&&e&&(this.ipcUrl=`http://127.0.0.1:${e.port}`,this.writeIpcRuntimeFile()),this.server.unref(),await this.channelRuntime.start(),this.healthTimer=setInterval(()=>this.scanWorkerHealth(),6e4),this.healthTimer.unref(),this.recoverPersistedInboxDeliveries()}async ready(){await this.start()}async stop(){this.healthTimer&&clearInterval(this.healthTimer),this.healthTimer=null,await this.channelRuntime.stop(),await new Promise(e=>{if(!this.server)return e();this.server.close(()=>e())}),this.server=null,this.ipcUrl=null,this.removeIpcRuntimeFile()}writeIpcRuntimeFile(){if(this.ipcUrl)try{const e=S();g.mkdirSync(y.dirname(e),{recursive:!0}),g.writeFileSync(e,JSON.stringify({url:this.ipcUrl,token:this.ipcToken,pid:process.pid,updatedAt:new Date().toISOString()},null,2),{mode:384}),g.chmodSync(e,384)}catch{}}removeIpcRuntimeFile(){try{const e=S(),t=JSON.parse(g.readFileSync(e,"utf-8"));(t.url===this.ipcUrl||t.token===this.ipcToken)&&g.unlinkSync(e)}catch{}}getInjectedEnv(e,t,r,n){return this.ipcUrl?{SHENNIAN_MANAGER_SESSION_ID:e,SHENNIAN_MANAGER_AGENT_SESSION_ID:t??"",SHENNIAN_MANAGER_WORKDIR:h(r),SHENNIAN_MANAGER_MODEL:n,SHENNIAN_MANAGER_IPC_URL:this.ipcUrl,SHENNIAN_MANAGER_IPC_TOKEN:this.ipcToken}:{}}registerManager(e){this.registry.upsertManager({...e,workDir:h(e.workDir),machineId:process.env.SHENNIAN_MACHINE_ID??null,managedRoomBound:e.managedRoomBound??(this.managedPolicies.has(e.sessionId)||void 0)})}setManagedAgentPolicy(e,t){if(t){this.managedPolicies.set(e,t);const r=this.registry.getManager(e);r&&!r.managedRoomBound&&this.registry.upsertManager({...r,managedRoomBound:!0})}else this.managedPolicies.delete(e)}getManagedAgentPolicyForWorker(e,t){const r=this.registry.getManager(e),n=this.managedPolicies.get(e);return!r||!n||h(t)!==r.workDir?null:n}isNativeSkillManager(e,t){const r=this.registry.getManager(e);return r?.controllerKind==="native_skill"&&h(t)===r.workDir}isManagedWorkerSession(e){return!!this.findWorker(e)}isManagedRoomBoundManager(e){return this.registry.getManager(e)?.managedRoomBound===!0}setManagerWorkerDefaults(e,t,r){const n=this.registry.getManager(e);n&&this.registry.upsertManager({...n,defaultWorkerAgentType:t??null,defaultWorkerModelId:r??null})}getManagerWorkerDefaults(e){const t=this.registry.getManager(e);return{agentType:t?.defaultWorkerAgentType??null,modelId:t?.defaultWorkerModelId??null}}noteManagerAgentSession(e,t,r,n){const s=this.registry.getManager(e);this.registry.upsertManager({sessionId:e,agentSessionId:t,workDir:h(r),machineId:process.env.SHENNIAN_MACHINE_ID??null,modelId:n,agentType:s?.agentType,providerModelId:s?.providerModelId})}noteAgentEvent(e,t){const r=this.findWorker(e);if(!r)return;const n={lastActivityAt:new Date().toISOString(),runId:t.runId};t.agentSessionId&&(n.agentSessionId=t.agentSessionId);const s=`${e}:${t.runId}`;let i=t.message||r.summary||"(\u65E0\u53EF\u89C1\u5185\u5BB9)";if(t.state==="delta"&&t.text&&!t.thinking){const u=(this.workerTextAcc.get(s)??"")+t.text;this.workerTextAcc.set(s,u);const a=M(u);a&&(n.summary=a.length>160?`${a.slice(0,160)}...`:a)}if(t.state==="final"||t.state==="error"||t.state==="aborted"){n.status=t.state;const u=M(this.workerTextAcc.get(s)??"");u&&(n.summary=u.length>240?`${u.slice(0,240)}...`:u,i=t.message||u),this.workerTextAcc.delete(s)}else t.state==="start"&&(n.status="running");const d=this.registry.updateWorker(e,n);d&&(t.state==="final"||t.state==="error"||t.state==="aborted")&&this.enqueueWorkerEvent(d.managedBy,d,t.state,t.runId,i)}findWorker(e){return this.registry.load().workers[e]}async handleAppReq(e){G(this.opts.getRuntime(),e.id,!1,{error:"feature_retired"})}async handleIpc(e,t){if(e.headers.authorization!==`Bearer ${this.ipcToken}`){c(t,401,{ok:!1,error:"Unauthorized"});return}try{const r=new URL(e.url??"/","http://127.0.0.1"),n=await j(e);if(r.pathname.startsWith("/channel/")||r.pathname.startsWith("/external/")||r.pathname.startsWith("/wechat-rpa/")){c(t,410,{ok:!1,error:"feature_retired"});return}const s=await E({registry:this.registry,getRuntime:this.opts.getRuntime,dispatchReq:this.opts.dispatchReq,dispatchChatEnqueue:this.dispatchChatEnqueue.bind(this),appendManagerVisibleMarkdown:this.appendManagerVisibleMarkdown.bind(this)},r.pathname,n,String(e.headers["x-shennian-manager-session-id"]||""));if(s){c(t,s.status,s.body);return}c(t,404,{ok:!1,error:`Unknown manager IPC path: ${r.pathname}`})}catch(r){c(t,400,{ok:!1,error:r instanceof Error?r.message:String(r)})}}async executeScopedWorkerAction(e,t,r){const n={"manager.sessions.list":"/sessions/list","manager.sessions.start":"/sessions/start","manager.sessions.send":"/sessions/send","manager.sessions.queue":"/sessions/queue","manager.sessions.queue.edit":"/sessions/queue/edit","manager.sessions.queue.delete":"/sessions/queue/delete","manager.sessions.stop":"/sessions/stop","manager.sessions.read":"/sessions/read","manager.memory.path":"/memory/path"},s=await E({registry:this.registry,getRuntime:this.opts.getRuntime,dispatchReq:this.opts.dispatchReq,dispatchChatEnqueue:this.dispatchChatEnqueue.bind(this),appendManagerVisibleMarkdown:this.appendManagerVisibleMarkdown.bind(this)},n[t],r,e);if(!s||s.status<200||s.status>=300)throw new Error(String(s?.body.error||"Managed Manager action failed"));return JSON.parse(JSON.stringify(s.body))}async dispatchChatEnqueue(e,t,r,n,s,i,d){if((await this.opts.dispatchReq({type:"req",id:`manager-enqueue-${f()}`,method:"chat.enqueue",params:{...I(),sessionId:e,text:n,agentType:t,workDir:r,agentSessionId:s,modelId:i,managedWorkerParentSessionId:d}}))?.durableQueueHandoff===!1)throw new Error("manager_worker_queue_handoff_unconfirmed")}appendManagerVisibleMarkdown(e,t,r){this.registry.getManager(e)?.controllerKind!=="native_skill"&&(D(e,{id:r,sessionId:e,role:"agent",ts:Date.now(),payload:t},"committed"),W(this.opts.getRuntime().client))}enqueueWorkerEvent(e,t,r,n,s){const i=this.registry.enqueueInboxEvent({managerSessionId:e,workerSessionId:t.sessionId,kind:r==="final"?"worker.final":r==="error"?"worker.error":"worker.aborted",priority:"normal",runId:n??null,summary:s||t.summary||"(\u65E0\u53EF\u89C1\u6458\u8981)"}),d=this.registry.getManager(e);i&&d?.controllerKind!=="native_skill"&&this.drainManagerInbox(e)}scanWorkerHealth(){const e=Date.now(),t=this.registry.load(),r=this.opts.getRuntime();for(const n of Object.values(t.workers)){if(n.status!=="running")continue;const s=e-Date.parse(n.createdAt);if(s<10*6e4)continue;const i=e-Date.parse(n.lastActivityAt),d=r.sessions.get(n.sessionId)?.currentRunId,u=!!r.chatQueue?.getSnapshot(n.sessionId).pending.length;if(!d){if(!u&&i>=10*6e4){const w="\u672A\u627E\u5230\u6D3B\u52A8\u4E2D\u7684 Provider Run \u6216\u5F85\u5904\u7406\u6D88\u606F\uFF1B\u5DF2\u5C06\u9057\u7559\u7684\u8FD0\u884C\u72B6\u6001\u6536\u655B\u4E3A\u5931\u8D25\u3002\u539F Worker \u4F1A\u8BDD\u4ECD\u4FDD\u7559\uFF0C\u53EF\u7EE7\u7EED\u53D1\u9001\u65B0\u6D88\u606F\u3002",p=this.registry.updateWorker(n.sessionId,{status:"error",lastActivityAt:new Date(e).toISOString(),summary:w});p&&this.enqueueWorkerEvent(p.managedBy,p,"error",n.runId??void 0,w)}continue}const a=n.healthNotifiedAt?Date.parse(n.healthNotifiedAt):0;if(a&&e-a<10*6e4)continue;const l=t.managers[n.managedBy];if(!l)continue;const A=Math.max(0,Math.floor(i/6e4)),b=M(n.runId?this.workerTextAcc.get(`${n.sessionId}:${n.runId}`)??"":""),k=`\u5DF2\u8FD0\u884C ${Math.floor(s/6e4)} \u5206\u949F\uFF0C\u5C1A\u672A\u7ED3\u675F\u3002
|
|
3
3
|
|
|
4
4
|
\u5F53\u524D\u53EF\u89C1\u8FDB\u5C55\uFF1A
|
|
5
5
|
- \u6700\u8FD1\u6587\u672C\uFF1A${b||n.summary||"(\u65E0\u53EF\u89C1\u5185\u5BB9)"}
|
|
6
6
|
- \u6700\u8FD1\u6D3B\u52A8\u65F6\u95F4\uFF1A${n.lastActivityAt}
|
|
7
|
-
- \u6700\u8FD1 ${A} \u5206\u949F\u6CA1\u6709\u65B0\u6D3B\u52A8\u3002`;this.registry.updateWorker(n.sessionId,{healthNotifiedAt:new Date(e).toISOString(),lastHealthSummary:k}),this.registry.enqueueInboxEvent({managerSessionId:l.sessionId,workerSessionId:n.sessionId,kind:"worker.health",priority:"normal",runId:n.runId??null,summary:k})&&l.controllerKind!=="native_skill"&&this.drainManagerInbox(l.sessionId)}}async interruptAndResumeManager(e,t,r){this.opts.getRuntime().sessions.get(e.sessionId)?.currentRunId&&await this.opts.dispatchReq({type:"req",id:`manager-interrupt-${f()}`,method:"chat.abort",params:{...I(),sessionId:e.sessionId}}),await this.dispatchManagerContinuation(e,t,!1,r)}bindManagerAdapterEvents(e,t){t.on("agentEvent",r=>{if(r.state==="start"&&r.agentSessionId){const n=this.registry.getManager(e);n&&this.noteManagerAgentSession(e,r.agentSessionId,n.workDir,n.modelId)}r.state==="start"&&(this.acknowledgeManagerInboxDelivery(e),this.updateManagerStatus(e,"running")),(r.state==="final"||r.state==="error"||r.state==="aborted")&&(this.updateManagerStatus(e,"idle"),queueMicrotask(()=>{this.drainManagerInbox(e)}))})}updateManagerStatus(e,t){const r=this.registry.getManager(e);r&&this.registry.upsertManager({...r,status:t})}async drainManagerInbox(e){const t=this.registry.getManager(e);if(!t||t.deliveringInboxEventIds?.length)return;const r=this.registry.listInboxEvents(e);if(!r.length)return;const n=r.find(a=>a.priority==="high"),s=this.opts.getRuntime().sessions.get(e);if(t.status!=="idle"&&!n||s?.currentRunId&&!n)return;const i=n?[n]:r.slice(0,20),d=i.map(a=>{const l=this.registry.getWorkerForManager(e,a.workerSessionId);return
|
|
7
|
+
- \u6700\u8FD1 ${A} \u5206\u949F\u6CA1\u6709\u65B0\u6D3B\u52A8\u3002`;this.registry.updateWorker(n.sessionId,{healthNotifiedAt:new Date(e).toISOString(),lastHealthSummary:k}),this.registry.enqueueInboxEvent({managerSessionId:l.sessionId,workerSessionId:n.sessionId,kind:"worker.health",priority:"normal",runId:n.runId??null,summary:k})&&l.controllerKind!=="native_skill"&&this.drainManagerInbox(l.sessionId)}}async interruptAndResumeManager(e,t,r){this.opts.getRuntime().sessions.get(e.sessionId)?.currentRunId&&await this.opts.dispatchReq({type:"req",id:`manager-interrupt-${f()}`,method:"chat.abort",params:{...I(),sessionId:e.sessionId}}),await this.dispatchManagerContinuation(e,t,!1,r)}bindManagerAdapterEvents(e,t){t.on("agentEvent",r=>{if(r.state==="start"&&r.agentSessionId){const n=this.registry.getManager(e);n&&this.noteManagerAgentSession(e,r.agentSessionId,n.workDir,n.modelId)}r.state==="start"&&(this.acknowledgeManagerInboxDelivery(e),this.updateManagerStatus(e,"running")),(r.state==="final"||r.state==="error"||r.state==="aborted")&&(this.updateManagerStatus(e,"idle"),queueMicrotask(()=>{this.drainManagerInbox(e)}))})}updateManagerStatus(e,t){const r=this.registry.getManager(e);r&&this.registry.upsertManager({...r,status:t})}async drainManagerInbox(e){const t=this.registry.getManager(e);if(!t||t.deliveringInboxEventIds?.length)return;const r=this.registry.listInboxEvents(e);if(!r.length)return;const n=r.find(a=>a.priority==="high"),s=this.opts.getRuntime().sessions.get(e);if(t.status!=="idle"&&!n||s?.currentRunId&&!n)return;const i=n?[n]:r.slice(0,20),d=i.map(a=>{const l=this.registry.getWorkerForManager(e,a.workerSessionId);return C({kind:$(a.kind),sessionId:a.workerSessionId,title:l?.title||O(l?.userGoal||l?.summary||a.summary),body:a.summary})}).join(`
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
-
`),u=U(e,i);try{n&&s?.currentRunId?(this.registry.upsertManager({...t,status:"interrupting",deliveringInboxEventIds:i.map(l=>l.id)}),await this.interruptAndResumeManager(t,d,u)):(this.registry.upsertManager({...t,status:"running",deliveringInboxEventIds:i.map(l=>l.id)}),await this.dispatchManagerContinuation(t,d,!0,u)),this.registry.removeInboxEvents(e,i.map(l=>l.id));const a=this.registry.getManager(e);a&&this.registry.upsertManager({...a,deliveringInboxEventIds:[]})}catch(a){this.registry.upsertManager({...t,status:"idle",deliveringInboxEventIds:[]}),console.error(`[manager-inbox] delivery failed sessionId=${e}: ${a instanceof Error?a.message:String(a)}`)}}acknowledgeManagerInboxDelivery(e){const t=this.registry.getManager(e),r=t?.deliveringInboxEventIds??[];!t||r.length===0||(this.registry.removeInboxEvents(e,r),this.registry.upsertManager({...t,deliveringInboxEventIds:[]}))}recoverPersistedInboxDeliveries(){const e=this.registry.load(),t=this.opts.getRuntime();for(const r of Object.values(e.managers)){if(t.sessions.get(r.sessionId)?.currentRunId)continue;(r.status!=="idle"||r.deliveringInboxEventIds?.length)&&this.registry.upsertManager({...r,status:"idle",deliveringInboxEventIds:[]});const s=this.registry.listInboxEvents(r.sessionId).filter(i=>i.kind==="worker.health"&&!t.sessions.get(i.workerSessionId)?.currentRunId).map(i=>i.id);this.registry.removeInboxEvents(r.sessionId,s),this.registry.listInboxEvents(r.sessionId).length>0&&queueMicrotask(()=>{this.drainManagerInbox(r.sessionId)})}}async dispatchManagerContinuation(e,t,r=!1,n){const s=e.agentType??"manager",i=s==="manager"?e.modelId:e.providerModelId??void 0,d={...I(),sessionId:e.sessionId,text:t,agentType:s,sessionMode:"manager",workDir:e.workDir,agentSessionId:e.agentSessionId,modelId:i,managerDefaultWorkerAgentType:e.defaultWorkerAgentType??null,managerDefaultWorkerModelId:e.defaultWorkerModelId??null,...e.managedRoomBound?{managedAgentDirect:!0}:{},clientMessageId:n,managerEventId:n};if((await this.opts.dispatchReq({type:"req",id:`manager-${r?"enqueue":"send"}-${f()}`,method:"chat.enqueue",params:d}))?.durableQueueHandoff===!1)throw new Error("manager_event_queue_handoff_unconfirmed")}getExternalChannelStatus(e){return this.channelRuntime.getManagerChannelStatus(e)}getManagerExternalChannelSystemPrompt(e){return this.channelRuntime.listManagerExternalChannels(e).map(t=>
|
|
11
|
+
`),u=U(e,i);try{n&&s?.currentRunId?(this.registry.upsertManager({...t,status:"interrupting",deliveringInboxEventIds:i.map(l=>l.id)}),await this.interruptAndResumeManager(t,d,u)):(this.registry.upsertManager({...t,status:"running",deliveringInboxEventIds:i.map(l=>l.id)}),await this.dispatchManagerContinuation(t,d,!0,u)),this.registry.removeInboxEvents(e,i.map(l=>l.id));const a=this.registry.getManager(e);a&&this.registry.upsertManager({...a,deliveringInboxEventIds:[]})}catch(a){this.registry.upsertManager({...t,status:"idle",deliveringInboxEventIds:[]}),console.error(`[manager-inbox] delivery failed sessionId=${e}: ${a instanceof Error?a.message:String(a)}`)}}acknowledgeManagerInboxDelivery(e){const t=this.registry.getManager(e),r=t?.deliveringInboxEventIds??[];!t||r.length===0||(this.registry.removeInboxEvents(e,r),this.registry.upsertManager({...t,deliveringInboxEventIds:[]}))}recoverPersistedInboxDeliveries(){const e=this.registry.load(),t=this.opts.getRuntime();for(const r of Object.values(e.managers)){if(t.sessions.get(r.sessionId)?.currentRunId)continue;(r.status!=="idle"||r.deliveringInboxEventIds?.length)&&this.registry.upsertManager({...r,status:"idle",deliveringInboxEventIds:[]});const s=this.registry.listInboxEvents(r.sessionId).filter(i=>i.kind==="worker.health"&&!t.sessions.get(i.workerSessionId)?.currentRunId).map(i=>i.id);this.registry.removeInboxEvents(r.sessionId,s),this.registry.listInboxEvents(r.sessionId).length>0&&queueMicrotask(()=>{this.drainManagerInbox(r.sessionId)})}}async dispatchManagerContinuation(e,t,r=!1,n){const s=e.agentType??"manager",i=s==="manager"?e.modelId:e.providerModelId??void 0,d={...I(),sessionId:e.sessionId,text:t,agentType:s,sessionMode:"manager",workDir:e.workDir,agentSessionId:e.agentSessionId,modelId:i,managerDefaultWorkerAgentType:e.defaultWorkerAgentType??null,managerDefaultWorkerModelId:e.defaultWorkerModelId??null,...e.managedRoomBound?{managedAgentDirect:!0}:{},clientMessageId:n,managerEventId:n};if((await this.opts.dispatchReq({type:"req",id:`manager-${r?"enqueue":"send"}-${f()}`,method:"chat.enqueue",params:d}))?.durableQueueHandoff===!1)throw new Error("manager_event_queue_handoff_unconfirmed")}getExternalChannelStatus(e){return this.channelRuntime.getManagerChannelStatus(e)}getManagerExternalChannelSystemPrompt(e){return this.channelRuntime.listManagerExternalChannels(e).map(t=>B(t,void 0,e,"manager")).join(`
|
|
12
12
|
|
|
13
13
|
`).trim()}}export{ue as ManagerRuntimeService,oe as getManagerRuntimeService,ae as setManagerRuntimeService};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createHash as
|
|
1
|
+
import{createHash as g}from"node:crypto";import{buildManagedAgentPermissionPrompt as d}from"../../room/managed-prompt-composer.js";import{configureAdapterForSession as m,replaceManagedRoomContextToken as p}from"./chat-adapter-config.js";import{createActiveSession as y}from"./chat-session-lifecycle.js";import{validateManagedRoomExecutionContext as v}from"./chat.js";async function h(o,e,u=()=>{}){let a=null,s=null,r=null,i;if(e.source==="managed_room"||e.source==="managed_direct"||e.source==="manager_event"&&e.managedPolicyVersion){const n=await o.prepareManagedRecovery?.(e);if(!n)return{result:"recovery_failed",reason:"permission_context_unavailable",message:"managed recovery requires a fresh permission context"};if(n.agentType!==e.agentType||n.sessionMode!==e.sessionMode||o.resolvePath(n.workDir)!==o.resolvePath(e.workDir)||n.managedRoomPromptSnapshot.managedRoomPolicyVersion!==e.managedPolicyVersion||n.managedRoomPromptSnapshot.runtimePermissionPolicyHash!==e.managedRuntimePolicyHash)return o.revokeManagedRoomContextToken?.(n.managedRoomContextToken),{result:"recovery_failed",reason:"permission_context_unavailable",message:"managed recovery authority no longer matches the interrupted run"};if(a=n.managedAgentPolicy,e.source==="manager_event")o.revokeManagedRoomContextToken?.(n.managedRoomContextToken),i={source:"upgrade_recovery",recovery:"upgrade"};else{const l={source:"room_activation",managedAgentPolicy:n.managedAgentPolicy,managedRoomPromptSnapshot:n.managedRoomPromptSnapshot};v(l,n.systemPrompt,n.managedRoomContextToken),s=n.managedRoomContextToken,r=n.systemPrompt,i={...l,recovery:"upgrade"}}}else if(e.source==="manager_worker"&&!e.managerWorkerNativeFull){const n=e.parentSessionId;if(a=n?o.managerRuntime?.getManagedAgentPolicyForWorker(n,e.workDir)??null:null,!a)return{result:"recovery_failed",reason:"permission_context_unavailable",message:"manager worker recovery requires the current parent policy"};if(g("sha256").update(JSON.stringify({requestedPolicy:a.requestedPolicy,enforcement:a.enforcement})).digest("hex")!==e.managedRuntimePolicyHash)return{result:"recovery_failed",reason:"permission_context_unavailable",message:"manager worker policy changed after interruption"};r=d({requestedPolicy:a.requestedPolicy,enforcement:a.enforcement,resolvedWorkDir:a.adapterPolicy.workDir})}i??={source:"upgrade_recovery",recovery:"upgrade"};let t=o.sessions.get(e.sessionId);return t||(t=await y(o,e.sessionId,e.agentType,e.sessionMode,e.workDir,e.agentSessionId,null,null,a,e.nativeSessionVisibility)),a&&e.source==="manager_event"?m(t.adapter,e.sessionId,e.agentType,null,null,null,a,null):s&&(p(o,t,s),m(t.adapter,e.sessionId,e.agentType,null,null,s,a,o.managedRoomExecutor)),t.journalRunId=e.runId,t.currentRunId=e.providerRunId??`recovery:${e.runId}`,t.currentRunPhase="thinking",e.agentType==="codex"&&e.deliveryMode==="external_owner_queue"&&e.agentSessionId&&e.externalRunId&&o.nativeFusion?.restoreExternalObservation({sessionId:e.sessionId,sourceSessionKey:e.agentSessionId,runId:e.externalRunId,canonicalMessageId:e.clientMessageId,createdAt:Date.parse(e.createdAt)})?{result:"reattached"}:t.adapter.recoverInterruptedRun({sessionId:e.sessionId,shennianRunId:e.runId,workDir:e.workDir,agentSessionId:e.agentSessionId,modelId:e.modelId,reasoningEffort:e.reasoningEffort,providerRunId:e.providerRunId,lastObservedEventSeq:e.lastObservedEventSeq,continueDispatchState:e.continueDispatchState,systemPrompt:r,sendContext:i,beforeContinueDispatch:u})}export{h as recoverJournaledRun};
|
|
@@ -3,10 +3,18 @@ import { type ReqFrame, type AgentType, type SessionMode } from '@shennian/wire'
|
|
|
3
3
|
import type { SessionManagerRuntime } from '../types.js';
|
|
4
4
|
import type { CompiledManagedAgentPolicy } from '../../security/managed-agent-policy.js';
|
|
5
5
|
import { type ManagedRoomPromptSnapshot } from '../../room/managed-prompt-composer.js';
|
|
6
|
-
export type ChatSendExecutionContext = AgentSendContext & {
|
|
6
|
+
export type ChatSendExecutionContext = (AgentSendContext & {
|
|
7
|
+
source: 'room_activation';
|
|
7
8
|
/** Local-only snapshot produced by RoomActivationService; never accepted from Relay input. */
|
|
8
9
|
managedAgentPolicy: CompiledManagedAgentPolicy;
|
|
9
10
|
managedRoomPromptSnapshot: ManagedRoomPromptSnapshot;
|
|
11
|
+
}) | {
|
|
12
|
+
/** A Worker event may reuse the Room's current permission ceiling, but it
|
|
13
|
+
* is a private Manager continuation and must never receive Room tools. */
|
|
14
|
+
source: 'manager_event';
|
|
15
|
+
managedAgentPolicy: CompiledManagedAgentPolicy;
|
|
16
|
+
managedRoomPolicyVersion: string;
|
|
17
|
+
managedRuntimePolicyHash: string | null;
|
|
10
18
|
};
|
|
11
19
|
export declare function validateManagedRoomExecutionContext(context: ChatSendExecutionContext, systemPrompt: string | null | undefined, managedRoomContextToken: string | null | undefined): void;
|
|
12
20
|
export declare function sendSessionUpdateEvent(runtime: SessionManagerRuntime, input: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import se from"node:os";import{createHash as V,randomUUID as _e}from"node:crypto";import{buildUserMessagePayload as ie}from"@shennian/wire";import{reportLog as E}from"../../log-reporter.js";import{appendMessage as de,getDaemonInstallationId as le,getMessageDeliveryStatus as Ae,recordSession as Me,upsertMessage as I}from"../store.js";import{mergeProjectedSessions as Pe}from"../projection.js";import{parsePersonalAttachmentReferences as De}from"../attachment-reference.js";import{assertFinishedAttachmentReferences as Ee,markAttachmentReferences as xe}from"../attachment-transfer-store.js";import{requestSessionIndexPublish as R}from"../index-publisher.js";import{getPersonalSyncIdentity as be}from"../../personal-sync/epoch.js";import{buildManagedAgentPermissionPrompt as Te}from"../../room/managed-prompt-composer.js";import{getNativeSourceAgentType as Ce}from"./chat-events.js";import{configureAdapterForSession as ce,maybeResolveClaudeImportedWorkDir as $e,replaceManagedRoomContextToken as ge,revokeManagedRoomContextToken as ue}from"./chat-adapter-config.js";import{createActiveSession as qe,disposeSession as $,prepareAdapterTurn as We,rememberProcessedReqId as je,sendAdapterTurn as me}from"./chat-session-lifecycle.js";function Fe(e,n,t){if(!e.managedAgentPolicy)throw new Error("managed_agent_policy_missing");const l=e.managedRoomPromptSnapshot;if(!l)throw new Error("managed_room_prompt_snapshot_missing");if(!n?.trim())throw new Error("managed_room_prompt_incomplete");if(!t||!/^mrc_[A-Za-z0-9_-]{43}$/.test(t))throw new Error("managed_room_context_unavailable");const o=V("sha256").update(JSON.stringify({requestedPolicy:e.managedAgentPolicy.requestedPolicy,enforcement:e.managedAgentPolicy.enforcement})).digest("hex"),x=V("sha256").update(e.managedAgentPolicy.adapterPolicy.workDir).digest("hex");if(l.runtimePermissionPolicyHash!==o||l.resolvedWorkDirHash!==x||l.enforcementLevel!==e.managedAgentPolicy.enforcement.level)throw new Error("managed_room_prompt_snapshot_mismatch")}function pe(e,n){return`Agent send failed: ${n instanceof Error?n.message:String(n)}`}function Be(e,n){Me({sessionId:n.sessionId,agentType:n.agentType,sessionMode:n.sessionMode,managerConfig:n.managerConfig,workDir:n.workDir,agentSessionId:n.agentSessionId??null,modelId:n.modelId??null,managerDefaultWorkerAgentType:n.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:n.managerDefaultWorkerModelId}),e.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:n.sessionId,agentType:n.agentType,sessionMode:n.sessionMode,managerConfig:n.managerConfig,agentSessionId:n.agentSessionId??null,modelId:n.modelId??null,workDir:n.workDir,status:"active",externalChannel:null}}})}function Je(e,n){const t=e.sessions.get(n),l=t?.currentRunId;if(!t||!l)return;const o=t.nextEventSeq;e.runTextAcc.delete(`${n}:${l}`),ue(e,t),t.pendingTextEvent=null,t.currentRunId=null,t.currentRunPhase=null,t.nextEventSeq=0,t.heartbeatTimer&&(clearInterval(t.heartbeatTimer),t.heartbeatTimer=null),e.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"aborted",sessionId:n,runId:l,seq:o},seq:o,id:`agent-evt-${l}-${o}`}),t.journalRunId&&(e.runJournal?.remove(n,t.journalRunId),e.recoveryBarrier?.end(n),t.journalRunId=null),e.chatQueue?.noteTerminal(n)}async function on(e,n,t){const l=t?n.params.managedRoomContextToken??null:null;let o=!1;try{await He(e,n,t,()=>{o=!0})}finally{l&&!o&&e.revokeManagedRoomContextToken?.(l)}}async function He(e,n,t,l){if(e.processedReqIds.has(n.id)){e.client.sendRes({type:"res",id:n.id,ok:!0});return}const{sessionId:o,text:x,agentType:A,sessionMode:fe,managerConfig:L,workDir:ye,agentSessionId:u,modelId:v,systemPrompt:Q,managerDefaultWorkerAgentType:q,managerDefaultWorkerModelId:W,reasoningEffort:j,clientMessageId:ve,sessionListProjection:he,waitForDispatch:Z,responseId:Ie,managedRoomContextToken:M,suppressExternalOwnerEcho:Re,queuedBatchItems:F,managedWorkerParentSessionId:h,managerWorkerNativeFull:G,managedRecoverySource:b}=n.params,c=Ie||n.id;Pe(he);const T=null,B="";if(t?.source==="room_activation"&&(Fe(t,Q,M),A==="manager"))throw new Error("managed_agent_manager_mode_unsupported");if(je(e,n.id),!o||!x){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:c,ok:!1,error:"sessionId and text are required"});return}if(e.recoveryBarrier?.isDispatchFrozen()){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:c,ok:!1,error:"upgrade_in_progress"});return}e.recoveryBarrier?.isRecovering(o)&&await e.recoveryBarrier.wait(o);const d=A,S=d==="manager"||fe==="manager"?"manager":"standard";S==="manager"&&e.managerRuntime?.setManagerWorkerDefaults(o,q??null,W??null);const P=(d==="claude"||d==="codex"||d==="workbuddy"||d==="pi-coding-agent"||d.startsWith("acp:"))&&typeof j=="string"&&j.trim()?j.trim():void 0,p=e.resolvePath($e(d,ye||se.homedir(),u)||se.homedir()),f=t?.managedAgentPolicy??(h?e.managerRuntime?.getManagedAgentPolicyForWorker(h,p)??null:null);if(h&&!G&&!f)throw new Error("managed_worker_permission_context_unavailable");const J=d==="codex"&&(t?.source==="room_activation"||h)?"internal":"user",H=h&&f?Te({requestedPolicy:f.requestedPolicy,enforcement:f.enforcement,resolvedWorkDir:f.adapterPolicy.workDir}):Q,Se=p,C=De(n.params.attachments);C?.length&&Ee(p,C);const m={text:x,attachments:C,localized:!1};let r=e.sessions.get(o);if(r){if(r.lastActiveAt=Date.now(),r.agentType!==d||(r.sessionMode??(r.agentType==="manager"?"manager":"standard"))!==S||r.workDir!==p||(r.nativeSessionVisibility??"user")!==J||JSON.stringify(r.externalChannel??null)!==JSON.stringify(T??null)){e.sessions.delete(o);try{await $(e,r)}catch{e.processedReqIds.delete(n.id)}r=void 0}else if(u&&r.agentSessionId!==u)try{await r.adapter.resume(u),r.agentSessionId=u}catch{e.sessions.delete(o);try{await $(e,r)}catch{e.processedReqIds.delete(n.id)}r=void 0}}if(!r)try{r=await qe(e,o,d,S,p,u,T,B,f,J)}catch(a){const i=a instanceof Error&&a.message.startsWith("Unsupported agent:")?a.message:`Failed to start ${A}: ${a instanceof Error?a.message:String(a)}`;console.error(`[chat.send] start failed reqId=${n.id} sessionId=${o} agentType=${A} workDir=${p} agentSessionId=${u??""}: ${i}`),e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:o,message:i,runId:"",seq:0}}),e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:c,ok:!1,error:i});return}const g={id:ve??`user-${n.id}`,sessionId:o,role:"user",ts:Date.now(),payload:ie(m.text,m.attachments)},X=Array.isArray(F)&&F.length>0?F.map(a=>({id:a.clientMessageId??a.id,sessionId:o,role:b==="manager_event"?"agent":"user",ts:b==="manager_event"?Date.parse(a.createdAt):g.ts,payload:ie(a.text,a.attachments)})):null,w=X??[g],z=Ae(o,g.id),Y=z.state==="queued";if(z.state!=="not_found"&&!Y){e.client.sendRes({type:"res",id:c,ok:!0,payload:z});return}if(Z){t?.source==="room_activation"&&ce(r.adapter,o,d,T,B,M,t.managedAgentPolicy,e.managedRoomExecutor);try{await We(r.adapter,m.text,v,P,m.attachments,H,t?{source:t.source}:void 0)}catch(a){e.processedReqIds.delete(n.id),e.sessions.delete(o),await $(e,r),e.client.sendRes({type:"res",id:c,ok:!1,error:a instanceof Error?a.message:String(a)});return}}E({level:"info",sessionId:o,wsEvent:"chat.send.start",metadata:{reqId:n.id,agentType:d,sessionMode:S,managerConfig:L??null,modelId:v,managerDefaultWorkerAgentType:q??null,managerDefaultWorkerModelId:W??null,reasoningEffort:P,...t?.source==="room_activation"?{managedRoomPromptSnapshot:t.managedRoomPromptSnapshot}:{}}});const k=(a,i)=>({...be(le()),sessionId:o,clientMessageId:g.id,messageId:g.id,deliveryState:a,bodyRevision:i,committedAt:new Date(g.ts).toISOString()}),N=a=>{e.nativeFusion?.registerManagedSend({sessionId:o,agentType:d,sourceAgentType:Ce(d,v),canonicalMessageId:g.id,sourceSessionKey:r.agentSessionId??u??null,text:m.text,managedEchoPolicy:a?.deliveryMode==="external_owner_queue"&&!Re?"import_following":"suppress_following",externalRunId:a?.deliveryMode==="external_owner_queue"?`codex-external-${a.queuedSubmissionId??g.id}`:null})},ee=()=>{r.currentRunId||(r.currentRunId=`dispatch:${n.id}`,r.currentRunPhase="thinking",r.nextEventSeq=0)},ne=()=>{if(!e.runJournal||r.journalRunId)return;const a=`run:${_e()}`;e.runJournal.begin({daemonInstallationId:le(),sessionId:o,runId:a,clientMessageId:g.id,agentType:d,sessionMode:S,workDir:p,agentSessionId:r.agentSessionId??u??null,modelId:v??null,reasoningEffort:P??null,nativeSessionVisibility:J,providerRunId:null,source:b==="manager_event"?"manager_event":t?.source==="room_activation"?b==="managed_direct"?"managed_direct":"managed_room":h?"manager_worker":"personal",parentSessionId:h??null,managerWorkerNativeFull:G===!0,roomContextRegistryKey:typeof n.params.roomContextRegistryKey=="string"?n.params.roomContextRegistryKey:null,activationCauseMessageId:typeof n.params.activationCauseMessageId=="string"?n.params.activationCauseMessageId:null,rootCauseMessageId:typeof n.params.rootCauseMessageId=="string"?n.params.rootCauseMessageId:null,activationSequence:typeof n.params.activationSequence=="string"?n.params.activationSequence:null,activationHopCount:typeof n.params.activationHopCount=="number"?n.params.activationHopCount:null,managedPolicyVersion:t?.managedRoomPromptSnapshot.managedRoomPolicyVersion??null,managedRuntimePolicyHash:t?.managedRoomPromptSnapshot.runtimePermissionPolicyHash??(f?V("sha256").update(JSON.stringify({requestedPolicy:f.requestedPolicy,enforcement:f.enforcement})).digest("hex"):null)}),r.journalRunId=a},ae=()=>{if(N(),Y){let a=0;for(const[i,s]of w.entries()){const y=I(o,s,"queued");i===0&&(a=y)}return k("queued",a)}try{Be(e,{sessionId:o,agentType:d,sessionMode:S,managerConfig:L??null,workDir:Se,agentSessionId:r.agentSessionId??u??null,modelId:v,managerDefaultWorkerAgentType:q??null,managerDefaultWorkerModelId:W??null});let a=0;for(const[i,s]of w.entries()){const y=X?I(o,s,"committed"):de(o,s,"committed");i===0&&(a=y)}return xe(p,C??[]),R(e.client),k("committed",a)}catch(a){throw e.nativeFusion?.cancelManagedSend?.(g.id),a}},oe=a=>{r.journalRunId&&e.runJournal?.update(o,r.journalRunId,{deliveryMode:a?.deliveryMode==="external_owner_queue"?"external_owner_queue":"direct",externalRunId:a?.deliveryMode==="external_owner_queue"?`codex-external-${a.queuedSubmissionId??g.id}`:null})},te=a=>{r.currentRunId||(r.nextEventSeq=0),oe(a),N(a);const i=a?.deliveryMode==="external_owner_queue"?"queued":"executing";let s=0;for(const[y,D]of w.entries()){const _=I(o,D,i);y===0&&(s=_)}return R(e.client),k(i,s)},U=async(a,i)=>{e.nativeFusion?.cancelManagedSend?.(g.id);const s=pe(d,a);if(console.error(`[chat.send] send failed reqId=${n.id} sessionId=${o} agentType=${A} workDir=${p} agentSessionId=${r.agentSessionId??u??""}: ${s}`),r.adapter.hasPendingAuthenticationRetry?.()===!0){i&&e.client.sendRes({type:"res",id:c,ok:!0,payload:k("queued",0)});return}e.sessions.delete(o),r.journalRunId&&(e.runJournal?.update(o,r.journalRunId,{phase:"terminal"}),e.runJournal?.remove(o,r.journalRunId),e.recoveryBarrier?.end(o),r.journalRunId=null);try{await $(e,r)}catch{}if(e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:o,message:s,runId:"",seq:0}}),!i){const D={id:`agent-error-${n.id}-${Date.now()}`,sessionId:o,role:"agent",ts:Date.now(),payload:s};de(o,D),R(e.client)}i&&(e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:c,ok:!1,error:s}))};if(Z){try{ae()}catch(s){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:c,ok:!1,error:`Durable message commit failed: ${s instanceof Error?s.message:String(s)}`});return}ne(),ee();let a;try{t?.source==="room_activation"&&(ge(e,r,M),l());const s=m.attachments;a=await me(r.adapter,m.text,v,P,s,H,t?{source:t.source}:void 0),E({level:"info",sessionId:o,wsEvent:"chat.send.done",metadata:{reqId:n.id}})}catch(s){if(r.adapter.hasPendingAuthenticationRetry?.()){let _=0;for(const[O,K]of w.entries()){const ke=I(o,K,"queued");O===0&&(_=ke)}R(e.client),await U(s,!1),e.client.sendRes({type:"res",id:c,ok:!0,payload:k("queued",_)});return}let y=0;for(const[_,O]of w.entries()){const K=I(o,O,"failed");_===0&&(y=K)}R(e.client);const D=pe(d,s);await U(s,!1),e.client.sendRes({type:"res",id:c,ok:!0,payload:{...k("failed",y),failureMessage:D}});return}const i=te(a??void 0);e.client.sendRes({type:"res",id:c,ok:!0,payload:{...i,...m.localized?{localizedAttachments:!0}:{},...a?.deliveryMode==="external_owner_queue"?{queued:!0,deliveryMode:a.deliveryMode,queuedSubmissionId:a.queuedSubmissionId}:{}}}),E({level:"info",sessionId:o,wsEvent:"chat.send.res",metadata:{reqId:n.id,ok:!0}});return}let re;try{re=ae()}catch(a){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:c,ok:!1,error:`Durable message commit failed: ${a instanceof Error?a.message:String(a)}`});return}ne(),ee(),t?.source==="room_activation"&&(ce(r.adapter,o,d,T,B,M,t.managedAgentPolicy,e.managedRoomExecutor),ge(e,r,M),l()),te(),e.client.sendRes({type:"res",id:c,ok:!0,payload:{...re,...m.localized?{localizedAttachments:!0}:{}}}),E({level:"info",sessionId:o,wsEvent:"chat.send.res",metadata:{reqId:n.id,ok:!0}});const we=m.attachments;me(r.adapter,m.text,v,P,we,H,t?{source:t.source}:void 0).then(a=>{if(a?.deliveryMode==="external_owner_queue"){oe(a),N(a);for(const i of w)I(o,i,"queued");R(e.client)}E({level:"info",sessionId:o,wsEvent:"chat.send.done",metadata:{reqId:n.id}})}).catch(a=>{I(o,g,r.adapter.hasPendingAuthenticationRetry?.()?"queued":"failed"),R(e.client),U(a,!1)})}async function tn(e,n){const{sessionId:t,discardPending:l}=n.params,o=e.sessions.get(t);if(o){o.journalRunId&&e.runJournal?.markAborted(t,o.journalRunId),ue(e,o);try{await o.adapter.stop()}catch{e.client.sendRes({type:"res",id:n.id,ok:!1,error:"agent_stop_failed"});return}Je(e,t),e.activityPublisher?.publish(t,null)}l===!0&&e.chatQueue?.purgeSession(t),e.client.sendRes({type:"res",id:n.id,ok:!0})}export{tn as handleChatAbort,on as handleChatSend,Be as sendSessionUpdateEvent,Fe as validateManagedRoomExecutionContext};
|
|
1
|
+
import se from"node:os";import{createHash as K,randomUUID as ke}from"node:crypto";import{buildUserMessagePayload as ie}from"@shennian/wire";import{reportLog as E}from"../../log-reporter.js";import{appendMessage as de,getDaemonInstallationId as le,getMessageDeliveryStatus as Pe,recordSession as Ae,upsertMessage as I}from"../store.js";import{mergeProjectedSessions as Me}from"../projection.js";import{parsePersonalAttachmentReferences as De}from"../attachment-reference.js";import{assertFinishedAttachmentReferences as Ee,markAttachmentReferences as be}from"../attachment-transfer-store.js";import{requestSessionIndexPublish as R}from"../index-publisher.js";import{getPersonalSyncIdentity as xe}from"../../personal-sync/epoch.js";import{buildManagedAgentPermissionPrompt as Te}from"../../room/managed-prompt-composer.js";import{getNativeSourceAgentType as Ce}from"./chat-events.js";import{configureAdapterForSession as ce,maybeResolveClaudeImportedWorkDir as $e,replaceManagedRoomContextToken as ue,revokeManagedRoomContextToken as ge}from"./chat-adapter-config.js";import{createActiveSession as qe,disposeSession as $,prepareAdapterTurn as We,rememberProcessedReqId as je,sendAdapterTurn as me}from"./chat-session-lifecycle.js";function Fe(e,n,t){if(e.source!=="room_activation")throw new Error("managed_room_execution_context_required");if(!e.managedAgentPolicy)throw new Error("managed_agent_policy_missing");const l=e.managedRoomPromptSnapshot;if(!l)throw new Error("managed_room_prompt_snapshot_missing");if(!n?.trim())throw new Error("managed_room_prompt_incomplete");if(!t||!/^mrc_[A-Za-z0-9_-]{43}$/.test(t))throw new Error("managed_room_context_unavailable");const o=K("sha256").update(JSON.stringify({requestedPolicy:e.managedAgentPolicy.requestedPolicy,enforcement:e.managedAgentPolicy.enforcement})).digest("hex"),b=K("sha256").update(e.managedAgentPolicy.adapterPolicy.workDir).digest("hex");if(l.runtimePermissionPolicyHash!==o||l.resolvedWorkDirHash!==b||l.enforcementLevel!==e.managedAgentPolicy.enforcement.level)throw new Error("managed_room_prompt_snapshot_mismatch")}function pe(e,n){return`Agent send failed: ${n instanceof Error?n.message:String(n)}`}function Be(e,n){Ae({sessionId:n.sessionId,agentType:n.agentType,sessionMode:n.sessionMode,managerConfig:n.managerConfig,workDir:n.workDir,agentSessionId:n.agentSessionId??null,modelId:n.modelId??null,managerDefaultWorkerAgentType:n.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:n.managerDefaultWorkerModelId}),e.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:n.sessionId,agentType:n.agentType,sessionMode:n.sessionMode,managerConfig:n.managerConfig,agentSessionId:n.agentSessionId??null,modelId:n.modelId??null,workDir:n.workDir,status:"active",externalChannel:null}}})}function Je(e,n){const t=e.sessions.get(n),l=t?.currentRunId;if(!t||!l)return;const o=t.nextEventSeq;e.runTextAcc.delete(`${n}:${l}`),ge(e,t),t.pendingTextEvent=null,t.currentRunId=null,t.currentRunPhase=null,t.nextEventSeq=0,t.heartbeatTimer&&(clearInterval(t.heartbeatTimer),t.heartbeatTimer=null),e.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"aborted",sessionId:n,runId:l,seq:o},seq:o,id:`agent-evt-${l}-${o}`}),t.journalRunId&&(e.runJournal?.remove(n,t.journalRunId),e.recoveryBarrier?.end(n),t.journalRunId=null),e.chatQueue?.noteTerminal(n)}async function on(e,n,t){const l=t?n.params.managedRoomContextToken??null:null;let o=!1;try{await He(e,n,t,()=>{o=!0})}finally{l&&!o&&e.revokeManagedRoomContextToken?.(l)}}async function He(e,n,t,l){if(e.processedReqIds.has(n.id)){e.client.sendRes({type:"res",id:n.id,ok:!0});return}const{sessionId:o,text:b,agentType:P,sessionMode:fe,managerConfig:L,workDir:ye,agentSessionId:g,modelId:v,systemPrompt:Q,managerDefaultWorkerAgentType:q,managerDefaultWorkerModelId:W,reasoningEffort:j,clientMessageId:ve,sessionListProjection:he,waitForDispatch:Z,responseId:Ie,managedRoomContextToken:A,suppressExternalOwnerEcho:Re,queuedBatchItems:F,managedWorkerParentSessionId:h,managerWorkerNativeFull:G,managedRecoverySource:x}=n.params,c=Ie||n.id;Me(he);const T=null,B="";if(t?.source==="room_activation"&&(Fe(t,Q,A),P==="manager"))throw new Error("managed_agent_manager_mode_unsupported");if(je(e,n.id),!o||!b){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:c,ok:!1,error:"sessionId and text are required"});return}if(e.recoveryBarrier?.isDispatchFrozen()){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:c,ok:!1,error:"upgrade_in_progress"});return}e.recoveryBarrier?.isRecovering(o)&&await e.recoveryBarrier.wait(o);const d=P,_=d==="manager"||fe==="manager"?"manager":"standard";_==="manager"&&e.managerRuntime?.setManagerWorkerDefaults(o,q??null,W??null);const M=(d==="claude"||d==="codex"||d==="workbuddy"||d==="pi-coding-agent"||d.startsWith("acp:"))&&typeof j=="string"&&j.trim()?j.trim():void 0,p=e.resolvePath($e(d,ye||se.homedir(),g)||se.homedir()),f=t?.managedAgentPolicy??(h?e.managerRuntime?.getManagedAgentPolicyForWorker(h,p)??null:null);if(h&&!G&&!f)throw new Error("managed_worker_permission_context_unavailable");const J=d==="codex"&&(t?.source==="room_activation"||t?.source==="manager_event"||h)?"internal":"user",H=h&&f?Te({requestedPolicy:f.requestedPolicy,enforcement:f.enforcement,resolvedWorkDir:f.adapterPolicy.workDir}):Q,_e=p,C=De(n.params.attachments);C?.length&&Ee(p,C);const m={text:b,attachments:C,localized:!1};let r=e.sessions.get(o);if(r){if(r.lastActiveAt=Date.now(),r.agentType!==d||(r.sessionMode??(r.agentType==="manager"?"manager":"standard"))!==_||r.workDir!==p||(r.nativeSessionVisibility??"user")!==J||JSON.stringify(r.externalChannel??null)!==JSON.stringify(T??null)){e.sessions.delete(o);try{await $(e,r)}catch{e.processedReqIds.delete(n.id)}r=void 0}else if(g&&r.agentSessionId!==g)try{await r.adapter.resume(g),r.agentSessionId=g}catch{e.sessions.delete(o);try{await $(e,r)}catch{e.processedReqIds.delete(n.id)}r=void 0}}if(!r)try{r=await qe(e,o,d,_,p,g,T,B,f,J)}catch(a){const i=a instanceof Error&&a.message.startsWith("Unsupported agent:")?a.message:`Failed to start ${P}: ${a instanceof Error?a.message:String(a)}`;console.error(`[chat.send] start failed reqId=${n.id} sessionId=${o} agentType=${P} workDir=${p} agentSessionId=${g??""}: ${i}`),e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:o,message:i,runId:"",seq:0}}),e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:c,ok:!1,error:i});return}const u={id:ve??`user-${n.id}`,sessionId:o,role:"user",ts:Date.now(),payload:ie(m.text,m.attachments)},X=Array.isArray(F)&&F.length>0?F.map(a=>({id:a.clientMessageId??a.id,sessionId:o,role:x==="manager_event"?"agent":"user",ts:x==="manager_event"?Date.parse(a.createdAt):u.ts,payload:ie(a.text,a.attachments)})):null,w=X??[u],z=Pe(o,u.id),Y=z.state==="queued";if(z.state!=="not_found"&&!Y){e.client.sendRes({type:"res",id:c,ok:!0,payload:z});return}if(Z){(t?.source==="room_activation"||t?.source==="manager_event")&&ce(r.adapter,o,d,T,B,t.source==="room_activation"?A:null,t.managedAgentPolicy,e.managedRoomExecutor);try{await We(r.adapter,m.text,v,M,m.attachments,H,t?.source==="room_activation"?{source:"room_activation"}:void 0)}catch(a){e.processedReqIds.delete(n.id),e.sessions.delete(o),await $(e,r),e.client.sendRes({type:"res",id:c,ok:!1,error:a instanceof Error?a.message:String(a)});return}}E({level:"info",sessionId:o,wsEvent:"chat.send.start",metadata:{reqId:n.id,agentType:d,sessionMode:_,managerConfig:L??null,modelId:v,managerDefaultWorkerAgentType:q??null,managerDefaultWorkerModelId:W??null,reasoningEffort:M,...t?.source==="room_activation"?{managedRoomPromptSnapshot:t.managedRoomPromptSnapshot}:{}}});const S=(a,i)=>({...xe(le()),sessionId:o,clientMessageId:u.id,messageId:u.id,deliveryState:a,bodyRevision:i,committedAt:new Date(u.ts).toISOString()}),N=a=>{e.nativeFusion?.registerManagedSend({sessionId:o,agentType:d,sourceAgentType:Ce(d,v),canonicalMessageId:u.id,sourceSessionKey:r.agentSessionId??g??null,text:m.text,managedEchoPolicy:a?.deliveryMode==="external_owner_queue"&&!Re?"import_following":"suppress_following",externalRunId:a?.deliveryMode==="external_owner_queue"?`codex-external-${a.queuedSubmissionId??u.id}`:null})},ee=()=>{r.currentRunId||(r.currentRunId=`dispatch:${n.id}`,r.currentRunPhase="thinking",r.nextEventSeq=0)},ne=()=>{if(!e.runJournal||r.journalRunId)return;const a=`run:${ke()}`;e.runJournal.begin({daemonInstallationId:le(),sessionId:o,runId:a,clientMessageId:u.id,agentType:d,sessionMode:_,workDir:p,agentSessionId:r.agentSessionId??g??null,modelId:v??null,reasoningEffort:M??null,nativeSessionVisibility:J,providerRunId:null,source:x==="manager_event"?"manager_event":t?.source==="room_activation"?x==="managed_direct"?"managed_direct":"managed_room":h?"manager_worker":"personal",parentSessionId:h??null,managerWorkerNativeFull:G===!0,roomContextRegistryKey:typeof n.params.roomContextRegistryKey=="string"?n.params.roomContextRegistryKey:null,activationCauseMessageId:typeof n.params.activationCauseMessageId=="string"?n.params.activationCauseMessageId:null,rootCauseMessageId:typeof n.params.rootCauseMessageId=="string"?n.params.rootCauseMessageId:null,activationSequence:typeof n.params.activationSequence=="string"?n.params.activationSequence:null,activationHopCount:typeof n.params.activationHopCount=="number"?n.params.activationHopCount:null,managedPolicyVersion:t?.source==="room_activation"?t.managedRoomPromptSnapshot.managedRoomPolicyVersion:t?.source==="manager_event"?t.managedRoomPolicyVersion:null,managedRuntimePolicyHash:(t?.source==="room_activation"?t.managedRoomPromptSnapshot.runtimePermissionPolicyHash:t?.source==="manager_event"?t.managedRuntimePolicyHash:null)??(f?K("sha256").update(JSON.stringify({requestedPolicy:f.requestedPolicy,enforcement:f.enforcement})).digest("hex"):null)}),r.journalRunId=a},ae=()=>{if(N(),Y){let a=0;for(const[i,s]of w.entries()){const y=I(o,s,"queued");i===0&&(a=y)}return S("queued",a)}try{Be(e,{sessionId:o,agentType:d,sessionMode:_,managerConfig:L??null,workDir:_e,agentSessionId:r.agentSessionId??g??null,modelId:v,managerDefaultWorkerAgentType:q??null,managerDefaultWorkerModelId:W??null});let a=0;for(const[i,s]of w.entries()){const y=X?I(o,s,"committed"):de(o,s,"committed");i===0&&(a=y)}return be(p,C??[]),R(e.client),S("committed",a)}catch(a){throw e.nativeFusion?.cancelManagedSend?.(u.id),a}},oe=a=>{r.journalRunId&&e.runJournal?.update(o,r.journalRunId,{deliveryMode:a?.deliveryMode==="external_owner_queue"?"external_owner_queue":"direct",externalRunId:a?.deliveryMode==="external_owner_queue"?`codex-external-${a.queuedSubmissionId??u.id}`:null})},te=a=>{r.currentRunId||(r.nextEventSeq=0),oe(a),N(a);const i=a?.deliveryMode==="external_owner_queue"?"queued":"executing";let s=0;for(const[y,D]of w.entries()){const k=I(o,D,i);y===0&&(s=k)}return R(e.client),S(i,s)},U=async(a,i)=>{e.nativeFusion?.cancelManagedSend?.(u.id);const s=pe(d,a);if(console.error(`[chat.send] send failed reqId=${n.id} sessionId=${o} agentType=${P} workDir=${p} agentSessionId=${r.agentSessionId??g??""}: ${s}`),r.adapter.hasPendingAuthenticationRetry?.()===!0){i&&e.client.sendRes({type:"res",id:c,ok:!0,payload:S("queued",0)});return}e.sessions.delete(o),r.journalRunId&&(e.runJournal?.update(o,r.journalRunId,{phase:"terminal"}),e.runJournal?.remove(o,r.journalRunId),e.recoveryBarrier?.end(o),r.journalRunId=null);try{await $(e,r)}catch{}if(e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:o,message:s,runId:"",seq:0}}),!i){const D={id:`agent-error-${n.id}-${Date.now()}`,sessionId:o,role:"agent",ts:Date.now(),payload:s};de(o,D),R(e.client)}i&&(e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:c,ok:!1,error:s}))};if(Z){try{ae()}catch(s){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:c,ok:!1,error:`Durable message commit failed: ${s instanceof Error?s.message:String(s)}`});return}ne(),ee();let a;try{t?.source==="room_activation"&&(ue(e,r,A),l());const s=m.attachments;a=await me(r.adapter,m.text,v,M,s,H,t?.source==="room_activation"?{source:"room_activation"}:void 0),E({level:"info",sessionId:o,wsEvent:"chat.send.done",metadata:{reqId:n.id}})}catch(s){if(r.adapter.hasPendingAuthenticationRetry?.()){let k=0;for(const[O,V]of w.entries()){const Se=I(o,V,"queued");O===0&&(k=Se)}R(e.client),await U(s,!1),e.client.sendRes({type:"res",id:c,ok:!0,payload:S("queued",k)});return}let y=0;for(const[k,O]of w.entries()){const V=I(o,O,"failed");k===0&&(y=V)}R(e.client);const D=pe(d,s);await U(s,!1),e.client.sendRes({type:"res",id:c,ok:!0,payload:{...S("failed",y),failureMessage:D}});return}const i=te(a??void 0);e.client.sendRes({type:"res",id:c,ok:!0,payload:{...i,...m.localized?{localizedAttachments:!0}:{},...a?.deliveryMode==="external_owner_queue"?{queued:!0,deliveryMode:a.deliveryMode,queuedSubmissionId:a.queuedSubmissionId}:{}}}),E({level:"info",sessionId:o,wsEvent:"chat.send.res",metadata:{reqId:n.id,ok:!0}});return}let re;try{re=ae()}catch(a){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:c,ok:!1,error:`Durable message commit failed: ${a instanceof Error?a.message:String(a)}`});return}ne(),ee(),t?.source==="room_activation"&&(ce(r.adapter,o,d,T,B,A,t.managedAgentPolicy,e.managedRoomExecutor),ue(e,r,A),l()),te(),e.client.sendRes({type:"res",id:c,ok:!0,payload:{...re,...m.localized?{localizedAttachments:!0}:{}}}),E({level:"info",sessionId:o,wsEvent:"chat.send.res",metadata:{reqId:n.id,ok:!0}});const we=m.attachments;me(r.adapter,m.text,v,M,we,H,t?.source==="room_activation"?{source:"room_activation"}:void 0).then(a=>{if(a?.deliveryMode==="external_owner_queue"){oe(a),N(a);for(const i of w)I(o,i,"queued");R(e.client)}E({level:"info",sessionId:o,wsEvent:"chat.send.done",metadata:{reqId:n.id}})}).catch(a=>{I(o,u,r.adapter.hasPendingAuthenticationRetry?.()?"queued":"failed"),R(e.client),U(a,!1)})}async function tn(e,n){const{sessionId:t,discardPending:l}=n.params,o=e.sessions.get(t);if(o){o.journalRunId&&e.runJournal?.markAborted(t,o.journalRunId),ge(e,o);try{await o.adapter.stop()}catch{e.client.sendRes({type:"res",id:n.id,ok:!1,error:"agent_stop_failed"});return}Je(e,t),e.activityPublisher?.publish(t,null)}l===!0&&e.chatQueue?.purgeSession(t),e.client.sendRes({type:"res",id:n.id,ok:!0})}export{tn as handleChatAbort,on as handleChatSend,Be as sendSessionUpdateEvent,Fe as validateManagedRoomExecutionContext};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{NativeSourceDeletionStore as U}from"../native-fusion/native-source-deletions.js";import{QueueRetryScheduler as J}from"./queue-retry.js";import{findDuplicateQueuedMessage as E,stripLocalQueueFields as v,takeCompatibleBatch as G,formatQueuedBatch as Q,dedupeQueuedAttachments as B,classifyQueueError as F,isControllableQueueMessage as T,takeControllableMessage as H,normalizeQueuedModelId as q,resolveManagedRoomNativeSessionId as W}from"./queue-helpers.js";import{randomUUID as V}from"node:crypto";import{CODEX_DESKTOP_SEND_UNAVAILABLE as X,buildUserMessagePayload as Y}from"@shennian/wire";import{isCodexThreadTailInterrupted as Z}from"../native-fusion/codex-files.js";import{mergeProjectedSessions as ee}from"./projection.js";import{parsePersonalAttachmentReferences as C}from"./attachment-reference.js";import{assertFinishedAttachmentReferences as L,markAttachmentReferences as b}from"./attachment-transfer-store.js";import{getPersonalSyncIdentity as R}from"../personal-sync/epoch.js";import{getDaemonInstallationId as D}from"../personal-sync/identity.js";import{appendMessage as j,getMessageDeliveryStatus as _,listSessionRecords as N,recordSession as z,tombstoneMessage as $,upsertMessage as x}from"./store.js";import{requestSessionIndexPublish as S}from"./index-publisher.js";import{purgeQueuedSessionData as te,readQueue as p,writeQueue as M}from"./queue-storage.js";import{getNativeSourceAgentType as se}from"./handlers/chat-events.js";import{purgeQueuedSessionData as Se}from"./queue-storage.js";class A extends Error{constructor(e){super(e),this.name="TerminalQueuedChatDispatchError"}}function P(){return new Date().toISOString()}function O(u){const e=P(),s=u.managedWorkerParentSessionId?{kind:"manager_worker",parentSessionId:u.managedWorkerParentSessionId}:u.managerEventId?{kind:"manager_event",eventId:u.managerEventId}:u.managedAgentDirect?{kind:"managed_direct"}:{kind:"personal"};return{...R(D()),id:u.queueMessageId||u.clientMessageId||`queue-${V()}`,sessionId:u.sessionId,text:u.text,agentType:u.agentType,sessionMode:u.sessionMode,managerConfig:u.managerConfig,workDir:u.workDir,agentSessionId:u.agentSessionId??null,modelId:q(u.modelId)??null,systemPrompt:u.systemPrompt??null,managerDefaultWorkerAgentType:u.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:u.managerDefaultWorkerModelId??null,reasoningEffort:u.reasoningEffort??null,clientMessageId:u.clientMessageId??null,attachments:C(u.attachments),externalChannel:u.externalChannel??null,replyTarget:u.replyTarget??null,origin:u.origin,createdAt:e,updatedAt:e,trigger:s}}function w(u){return{id:u.clientMessageId??u.id,sessionId:u.sessionId,role:u.trigger?.kind==="manager_event"?"agent":"user",ts:Date.parse(u.createdAt),payload:Y(u.text,u.attachments)}}class ye{opts;draining=new Set;promoting=new Set;disposed=!1;retries=new J(e=>{this.wakeSession(e)});deletions=new U;inFlightMessageIds=new Map;deferredLogState=new Map;prepareManagedRoomTurn=null;prepareManagedAgentDirectTurn=null;constructor(e){this.opts=e,setTimeout(()=>this.drainIdleQueues(),0).unref?.()}stop(){this.disposed=!0,this.retries.stop()}getSnapshot(e){const s=p().sessions[e]??[],t=this.inFlightMessageIds.get(e);return{...R(D()),sessionId:e,busy:!!this.opts.getRuntime().sessions.get(e)?.currentRunId,pending:s.map(v),controllablePending:s.filter(o=>T(o)&&!t?.has(o.id)).map(v)}}setManagedRoomTurnPreparer(e){this.prepareManagedRoomTurn=e}setManagedAgentDirectTurnPreparer(e){this.prepareManagedAgentDirectTurn=e}async enqueueManagedRoomBatch(e){if(e.length===0)return;const s=this.opts.getRuntime(),t=p(),o=new Set,i=new Map;let n=!1;for(const d of e){const a=d.params;if(this.deletions.hasSession(a.sessionId)){delete t.sessions[a.sessionId],n=!0;continue}if(!a.sessionId||!a.text||!a.agentType||!a.workDir)throw new Error("sessionId, text, agentType and workDir are required");const g=O({...R(D()),...a});g.origin="room",g.managedRoom=d.context,g.trigger={kind:"room_activation",...d.context};const c=t.sessions[a.sessionId]??[],h=E(c,g),I=g.clientMessageId??g.id,m=_(a.sessionId,I);if(!h&&m.state==="not_found")c.push(g),t.sessions[a.sessionId]=c,i.set(`${g.sessionId}\0${I}`,g),o.add(a.sessionId),n=!0;else if(h&&m.state==="not_found")i.set(`${h.sessionId}\0${I}`,h),o.add(a.sessionId);else if(h&&m.state==="queued")o.add(a.sessionId);else if(h){const l=c.filter(k=>k.id!==h.id);l.length?t.sessions[a.sessionId]=l:delete t.sessions[a.sessionId],n=!0}}n&&M(t);let r;for(const d of o)try{for(const a of i.values())a.sessionId===d&&(j(d,w(a),"queued"),z({sessionId:d,agentType:a.agentType,sessionMode:a.sessionMode,managerConfig:a.managerConfig,workDir:a.workDir,agentSessionId:W(s,a),modelId:a.modelId}));this.logLifecycle("enqueue.persisted",d,{origin:"room",pendingCount:p().sessions[d]?.length??0}),this.broadcast(d),this.wakeSession(d)}catch(a){r=a,this.logLifecycle("enqueue.commit-failed",d,{failureClass:F(a instanceof Error?a.message:"","canonical_commit_failed")},"error"),this.retries.schedule(d,!0)}if(o.size&&S(s.client),r)throw r}async handleEnqueue(e){const s=this.opts.getRuntime(),t=e.params;if(ee(t.sessionListProjection),!t.sessionId||!t.text||!t.agentType||!t.workDir)return s.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId, text, agentType and workDir are required"}),!1;const o=t.managedAgentDirect?await this.prepareManagedAgentDirectTurn?.(t.sessionId):null;if(t.managedAgentDirect&&!o)throw new Error("managed_agent_context_unavailable");const i=o?N().find(K=>K.sessionId===t.sessionId):null,n=o?{...t,...o,agentType:o.agentType,sessionMode:o.sessionMode,managerConfig:o.managerConfig,workDir:o.workDir,agentSessionId:o.agentSessionId,modelId:i?.modelId??void 0,systemPrompt:o.systemPrompt,managerDefaultWorkerAgentType:o.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:o.managerDefaultWorkerModelId}:t,r=s.resolvePath(n.workDir),d=C(n.attachments);d?.length&&L(r,d);const a={text:n.text,attachments:d,localized:!1},g=s.sessions.get(n.sessionId);if(n.agentType==="codex"&&n.agentSessionId&&g?.currentRunId?.startsWith("dispatch:")&&Z(n.agentSessionId))return s.client.sendRes({type:"res",id:e.id,ok:!1,error:X}),!1;o?.managedRoomContextToken&&s.revokeManagedRoomContextToken?.(o.managedRoomContextToken);const c=await this.isSessionBusy(n.sessionId),h=p(),I=h.sessions[n.sessionId]??[],m=O({...n,text:a.text,attachments:a.attachments,systemPrompt:o?null:n.systemPrompt});o&&(m.managedAgentDirect=!0,m.trigger?.kind!=="manager_event"&&(m.trigger={kind:"managed_direct"}));const l=m.clientMessageId??m.id,k=_(n.sessionId,l);if(k.state!=="not_found")return s.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queued:k.state==="queued",queueMessageId:m.id,...k,queue:this.getSnapshot(n.sessionId)}}),!0;const y=E(I,m);y||(h.sessions[n.sessionId]=[...I,m],M(h));const f=y??m;return z({sessionId:f.sessionId,agentType:f.agentType,sessionMode:f.sessionMode,managerConfig:f.managerConfig,workDir:f.workDir,agentSessionId:f.agentSessionId,modelId:f.modelId}),j(f.sessionId,w(f),"queued"),b(s.resolvePath(f.workDir),f.attachments??[]),S(s.client),this.logLifecycle("enqueue.persisted",n.sessionId,{origin:n.origin??"app",queueMessageId:f.id,pendingCount:(p().sessions[n.sessionId]??[]).length}),this.broadcast(n.sessionId),s.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queued:!0,queueMessageId:y?.id??m.id,..._(n.sessionId,y?.clientMessageId??y?.id??m.clientMessageId??m.id),queue:this.getSnapshot(n.sessionId),...a.localized?{localizedAttachments:!0}:{}}}),c?this.retries.schedule(n.sessionId):this.wakeSession(n.sessionId),!0}async handleGet(e){const s=this.opts.getRuntime(),t=e.params;if(!t.sessionId){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId is required"});return}s.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}})}async handleEdit(e){const s=this.opts.getRuntime(),t=e.params;if(!t.sessionId||!t.queueMessageId||!t.text){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId, queueMessageId and text are required"});return}const o=p(),i=o.sessions[t.sessionId]??[],n=i.findIndex(a=>a.id===t.queueMessageId);if(n<0){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}if(!T(i[n])||this.inFlightMessageIds.get(t.sessionId)?.has(t.queueMessageId)){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"managed_agent_queue_mutation_forbidden"});return}const r=C(t.attachments),d=s.resolvePath(i[n].workDir);r?.length&&L(d,r),i[n]={...i[n],text:t.text,attachments:r,updatedAt:P()},o.sessions[t.sessionId]=i,M(o),x(t.sessionId,w(i[n]),"queued"),i[n].attachments?.length&&b(d,i[n].attachments??[]),S(s.client),this.broadcast(t.sessionId),s.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}})}async handleDelete(e){const s=this.opts.getRuntime(),t=e.params;if(!t.sessionId||!t.queueMessageId){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId and queueMessageId are required"});return}const o=p(),i=o.sessions[t.sessionId]??[],n=i.find(d=>d.id===t.queueMessageId);if(n&&(!T(n)||this.inFlightMessageIds.get(t.sessionId)?.has(t.queueMessageId))){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"managed_agent_queue_mutation_forbidden"});return}const r=i.filter(d=>d.id!==t.queueMessageId);if(r.length===i.length){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}r.length?o.sessions[t.sessionId]=r:delete o.sessions[t.sessionId],M(o),$(t.sessionId,n?.clientMessageId??n?.id??t.queueMessageId),S(s.client),this.broadcast(t.sessionId),s.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}})}async handlePromote(e){const s=this.opts.getRuntime(),t=e.params,o=n=>s.client.sendRes({type:"res",id:e.id,ok:!1,error:n});if(!t.sessionId||!t.queueMessageId){o("sessionId and queueMessageId are required");return}if(this.promoting.has(t.sessionId)){o("queue_promotion_in_progress");return}this.promoting.add(t.sessionId);let i=[];try{for(;this.draining.has(t.sessionId);)await new Promise(d=>setTimeout(d,5));if(i=H(p().sessions[t.sessionId]??[],t.queueMessageId),!i.length){o("Queued message not found");return}this.inFlightMessageIds.set(t.sessionId,new Set(i.map(d=>d.id)));const n=s.sessions.get(t.sessionId);let r="idle";if(n?.currentRunId)if(this.canInjectActiveBatch(n,i)){const d=i.length===1?i[0].text:Q(i),a=i[0].clientMessageId??i[0].id;s.nativeFusion?.registerManagedSend({sessionId:t.sessionId,agentType:i[0].agentType,sourceAgentType:se(i[0].agentType,i[0].modelId),canonicalMessageId:a,sourceSessionKey:n.agentSessionId,text:d,managedEchoPolicy:"suppress_following"});const g=Date.now();try{await n.adapter.injectActiveInput({text:d,attachments:B(i.flatMap(c=>c.attachments??[])),clientMessageId:a})}catch(c){throw s.nativeFusion?.cancelManagedSend(a),c}for(const c of i)x(t.sessionId,{...w(c),ts:g},"executing");this.removeBatch(t.sessionId,i),r="injected"}else{if(!this.opts.interruptQueuedSession)throw new Error("queue_interrupt_unavailable");await this.opts.interruptQueuedSession(t.sessionId),await this.dispatchQueuedMessages(i),this.removeBatch(t.sessionId,i),r="restarted"}else await this.dispatchQueuedMessages(i),this.removeBatch(t.sessionId,i);S(s.client),this.broadcast(t.sessionId),s.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId),promotedMessageIds:i.map(d=>d.id),mode:r}})}catch(n){o(n instanceof Error?n.message:"queue_promotion_failed"),this.broadcast(t.sessionId)}finally{this.inFlightMessageIds.delete(t.sessionId),this.promoting.delete(t.sessionId),p().sessions[t.sessionId]?.length&&this.wakeSession(t.sessionId)}}noteTerminal(e){this.wakeSession(e)}purgeSession(e){te(e),this.retries.cancel(e)}discardPendingManagedRoomMessages(e){if(this.deletions.hasSession(e)){const r=p().sessions[e]?.length??0;return this.purgeSession(e),r}const s=p(),t=s.sessions[e]??[],o=this.inFlightMessageIds.get(e)??new Set,i=t.filter(r=>r.managedRoom&&!o.has(r.id));if(i.length===0)return 0;const n=t.filter(r=>!i.includes(r));n.length?s.sessions[e]=n:delete s.sessions[e],M(s);for(const r of i)$(e,r.clientMessageId??r.id);return S(this.opts.getRuntime().client),this.logLifecycle("managed-room.discarded-paused",e,{discardedCount:i.length,pendingCount:n.length}),this.broadcast(e),i.length}wakeSession(e){this.drainNext(e).catch(()=>{this.logLifecycle("drain.retry-required",e,{},"error"),this.retries.schedule(e,!0)})}drainIdleQueues(){if(this.disposed)return;const e=p();for(const s of Object.keys(e.sessions))this.wakeSession(s)}async drainNext(e){if(this.disposed||this.draining.has(e)||this.promoting.has(e))return;this.draining.add(e);let s=!1,t=!1,o=[];try{if(this.deletions.hasSession(e)){this.purgeSession(e);return}if(await this.isSessionBusy(e)){const g=p().sessions[e]?.length??0,c=this.deferredLogState.get(e),h=Date.now();(!c||c.pendingCount!==g||h-c.at>=3e4)&&(this.logLifecycle("drain.deferred",e,{pendingCount:g}),this.deferredLogState.set(e,{at:h,pendingCount:g}));return}this.deferredLogState.delete(e);const i=p(),n=i.sessions[e]??[],r=n.filter(g=>{const c=g.clientMessageId??g.id,h=_(e,c).state;return h==="queued"||h==="not_found"});r.length!==n.length&&(r.length?i.sessions[e]=r:delete i.sessions[e],M(i),this.logLifecycle("drain.pruned-terminal",e,{prunedCount:n.length-r.length,pendingCount:r.length}));const d=G(r);if(d.length===0){this.broadcast(e);return}o=d,this.inFlightMessageIds.set(e,new Set(d.map(g=>g.id))),this.logLifecycle("dispatch.start",e,{batchCount:d.length,queueMessageId:d[0].id,origin:d[0].origin??"app"});for(const g of d)x(e,w(g),"queued");await this.dispatchQueuedMessages(d);const a=this.removeBatch(e,d);this.logLifecycle("dispatch.accepted",e,{batchCount:d.length,queueMessageId:d[0].id,pendingCount:a}),this.deferredLogState.delete(e),s=a>0,this.broadcast(e)}catch(i){const n=F(i instanceof Error?i.message:String(i),i instanceof A?"terminal_dispatch_failed":"transient_dispatch_failed"),r=o[0],d=!!(r&&!r.trigger&&!r.clientMessageId&&Date.now()-Date.parse(r.createdAt)>=10*6e4&&this.opts.getRuntime().managerRuntime?.isManagedWorkerSession(e));if(i instanceof A||n==="managed_claim_source_binding_conflict"||d){const g=this.removeBatch(e,o);this.logLifecycle("dispatch.terminal-failure",e,{batchCount:o.length,queueMessageId:o[0]?.id,pendingCount:g,failureClass:d?"stale_legacy_managed_worker_dispatch_failure":n},"error"),s=g>0}else t=!0,this.logLifecycle("dispatch.transient-failure",e,{failureClass:n},"error");this.broadcast(e)}finally{this.inFlightMessageIds.delete(e),this.draining.delete(e),p().sessions[e]?.length?s?queueMicrotask(()=>this.wakeSession(e)):this.retries.schedule(e,t):this.retries.cancel(e)}}canInjectActiveBatch(e,s){if(!e.adapter.supportsActiveInput||!e.adapter.injectActiveInput||!e.journalRunId)return!1;const t=this.opts.getRuntime().runJournal?.get(s[0].sessionId,e.journalRunId),o=s[0].trigger?.kind==="managed_direct"||s[0].managedAgentDirect?null:"personal";return!!(o&&s[0].systemPrompt==null&&t&&t.source===o&&t.deliveryMode==="direct"&&t.agentType===s[0].agentType&&t.workDir===e.workDir&&t.agentSessionId===e.agentSessionId&&t.modelId===(s[0].modelId??null)&&t.reasoningEffort===(s[0].reasoningEffort??null))}removeBatch(e,s){if(s.length===0)return p().sessions[e]?.length??0;const t=p(),o=new Set(s.map(n=>n.id)),i=(t.sessions[e]??[]).filter(n=>!o.has(n.id));return i.length?t.sessions[e]=i:delete t.sessions[e],M(t),i.length}logLifecycle(e,s,t,o="info"){const i=`[chat.queue] ${JSON.stringify({at:P(),event:e,sessionId:s,...t})}`;o==="error"?console.error(i):console.info(i)}async dispatchQueuedMessages(e){const s=e[0],t=e.flatMap(l=>{if(l.trigger?.kind==="room_activation"){const{kind:k,...y}=l.trigger;return[y]}return l.managedRoom?[l.managedRoom]:[]}),o=e.some(l=>l.trigger?.kind==="managed_direct"||!!l.managedAgentDirect),i=e.find(l=>l.trigger?.kind==="manager_event")?.trigger,n=e.find(l=>l.trigger?.kind==="manager_worker")?.trigger,r=t.length>0?await this.prepareManagedRoomTurn?.(t):o?await this.prepareManagedAgentDirectTurn?.(s.sessionId):void 0;if(t.length>0&&!r)throw new Error("managed_room_turn_preparer_unavailable");if(o&&!r)throw new Error("managed_agent_context_unavailable");const d=new Set((p().sessions[s.sessionId]??[]).map(l=>l.id));if(this.disposed||this.deletions.hasSession(s.sessionId)||e.some(l=>!d.has(l.id)))throw r?.managedRoomContextToken&&this.opts.getRuntime().revokeManagedRoomContextToken?.(r.managedRoomContextToken),this.disposed?new Error("queue_stopped"):new A("queued_dispatch_cancelled");const a=r??null,g=a?.sessionMode==="manager"?a.managerConfig:null,c=e.length===1?s.text:Q(e),h=B(e.flatMap(l=>l.attachments??[])),I=o?r.agentSessionId:t.length>0?W(this.opts.getRuntime(),s):s.agentSessionId??null,m=r?N().find(l=>l.sessionId===s.sessionId):null;await this.opts.dispatchQueuedReq({type:"req",id:`queue-send-${s.id}-${Date.now()}`,method:"chat.send",params:{...R(D()),sessionId:s.sessionId,text:c,agentType:o?r.agentType:s.agentType,sessionMode:a?.sessionMode??s.sessionMode,managerConfig:a?g:s.managerConfig,workDir:o?r.workDir:s.workDir,agentSessionId:I,modelId:q(m?.modelId)??q(s.modelId),systemPrompt:r?.systemPrompt??s.systemPrompt??null,managerDefaultWorkerAgentType:a?a.managerDefaultWorkerAgentType:s.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:a?a.managerDefaultWorkerModelId:s.managerDefaultWorkerModelId,reasoningEffort:s.reasoningEffort??void 0,clientMessageId:s.clientMessageId??s.id,attachments:h,externalChannel:s.externalChannel,replyTarget:s.replyTarget,roomContextRegistryKey:r?.roomContextRegistryKey,activationCauseMessageId:t.at(-1)?.causeMessageId,rootCauseMessageId:t.at(-1)?.rootCauseMessageId,activationSequence:t.at(-1)?.sequence,activationHopCount:t.at(-1)?.hopCount,managedRecoverySource:i?.kind==="manager_event"?"manager_event":o?"managed_direct":t.length>0?"managed_room":void 0,managerEventId:i?.kind==="manager_event"?i.eventId:void 0,managedWorkerParentSessionId:n?.kind==="manager_worker"?n.parentSessionId:void 0,managerWorkerNativeFull:n?.kind==="manager_worker"&&this.opts.getRuntime().managerRuntime?.isNativeSkillManager(n.parentSessionId,s.workDir)||void 0,managedRoomContextToken:r?.managedRoomContextToken,suppressExternalOwnerEcho:t.length>0||void 0,queuedBatchItems:e.map(v),waitForDispatch:!0}},r??void 0)}async isSessionBusy(e){const s=this.opts.getRuntime().recoveryBarrier;if(s?.isDispatchFrozen()||s?.isRecovering(e))return!0;const t=this.opts.getRuntime().sessions.get(e);if(!t)return!1;if(t.currentRunId){if(!(t.currentRunId.startsWith("dispatch:")&&!!this.opts.getRuntime().runJournal&&!t.journalRunId)||(t.adapter.getStatus?await t.adapter.getStatus().catch(()=>null):null)?.active)return!0;this.logLifecycle("drain.recovered-stale-dispatch",e,{staleRunId:t.currentRunId}),t.currentRunId=null,t.currentRunPhase=null}if(!t.adapter.getStatus)return!1;const o=await t.adapter.getStatus().catch(()=>null);return o?.active?(o.runId&&(t.currentRunId=o.runId,t.currentRunPhase=o.runPhase??t.currentRunPhase),!0):!1}broadcast(e){this.opts.getRuntime().client.sendEvent({type:"event",event:"session.queue.update",payload:{queue:this.getSnapshot(e)}})}}export{ye as ChatQueueManager,A as TerminalQueuedChatDispatchError,Se as purgeQueuedSessionData};
|
|
1
|
+
import{NativeSourceDeletionStore as U}from"../native-fusion/native-source-deletions.js";import{QueueRetryScheduler as J}from"./queue-retry.js";import{findDuplicateQueuedMessage as E,stripLocalQueueFields as D,takeCompatibleBatch as G,formatQueuedBatch as Q,dedupeQueuedAttachments as B,classifyQueueError as F,isControllableQueueMessage as T,takeControllableMessage as H,normalizeQueuedModelId as x,resolveManagedRoomNativeSessionId as W}from"./queue-helpers.js";import{randomUUID as V}from"node:crypto";import{CODEX_DESKTOP_SEND_UNAVAILABLE as X,buildUserMessagePayload as Y}from"@shennian/wire";import{isCodexThreadTailInterrupted as Z}from"../native-fusion/codex-files.js";import{mergeProjectedSessions as ee}from"./projection.js";import{parsePersonalAttachmentReferences as C}from"./attachment-reference.js";import{assertFinishedAttachmentReferences as L,markAttachmentReferences as b}from"./attachment-transfer-store.js";import{getPersonalSyncIdentity as R}from"../personal-sync/epoch.js";import{getDaemonInstallationId as _}from"../personal-sync/identity.js";import{appendMessage as j,getMessageDeliveryStatus as v,listSessionRecords as N,recordSession as z,tombstoneMessage as $,upsertMessage as q}from"./store.js";import{requestSessionIndexPublish as S}from"./index-publisher.js";import{purgeQueuedSessionData as te,readQueue as p,writeQueue as k}from"./queue-storage.js";import{getNativeSourceAgentType as ne}from"./handlers/chat-events.js";import{purgeQueuedSessionData as Se}from"./queue-storage.js";class A extends Error{constructor(e){super(e),this.name="TerminalQueuedChatDispatchError"}}function P(){return new Date().toISOString()}function O(u){const e=P(),n=u.managedWorkerParentSessionId?{kind:"manager_worker",parentSessionId:u.managedWorkerParentSessionId}:u.managerEventId?{kind:"manager_event",eventId:u.managerEventId}:u.managedAgentDirect?{kind:"managed_direct"}:{kind:"personal"};return{...R(_()),id:u.queueMessageId||u.clientMessageId||`queue-${V()}`,sessionId:u.sessionId,text:u.text,agentType:u.agentType,sessionMode:u.sessionMode,managerConfig:u.managerConfig,workDir:u.workDir,agentSessionId:u.agentSessionId??null,modelId:x(u.modelId)??null,systemPrompt:u.systemPrompt??null,managerDefaultWorkerAgentType:u.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:u.managerDefaultWorkerModelId??null,reasoningEffort:u.reasoningEffort??null,clientMessageId:u.clientMessageId??null,attachments:C(u.attachments),externalChannel:u.externalChannel??null,replyTarget:u.replyTarget??null,origin:u.origin,createdAt:e,updatedAt:e,trigger:n}}function w(u){return{id:u.clientMessageId??u.id,sessionId:u.sessionId,role:u.trigger?.kind==="manager_event"?"agent":"user",ts:Date.parse(u.createdAt),payload:Y(u.text,u.attachments)}}class ye{opts;draining=new Set;promoting=new Set;disposed=!1;retries=new J(e=>{this.wakeSession(e)});deletions=new U;inFlightMessageIds=new Map;deferredLogState=new Map;prepareManagedRoomTurn=null;prepareManagedAgentDirectTurn=null;constructor(e){this.opts=e,setTimeout(()=>this.drainIdleQueues(),0).unref?.()}stop(){this.disposed=!0,this.retries.stop()}getSnapshot(e){const n=p().sessions[e]??[],t=this.inFlightMessageIds.get(e);return{...R(_()),sessionId:e,busy:!!this.opts.getRuntime().sessions.get(e)?.currentRunId,pending:n.map(D),controllablePending:n.filter(s=>T(s)&&!t?.has(s.id)).map(D)}}setManagedRoomTurnPreparer(e){this.prepareManagedRoomTurn=e}setManagedAgentDirectTurnPreparer(e){this.prepareManagedAgentDirectTurn=e}async enqueueManagedRoomBatch(e){if(e.length===0)return;const n=this.opts.getRuntime(),t=p(),s=new Set,a=new Map;let i=!1;for(const d of e){const r=d.params;if(this.deletions.hasSession(r.sessionId)){delete t.sessions[r.sessionId],i=!0;continue}if(!r.sessionId||!r.text||!r.agentType||!r.workDir)throw new Error("sessionId, text, agentType and workDir are required");const g=O({...R(_()),...r});g.origin="room",g.managedRoom=d.context,g.trigger={kind:"room_activation",...d.context};const c=t.sessions[r.sessionId]??[],m=E(c,g),I=g.clientMessageId??g.id,h=v(r.sessionId,I);if(!m&&h.state==="not_found")c.push(g),t.sessions[r.sessionId]=c,a.set(`${g.sessionId}\0${I}`,g),s.add(r.sessionId),i=!0;else if(m&&h.state==="not_found")a.set(`${m.sessionId}\0${I}`,m),s.add(r.sessionId);else if(m&&h.state==="queued")s.add(r.sessionId);else if(m){const M=c.filter(l=>l.id!==m.id);M.length?t.sessions[r.sessionId]=M:delete t.sessions[r.sessionId],i=!0}}i&&k(t);let o;for(const d of s)try{for(const r of a.values())r.sessionId===d&&(j(d,w(r),"queued"),z({sessionId:d,agentType:r.agentType,sessionMode:r.sessionMode,managerConfig:r.managerConfig,workDir:r.workDir,agentSessionId:W(n,r),modelId:r.modelId}));this.logLifecycle("enqueue.persisted",d,{origin:"room",pendingCount:p().sessions[d]?.length??0}),this.broadcast(d),this.wakeSession(d)}catch(r){o=r,this.logLifecycle("enqueue.commit-failed",d,{failureClass:F(r instanceof Error?r.message:"","canonical_commit_failed")},"error"),this.retries.schedule(d,!0)}if(s.size&&S(n.client),o)throw o}async handleEnqueue(e){const n=this.opts.getRuntime(),t=e.params;if(ee(t.sessionListProjection),!t.sessionId||!t.text||!t.agentType||!t.workDir)return n.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId, text, agentType and workDir are required"}),!1;const s=t.managedAgentDirect?await this.prepareManagedAgentDirectTurn?.(t.sessionId):null;if(t.managedAgentDirect&&!s)throw new Error("managed_agent_context_unavailable");const a=s?N().find(K=>K.sessionId===t.sessionId):null,i=s?{...t,...s,agentType:s.agentType,sessionMode:s.sessionMode,managerConfig:s.managerConfig,workDir:s.workDir,agentSessionId:s.agentSessionId,modelId:a?.modelId??void 0,systemPrompt:s.systemPrompt,managerDefaultWorkerAgentType:s.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:s.managerDefaultWorkerModelId}:t,o=n.resolvePath(i.workDir),d=C(i.attachments);d?.length&&L(o,d);const r={text:i.text,attachments:d,localized:!1},g=n.sessions.get(i.sessionId);if(i.agentType==="codex"&&i.agentSessionId&&g?.currentRunId?.startsWith("dispatch:")&&Z(i.agentSessionId))return n.client.sendRes({type:"res",id:e.id,ok:!1,error:X}),!1;s?.managedRoomContextToken&&n.revokeManagedRoomContextToken?.(s.managedRoomContextToken);const c=await this.isSessionBusy(i.sessionId),m=p(),I=m.sessions[i.sessionId]??[],h=O({...i,text:r.text,attachments:r.attachments,systemPrompt:s?null:i.systemPrompt});s&&(h.managedAgentDirect=!0,h.trigger?.kind!=="manager_event"&&(h.trigger={kind:"managed_direct"}));const M=h.clientMessageId??h.id,l=v(i.sessionId,M);if(l.state!=="not_found")return n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queued:l.state==="queued",queueMessageId:h.id,...l,queue:this.getSnapshot(i.sessionId)}}),!0;const y=E(I,h);y||(m.sessions[i.sessionId]=[...I,h],k(m));const f=y??h;return z({sessionId:f.sessionId,agentType:f.agentType,sessionMode:f.sessionMode,managerConfig:f.managerConfig,workDir:f.workDir,agentSessionId:f.agentSessionId,modelId:f.modelId}),j(f.sessionId,w(f),"queued"),b(n.resolvePath(f.workDir),f.attachments??[]),S(n.client),this.logLifecycle("enqueue.persisted",i.sessionId,{origin:i.origin??"app",queueMessageId:f.id,pendingCount:(p().sessions[i.sessionId]??[]).length}),this.broadcast(i.sessionId),n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queued:!0,queueMessageId:y?.id??h.id,...v(i.sessionId,y?.clientMessageId??y?.id??h.clientMessageId??h.id),queue:this.getSnapshot(i.sessionId),...r.localized?{localizedAttachments:!0}:{}}}),c?this.retries.schedule(i.sessionId):this.wakeSession(i.sessionId),!0}async handleGet(e){const n=this.opts.getRuntime(),t=e.params;if(!t.sessionId){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId is required"});return}n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}})}async handleEdit(e){const n=this.opts.getRuntime(),t=e.params;if(!t.sessionId||!t.queueMessageId||!t.text){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId, queueMessageId and text are required"});return}const s=p(),a=s.sessions[t.sessionId]??[],i=a.findIndex(r=>r.id===t.queueMessageId);if(i<0){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}if(!T(a[i])||this.inFlightMessageIds.get(t.sessionId)?.has(t.queueMessageId)){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"managed_agent_queue_mutation_forbidden"});return}const o=C(t.attachments),d=n.resolvePath(a[i].workDir);o?.length&&L(d,o),a[i]={...a[i],text:t.text,attachments:o,updatedAt:P()},s.sessions[t.sessionId]=a,k(s),q(t.sessionId,w(a[i]),"queued"),a[i].attachments?.length&&b(d,a[i].attachments??[]),S(n.client),this.broadcast(t.sessionId),n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}})}async handleDelete(e){const n=this.opts.getRuntime(),t=e.params;if(!t.sessionId||!t.queueMessageId){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId and queueMessageId are required"});return}const s=p(),a=s.sessions[t.sessionId]??[],i=a.find(d=>d.id===t.queueMessageId);if(i&&(!T(i)||this.inFlightMessageIds.get(t.sessionId)?.has(t.queueMessageId))){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"managed_agent_queue_mutation_forbidden"});return}const o=a.filter(d=>d.id!==t.queueMessageId);if(o.length===a.length){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}o.length?s.sessions[t.sessionId]=o:delete s.sessions[t.sessionId],k(s),$(t.sessionId,i?.clientMessageId??i?.id??t.queueMessageId),S(n.client),this.broadcast(t.sessionId),n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}})}async handlePromote(e){const n=this.opts.getRuntime(),t=e.params,s=i=>n.client.sendRes({type:"res",id:e.id,ok:!1,error:i});if(!t.sessionId||!t.queueMessageId){s("sessionId and queueMessageId are required");return}if(this.promoting.has(t.sessionId)){s("queue_promotion_in_progress");return}this.promoting.add(t.sessionId);let a=[];try{for(;this.draining.has(t.sessionId);)await new Promise(d=>setTimeout(d,5));if(a=H(p().sessions[t.sessionId]??[],t.queueMessageId),!a.length){s("Queued message not found");return}this.inFlightMessageIds.set(t.sessionId,new Set(a.map(d=>d.id)));const i=n.sessions.get(t.sessionId);let o="idle";if(i?.currentRunId)if(this.canInjectActiveBatch(i,a)){const d=a.length===1?a[0].text:Q(a),r=a[0].clientMessageId??a[0].id;n.nativeFusion?.registerManagedSend({sessionId:t.sessionId,agentType:a[0].agentType,sourceAgentType:ne(a[0].agentType,a[0].modelId),canonicalMessageId:r,sourceSessionKey:i.agentSessionId,text:d,managedEchoPolicy:"suppress_following"});const g=Date.now();try{await i.adapter.injectActiveInput({text:d,attachments:B(a.flatMap(c=>c.attachments??[])),clientMessageId:r})}catch(c){throw n.nativeFusion?.cancelManagedSend(r),c}for(const c of a)q(t.sessionId,{...w(c),ts:g},"executing");this.removeBatch(t.sessionId,a),o="injected"}else{if(!this.opts.interruptQueuedSession)throw new Error("queue_interrupt_unavailable");await this.opts.interruptQueuedSession(t.sessionId),await this.dispatchQueuedMessages(a),this.removeBatch(t.sessionId,a),o="restarted"}else await this.dispatchQueuedMessages(a),this.removeBatch(t.sessionId,a);S(n.client),this.broadcast(t.sessionId),n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId),promotedMessageIds:a.map(d=>d.id),mode:o}})}catch(i){s(i instanceof Error?i.message:"queue_promotion_failed"),this.broadcast(t.sessionId)}finally{this.inFlightMessageIds.delete(t.sessionId),this.promoting.delete(t.sessionId),p().sessions[t.sessionId]?.length&&this.wakeSession(t.sessionId)}}noteTerminal(e){this.wakeSession(e)}purgeSession(e){te(e),this.retries.cancel(e)}discardPendingManagedRoomMessages(e){if(this.deletions.hasSession(e)){const o=p().sessions[e]?.length??0;return this.purgeSession(e),o}const n=p(),t=n.sessions[e]??[],s=this.inFlightMessageIds.get(e)??new Set,a=t.filter(o=>o.managedRoom&&!s.has(o.id));if(a.length===0)return 0;const i=t.filter(o=>!a.includes(o));i.length?n.sessions[e]=i:delete n.sessions[e],k(n);for(const o of a)$(e,o.clientMessageId??o.id);return S(this.opts.getRuntime().client),this.logLifecycle("managed-room.discarded-paused",e,{discardedCount:a.length,pendingCount:i.length}),this.broadcast(e),a.length}wakeSession(e){this.drainNext(e).catch(()=>{this.logLifecycle("drain.retry-required",e,{},"error"),this.retries.schedule(e,!0)})}drainIdleQueues(){if(this.disposed)return;const e=p();for(const n of Object.keys(e.sessions))this.wakeSession(n)}async drainNext(e){if(this.disposed||this.draining.has(e)||this.promoting.has(e))return;this.draining.add(e);let n=!1,t=!1,s=[];try{if(this.deletions.hasSession(e)){this.purgeSession(e);return}if(await this.isSessionBusy(e)){const g=p().sessions[e]?.length??0,c=this.deferredLogState.get(e),m=Date.now();(!c||c.pendingCount!==g||m-c.at>=3e4)&&(this.logLifecycle("drain.deferred",e,{pendingCount:g}),this.deferredLogState.set(e,{at:m,pendingCount:g}));return}this.deferredLogState.delete(e);const a=p(),i=a.sessions[e]??[],o=i.filter(g=>{const c=g.clientMessageId??g.id,m=v(e,c).state;return m==="queued"||m==="not_found"});o.length!==i.length&&(o.length?a.sessions[e]=o:delete a.sessions[e],k(a),this.logLifecycle("drain.pruned-terminal",e,{prunedCount:i.length-o.length,pendingCount:o.length}));const d=G(o);if(d.length===0){this.broadcast(e);return}s=d,this.inFlightMessageIds.set(e,new Set(d.map(g=>g.id))),this.logLifecycle("dispatch.start",e,{batchCount:d.length,queueMessageId:d[0].id,origin:d[0].origin??"app"});for(const g of d)q(e,w(g),"queued");await this.dispatchQueuedMessages(d);const r=this.removeBatch(e,d);this.logLifecycle("dispatch.accepted",e,{batchCount:d.length,queueMessageId:d[0].id,pendingCount:r}),this.deferredLogState.delete(e),n=r>0,this.broadcast(e)}catch(a){const i=F(a instanceof Error?a.message:String(a),a instanceof A?"terminal_dispatch_failed":"transient_dispatch_failed"),o=s[0],d=!!(o&&!o.trigger&&!o.clientMessageId&&Date.now()-Date.parse(o.createdAt)>=10*6e4&&this.opts.getRuntime().managerRuntime?.isManagedWorkerSession(e));if(a instanceof A||i==="managed_claim_source_binding_conflict"||d){const g=this.removeBatch(e,s);this.logLifecycle("dispatch.terminal-failure",e,{batchCount:s.length,queueMessageId:s[0]?.id,pendingCount:g,failureClass:d?"stale_legacy_managed_worker_dispatch_failure":i},"error"),n=g>0}else t=!0,this.logLifecycle("dispatch.transient-failure",e,{failureClass:i},"error");this.broadcast(e)}finally{this.inFlightMessageIds.delete(e),this.draining.delete(e),p().sessions[e]?.length?n?queueMicrotask(()=>this.wakeSession(e)):this.retries.schedule(e,t):this.retries.cancel(e)}}canInjectActiveBatch(e,n){if(!e.adapter.supportsActiveInput||!e.adapter.injectActiveInput||!e.journalRunId)return!1;const t=this.opts.getRuntime().runJournal?.get(n[0].sessionId,e.journalRunId),s=n[0].trigger?.kind==="managed_direct"||n[0].managedAgentDirect?null:"personal";return!!(s&&n[0].systemPrompt==null&&t&&t.source===s&&t.deliveryMode==="direct"&&t.agentType===n[0].agentType&&t.workDir===e.workDir&&t.agentSessionId===e.agentSessionId&&t.modelId===(n[0].modelId??null)&&t.reasoningEffort===(n[0].reasoningEffort??null))}removeBatch(e,n){if(n.length===0)return p().sessions[e]?.length??0;const t=p(),s=new Set(n.map(i=>i.id)),a=(t.sessions[e]??[]).filter(i=>!s.has(i.id));return a.length?t.sessions[e]=a:delete t.sessions[e],k(t),a.length}logLifecycle(e,n,t,s="info"){const a=`[chat.queue] ${JSON.stringify({at:P(),event:e,sessionId:n,...t})}`;s==="error"?console.error(a):console.info(a)}async dispatchQueuedMessages(e){const n=e[0],t=e.flatMap(l=>{if(l.trigger?.kind==="room_activation"){const{kind:y,...f}=l.trigger;return[f]}return!l.trigger&&l.managedRoom?[l.managedRoom]:[]}),s=e.find(l=>l.trigger?.kind==="manager_event")?.trigger,a=e.some(l=>l.trigger?.kind==="managed_direct"||!!l.managedAgentDirect),i=e.find(l=>l.trigger?.kind==="manager_worker")?.trigger,o=t.length>0?await this.prepareManagedRoomTurn?.(t):a||s?.kind==="manager_event"?await this.prepareManagedAgentDirectTurn?.(n.sessionId):void 0;if(t.length>0&&!o)throw new Error("managed_room_turn_preparer_unavailable");if(a&&!o)throw new Error("managed_agent_context_unavailable");if(s?.kind==="manager_event"&&this.opts.getRuntime().managerRuntime?.isManagedRoomBoundManager(n.sessionId)&&!o)throw new Error("managed_agent_context_unavailable");const d=new Set((p().sessions[n.sessionId]??[]).map(l=>l.id));if(this.disposed||this.deletions.hasSession(n.sessionId)||e.some(l=>!d.has(l.id)))throw o?.managedRoomContextToken&&this.opts.getRuntime().revokeManagedRoomContextToken?.(o.managedRoomContextToken),this.disposed?new Error("queue_stopped"):new A("queued_dispatch_cancelled");s?.kind==="manager_event"&&o?.managedRoomContextToken&&this.opts.getRuntime().revokeManagedRoomContextToken?.(o.managedRoomContextToken);const r=o??null,g=r?.sessionMode==="manager"?r.managerConfig:null,c=e.length===1?n.text:Q(e),m=B(e.flatMap(l=>l.attachments??[])),I=a||s?.kind==="manager_event"&&!!o,h=I?o.agentSessionId:t.length>0?W(this.opts.getRuntime(),n):n.agentSessionId??null,M=o?N().find(l=>l.sessionId===n.sessionId):null;await this.opts.dispatchQueuedReq({type:"req",id:`queue-send-${n.id}-${Date.now()}`,method:"chat.send",params:{...R(_()),sessionId:n.sessionId,text:c,agentType:I?o.agentType:n.agentType,sessionMode:r?.sessionMode??n.sessionMode,managerConfig:r?g:n.managerConfig,workDir:I?o.workDir:n.workDir,agentSessionId:h,modelId:x(M?.modelId)??x(n.modelId),systemPrompt:s?.kind==="manager_event"?null:o?.systemPrompt??n.systemPrompt??null,managerDefaultWorkerAgentType:r?r.managerDefaultWorkerAgentType:n.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:r?r.managerDefaultWorkerModelId:n.managerDefaultWorkerModelId,reasoningEffort:n.reasoningEffort??void 0,clientMessageId:n.clientMessageId??n.id,attachments:m,externalChannel:s?.kind==="manager_event"?void 0:n.externalChannel,replyTarget:s?.kind==="manager_event"?void 0:n.replyTarget,roomContextRegistryKey:s?.kind==="manager_event"?void 0:o?.roomContextRegistryKey,activationCauseMessageId:s?.kind==="manager_event"?void 0:t.at(-1)?.causeMessageId,rootCauseMessageId:s?.kind==="manager_event"?void 0:t.at(-1)?.rootCauseMessageId,activationSequence:s?.kind==="manager_event"?void 0:t.at(-1)?.sequence,activationHopCount:s?.kind==="manager_event"?void 0:t.at(-1)?.hopCount,managedRecoverySource:s?.kind==="manager_event"?"manager_event":a?"managed_direct":t.length>0?"managed_room":void 0,managerEventId:s?.kind==="manager_event"?s.eventId:void 0,managedWorkerParentSessionId:i?.kind==="manager_worker"?i.parentSessionId:void 0,managerWorkerNativeFull:i?.kind==="manager_worker"&&this.opts.getRuntime().managerRuntime?.isNativeSkillManager(i.parentSessionId,n.workDir)||void 0,managedRoomContextToken:s?.kind==="manager_event"?void 0:o?.managedRoomContextToken,suppressExternalOwnerEcho:t.length>0||void 0,queuedBatchItems:e.map(l=>{const y=D(l);return s?.kind==="manager_event"?{...y,systemPrompt:null,externalChannel:null,replyTarget:null}:y}),waitForDispatch:!0}},o??void 0)}async isSessionBusy(e){const n=this.opts.getRuntime().recoveryBarrier;if(n?.isDispatchFrozen()||n?.isRecovering(e))return!0;const t=this.opts.getRuntime().sessions.get(e);if(!t)return!1;if(t.currentRunId){if(!(t.currentRunId.startsWith("dispatch:")&&!!this.opts.getRuntime().runJournal&&!t.journalRunId)||(t.adapter.getStatus?await t.adapter.getStatus().catch(()=>null):null)?.active)return!0;this.logLifecycle("drain.recovered-stale-dispatch",e,{staleRunId:t.currentRunId}),t.currentRunId=null,t.currentRunPhase=null}if(!t.adapter.getStatus)return!1;const s=await t.adapter.getStatus().catch(()=>null);return s?.active?(s.runId&&(t.currentRunId=s.runId,t.currentRunPhase=s.runPhase??t.currentRunPhase),!0):!1}broadcast(e){this.opts.getRuntime().client.sendEvent({type:"event",event:"session.queue.update",payload:{queue:this.getSnapshot(e)}})}}export{ye as ChatQueueManager,A as TerminalQueuedChatDispatchError,Se as purgeQueuedSessionData};
|
|
@@ -3,6 +3,7 @@ import type { SessionManagerRuntime } from './types.js';
|
|
|
3
3
|
import { type ChatSendExecutionContext } from './handlers/chat.js';
|
|
4
4
|
import { type PreparedManagedRoomTurn } from './queue.js';
|
|
5
5
|
export declare function managedChatContext(prepared: PreparedManagedRoomTurn): ChatSendExecutionContext;
|
|
6
|
+
export declare function managedManagerEventContext(prepared: PreparedManagedRoomTurn): ChatSendExecutionContext;
|
|
6
7
|
export declare function dispatchManagedChat(runtime: SessionManagerRuntime, req: ReqFrame, prepared: PreparedManagedRoomTurn): Promise<void>;
|
|
7
8
|
export declare function dispatchQueuedChat(runtime: SessionManagerRuntime, req: ReqFrame, prepared?: PreparedManagedRoomTurn): Promise<void>;
|
|
8
9
|
export declare function interruptQueuedChat(runtime: SessionManagerRuntime, sessionId: string): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{handleChatAbort as
|
|
1
|
+
import{handleChatAbort as m,handleChatSend as u}from"./handlers/chat.js";import{TerminalQueuedChatDispatchError as s}from"./queue.js";function d(e){return{source:"room_activation",managedAgentPolicy:e.managedAgentPolicy,managedRoomPromptSnapshot:e.managedRoomPromptSnapshot}}function h(e){return{source:"manager_event",managedAgentPolicy:e.managedAgentPolicy,managedRoomPolicyVersion:e.managedRoomPromptSnapshot.managedRoomPolicyVersion,managedRuntimePolicyHash:e.managedRoomPromptSnapshot.runtimePermissionPolicyHash}}async function v(e,i,a){await u(e,i,d(a))}async function y(e,i,a){const o={value:null},n=new Proxy(e.client,{get:(t,l)=>{if(l==="sendRes")return r=>{o.value=r};const c=Reflect.get(t,l,t);return typeof c=="function"?c.bind(t):c}});if(await u({...e,client:n},i,a?i.params.managedRecoverySource==="manager_event"?h(a):d(a):void 0),o.value?.payload?.deliveryState==="failed")throw new s(o.value.payload.failureMessage??o.value.error??"queued_chat_dispatch_failed");if(!o.value?.ok)throw new Error(o.value?.error??"queued_chat_dispatch_failed")}async function P(e,i){const a={value:null},o=new Proxy(e.client,{get:(n,t)=>t==="sendRes"?l=>{a.value=l}:typeof Reflect.get(n,t,n)=="function"?Reflect.get(n,t,n).bind(n):Reflect.get(n,t,n)});if(await m({...e,client:o},{type:"req",id:`queue-promote-abort-${Date.now()}`,method:"chat.abort",params:{sessionId:i,discardPending:!1}}),!a.value?.ok)throw new Error(a.value?.error??"agent_stop_failed")}export{v as dispatchManagedChat,y as dispatchQueuedChat,P as interruptQueuedChat,d as managedChatContext,h as managedManagerEventContext};
|