shennian 0.4.44 → 0.4.46

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.
@@ -906,8 +906,8 @@
906
906
  },
907
907
  {
908
908
  "file": "src/session/queue.js",
909
- "beforeBytes": 40646,
910
- "afterBytes": 18459
909
+ "beforeBytes": 41756,
910
+ "afterBytes": 18890
911
911
  },
912
912
  {
913
913
  "file": "src/session/queued-chat-dispatch.js",
@@ -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 P,stripLocalQueueFields as _,takeCompatibleBatch as K,formatQueuedBatch as E,dedupeQueuedAttachments as Q,classifyQueueError as q,isControllableQueueMessage as C,takeControllablePrefix as G,resolveManagedRoomNativeSessionId as B}from"./queue-helpers.js";import{randomUUID as H}from"node:crypto";import{CODEX_DESKTOP_SEND_UNAVAILABLE as V,buildUserMessagePayload as X}from"@shennian/wire";import{isCodexThreadTailInterrupted as Y}from"../native-fusion/codex-files.js";import{mergeProjectedSessions as Z}from"./projection.js";import{parsePersonalAttachmentReferences as x}from"./attachment-reference.js";import{assertFinishedAttachmentReferences as L,markAttachmentReferences as W}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 b,getMessageDeliveryStatus as v,listSessionRecords as F,recordSession as j,tombstoneMessage as N,upsertMessage as T}from"./store.js";import{requestSessionIndexPublish as S}from"./index-publisher.js";import{purgeQueuedSessionData as ee,readQueue as m,writeQueue as M}from"./queue-storage.js";import{purgeQueuedSessionData as ye}from"./queue-storage.js";class $ extends Error{constructor(e){super(e),this.name="TerminalQueuedChatDispatchError"}}function A(){return new Date().toISOString()}function z(u){const e=A(),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-${H()}`,sessionId:u.sessionId,text:u.text,agentType:u.agentType,sessionMode:u.sessionMode,managerConfig:u.managerConfig,workDir:u.workDir,agentSessionId:u.agentSessionId??null,modelId:u.modelId??null,systemPrompt:u.systemPrompt??null,managerDefaultWorkerAgentType:u.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:u.managerDefaultWorkerModelId??null,reasoningEffort:u.reasoningEffort??null,clientMessageId:u.clientMessageId??null,attachments:x(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:X(u.text,u.attachments)}}class pe{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=m().sessions[e]??[];return{...R(D()),sessionId:e,busy:!!this.opts.getRuntime().sessions.get(e)?.currentRunId,pending:s.map(_),controllablePending:s.filter(C).map(_)}}setManagedRoomTurnPreparer(e){this.prepareManagedRoomTurn=e}setManagedAgentDirectTurnPreparer(e){this.prepareManagedAgentDirectTurn=e}async enqueueManagedRoomBatch(e){if(e.length===0)return;const s=this.opts.getRuntime(),t=m(),o=new Set,i=new Map;let n=!1;for(const a of e){const d=a.params;if(this.deletions.hasSession(d.sessionId)){delete t.sessions[d.sessionId],n=!0;continue}if(!d.sessionId||!d.text||!d.agentType||!d.workDir)throw new Error("sessionId, text, agentType and workDir are required");const g=z({...R(D()),...d});g.origin="room",g.managedRoom=a.context,g.trigger={kind:"room_activation",...a.context};const p=t.sessions[d.sessionId]??[],c=P(p,g),I=g.clientMessageId??g.id,h=v(d.sessionId,I);if(!c&&h.state==="not_found")p.push(g),t.sessions[d.sessionId]=p,i.set(`${g.sessionId}\0${I}`,g),o.add(d.sessionId),n=!0;else if(c&&h.state==="not_found")i.set(`${c.sessionId}\0${I}`,c),o.add(d.sessionId);else if(c&&h.state==="queued")o.add(d.sessionId);else if(c){const l=p.filter(k=>k.id!==c.id);l.length?t.sessions[d.sessionId]=l:delete t.sessions[d.sessionId],n=!0}}n&&M(t);let r;for(const a of o)try{for(const d of i.values())d.sessionId===a&&(b(a,w(d),"queued"),j({sessionId:a,agentType:d.agentType,sessionMode:d.sessionMode,managerConfig:d.managerConfig,workDir:d.workDir,agentSessionId:B(s,d),modelId:d.modelId}));this.logLifecycle("enqueue.persisted",a,{origin:"room",pendingCount:m().sessions[a]?.length??0}),this.broadcast(a),this.wakeSession(a)}catch(d){r=d,this.logLifecycle("enqueue.commit-failed",a,{failureClass:q(d instanceof Error?d.message:"","canonical_commit_failed")},"error"),this.retries.schedule(a,!0)}if(o.size&&S(s.client),r)throw r}async handleEnqueue(e){const s=this.opts.getRuntime(),t=e.params;if(Z(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?F().find(O=>O.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),a=x(n.attachments);a?.length&&L(r,a);const d={text:n.text,attachments:a,localized:!1},g=s.sessions.get(n.sessionId);if(n.agentType==="codex"&&n.agentSessionId&&g?.currentRunId?.startsWith("dispatch:")&&Y(n.agentSessionId))return s.client.sendRes({type:"res",id:e.id,ok:!1,error:V}),!1;o?.managedRoomContextToken&&s.revokeManagedRoomContextToken?.(o.managedRoomContextToken);const p=await this.isSessionBusy(n.sessionId),c=m(),I=c.sessions[n.sessionId]??[],h=z({...n,text:d.text,attachments:d.attachments,systemPrompt:o?null:n.systemPrompt});o&&(h.managedAgentDirect=!0,h.trigger?.kind!=="manager_event"&&(h.trigger={kind:"managed_direct"}));const l=h.clientMessageId??h.id,k=v(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:h.id,...k,queue:this.getSnapshot(n.sessionId)}}),!0;const y=P(I,h);y||(c.sessions[n.sessionId]=[...I,h],M(c));const f=y??h;return j({sessionId:f.sessionId,agentType:f.agentType,sessionMode:f.sessionMode,managerConfig:f.managerConfig,workDir:f.workDir,agentSessionId:f.agentSessionId,modelId:f.modelId}),b(f.sessionId,w(f),"queued"),W(s.resolvePath(f.workDir),f.attachments??[]),S(s.client),this.logLifecycle("enqueue.persisted",n.sessionId,{origin:n.origin??"app",queueMessageId:f.id,pendingCount:(m().sessions[n.sessionId]??[]).length}),this.broadcast(n.sessionId),s.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queued:!0,queueMessageId:y?.id??h.id,...v(n.sessionId,y?.clientMessageId??y?.id??h.clientMessageId??h.id),queue:this.getSnapshot(n.sessionId),...d.localized?{localizedAttachments:!0}:{}}}),p?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=m(),i=o.sessions[t.sessionId]??[],n=i.findIndex(d=>d.id===t.queueMessageId);if(n<0){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}if(!C(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=x(t.attachments),a=s.resolvePath(i[n].workDir);r?.length&&L(a,r),i[n]={...i[n],text:t.text,attachments:r,updatedAt:A()},o.sessions[t.sessionId]=i,M(o),T(t.sessionId,w(i[n]),"queued"),i[n].attachments?.length&&W(a,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=m(),i=o.sessions[t.sessionId]??[],n=i.find(a=>a.id===t.queueMessageId);if(n&&(!C(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(a=>a.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),N(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(a=>setTimeout(a,5));if(i=G(m().sessions[t.sessionId]??[],t.queueMessageId),!i.length){o("Queued message not found");return}this.inFlightMessageIds.set(t.sessionId,new Set(i.map(a=>a.id)));const n=s.sessions.get(t.sessionId);let r="idle";if(n?.currentRunId)if(this.canInjectActiveBatch(n,i)){await n.adapter.injectActiveInput({text:i.length===1?i[0].text:E(i),attachments:Q(i.flatMap(a=>a.attachments??[])),clientMessageId:i[0].clientMessageId??i[0].id});for(const a of i)T(t.sessionId,w(a),"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(a=>a.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),m().sessions[t.sessionId]?.length&&this.wakeSession(t.sessionId)}}noteTerminal(e){this.wakeSession(e)}purgeSession(e){ee(e),this.retries.cancel(e)}discardPendingManagedRoomMessages(e){if(this.deletions.hasSession(e)){const r=m().sessions[e]?.length??0;return this.purgeSession(e),r}const s=m(),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)N(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=m();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=m().sessions[e]?.length??0,p=this.deferredLogState.get(e),c=Date.now();(!p||p.pendingCount!==g||c-p.at>=3e4)&&(this.logLifecycle("drain.deferred",e,{pendingCount:g}),this.deferredLogState.set(e,{at:c,pendingCount:g}));return}this.deferredLogState.delete(e);const i=m(),n=i.sessions[e]??[],r=n.filter(g=>{const p=g.clientMessageId??g.id,c=v(e,p).state;return c==="queued"||c==="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 a=K(r);if(a.length===0){this.broadcast(e);return}o=a,this.inFlightMessageIds.set(e,new Set(a.map(g=>g.id))),this.logLifecycle("dispatch.start",e,{batchCount:a.length,queueMessageId:a[0].id,origin:a[0].origin??"app"});for(const g of a)T(e,w(g),"queued");await this.dispatchQueuedMessages(a);const d=this.removeBatch(e,a);this.logLifecycle("dispatch.accepted",e,{batchCount:a.length,queueMessageId:a[0].id,pendingCount:d}),this.deferredLogState.delete(e),s=d>0,this.broadcast(e)}catch(i){if(i instanceof $){const n=this.removeBatch(e,o);this.logLifecycle("dispatch.terminal-failure",e,{batchCount:o.length,queueMessageId:o[0]?.id,pendingCount:n,failureClass:q(i.message,"terminal_dispatch_failed")},"error"),s=n>0}else t=!0,this.logLifecycle("dispatch.transient-failure",e,{failureClass:q(i instanceof Error?i.message:String(i),"transient_dispatch_failed")},"error");this.broadcast(e)}finally{this.inFlightMessageIds.delete(e),this.draining.delete(e),m().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 m().sessions[e]?.length??0;const t=m(),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:A(),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 a=new Set((m().sessions[s.sessionId]??[]).map(l=>l.id));if(this.disposed||this.deletions.hasSession(s.sessionId)||e.some(l=>!a.has(l.id)))throw r?.managedRoomContextToken&&this.opts.getRuntime().revokeManagedRoomContextToken?.(r.managedRoomContextToken),this.disposed?new Error("queue_stopped"):new $("queued_dispatch_cancelled");const d=r??null,g=d?.sessionMode==="manager"?d.managerConfig:null,p=e.length===1?s.text:E(e),c=Q(e.flatMap(l=>l.attachments??[])),I=o?r.agentSessionId:t.length>0?B(this.opts.getRuntime(),s):s.agentSessionId??null,h=r?F().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:p,agentType:o?r.agentType:s.agentType,sessionMode:d?.sessionMode??s.sessionMode,managerConfig:d?g:s.managerConfig,workDir:o?r.workDir:s.workDir,agentSessionId:I,modelId:h?.modelId??s.modelId??void 0,systemPrompt:r?.systemPrompt??s.systemPrompt??null,managerDefaultWorkerAgentType:d?d.managerDefaultWorkerAgentType:s.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:d?d.managerDefaultWorkerModelId:s.managerDefaultWorkerModelId,reasoningEffort:s.reasoningEffort??void 0,clientMessageId:s.clientMessageId??s.id,attachments:c,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(_),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{pe as ChatQueueManager,$ as TerminalQueuedChatDispatchError,ye as purgeQueuedSessionData};
1
+ import{NativeSourceDeletionStore as K}from"../native-fusion/native-source-deletions.js";import{QueueRetryScheduler as U}from"./queue-retry.js";import{findDuplicateQueuedMessage as P,stripLocalQueueFields as _,takeCompatibleBatch as J,formatQueuedBatch as E,dedupeQueuedAttachments as Q,classifyQueueError as T,isControllableQueueMessage as q,takeControllablePrefix as G,resolveManagedRoomNativeSessionId as B}from"./queue-helpers.js";import{randomUUID as H}from"node:crypto";import{CODEX_DESKTOP_SEND_UNAVAILABLE as V,buildUserMessagePayload as X}from"@shennian/wire";import{isCodexThreadTailInterrupted as Y}from"../native-fusion/codex-files.js";import{mergeProjectedSessions as Z}from"./projection.js";import{parsePersonalAttachmentReferences as x}from"./attachment-reference.js";import{assertFinishedAttachmentReferences as L,markAttachmentReferences as W}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 F,getMessageDeliveryStatus as v,listSessionRecords as b,recordSession as j,tombstoneMessage as N,upsertMessage as C}from"./store.js";import{requestSessionIndexPublish as S}from"./index-publisher.js";import{purgeQueuedSessionData as ee,readQueue as m,writeQueue as M}from"./queue-storage.js";import{getNativeSourceAgentType as te}from"./handlers/chat-events.js";import{purgeQueuedSessionData as ke}from"./queue-storage.js";class $ extends Error{constructor(e){super(e),this.name="TerminalQueuedChatDispatchError"}}function A(){return new Date().toISOString()}function z(u){const e=A(),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-${H()}`,sessionId:u.sessionId,text:u.text,agentType:u.agentType,sessionMode:u.sessionMode,managerConfig:u.managerConfig,workDir:u.workDir,agentSessionId:u.agentSessionId??null,modelId:u.modelId??null,systemPrompt:u.systemPrompt??null,managerDefaultWorkerAgentType:u.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:u.managerDefaultWorkerModelId??null,reasoningEffort:u.reasoningEffort??null,clientMessageId:u.clientMessageId??null,attachments:x(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:X(u.text,u.attachments)}}class Ie{opts;draining=new Set;promoting=new Set;disposed=!1;retries=new U(e=>{this.wakeSession(e)});deletions=new K;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=m().sessions[e]??[],t=this.inFlightMessageIds.get(e);return{...R(D()),sessionId:e,busy:!!this.opts.getRuntime().sessions.get(e)?.currentRunId,pending:s.map(_),controllablePending:s.filter(o=>q(o)&&!t?.has(o.id)).map(_)}}setManagedRoomTurnPreparer(e){this.prepareManagedRoomTurn=e}setManagedAgentDirectTurnPreparer(e){this.prepareManagedAgentDirectTurn=e}async enqueueManagedRoomBatch(e){if(e.length===0)return;const s=this.opts.getRuntime(),t=m(),o=new Set,i=new Map;let n=!1;for(const d of e){const r=d.params;if(this.deletions.hasSession(r.sessionId)){delete t.sessions[r.sessionId],n=!0;continue}if(!r.sessionId||!r.text||!r.agentType||!r.workDir)throw new Error("sessionId, text, agentType and workDir are required");const g=z({...R(D()),...r});g.origin="room",g.managedRoom=d.context,g.trigger={kind:"room_activation",...d.context};const p=t.sessions[r.sessionId]??[],c=P(p,g),I=g.clientMessageId??g.id,h=v(r.sessionId,I);if(!c&&h.state==="not_found")p.push(g),t.sessions[r.sessionId]=p,i.set(`${g.sessionId}\0${I}`,g),o.add(r.sessionId),n=!0;else if(c&&h.state==="not_found")i.set(`${c.sessionId}\0${I}`,c),o.add(r.sessionId);else if(c&&h.state==="queued")o.add(r.sessionId);else if(c){const l=p.filter(k=>k.id!==c.id);l.length?t.sessions[r.sessionId]=l:delete t.sessions[r.sessionId],n=!0}}n&&M(t);let a;for(const d of o)try{for(const r of i.values())r.sessionId===d&&(F(d,w(r),"queued"),j({sessionId:d,agentType:r.agentType,sessionMode:r.sessionMode,managerConfig:r.managerConfig,workDir:r.workDir,agentSessionId:B(s,r),modelId:r.modelId}));this.logLifecycle("enqueue.persisted",d,{origin:"room",pendingCount:m().sessions[d]?.length??0}),this.broadcast(d),this.wakeSession(d)}catch(r){a=r,this.logLifecycle("enqueue.commit-failed",d,{failureClass:T(r instanceof Error?r.message:"","canonical_commit_failed")},"error"),this.retries.schedule(d,!0)}if(o.size&&S(s.client),a)throw a}async handleEnqueue(e){const s=this.opts.getRuntime(),t=e.params;if(Z(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?b().find(O=>O.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,a=s.resolvePath(n.workDir),d=x(n.attachments);d?.length&&L(a,d);const r={text:n.text,attachments:d,localized:!1},g=s.sessions.get(n.sessionId);if(n.agentType==="codex"&&n.agentSessionId&&g?.currentRunId?.startsWith("dispatch:")&&Y(n.agentSessionId))return s.client.sendRes({type:"res",id:e.id,ok:!1,error:V}),!1;o?.managedRoomContextToken&&s.revokeManagedRoomContextToken?.(o.managedRoomContextToken);const p=await this.isSessionBusy(n.sessionId),c=m(),I=c.sessions[n.sessionId]??[],h=z({...n,text:r.text,attachments:r.attachments,systemPrompt:o?null:n.systemPrompt});o&&(h.managedAgentDirect=!0,h.trigger?.kind!=="manager_event"&&(h.trigger={kind:"managed_direct"}));const l=h.clientMessageId??h.id,k=v(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:h.id,...k,queue:this.getSnapshot(n.sessionId)}}),!0;const y=P(I,h);y||(c.sessions[n.sessionId]=[...I,h],M(c));const f=y??h;return j({sessionId:f.sessionId,agentType:f.agentType,sessionMode:f.sessionMode,managerConfig:f.managerConfig,workDir:f.workDir,agentSessionId:f.agentSessionId,modelId:f.modelId}),F(f.sessionId,w(f),"queued"),W(s.resolvePath(f.workDir),f.attachments??[]),S(s.client),this.logLifecycle("enqueue.persisted",n.sessionId,{origin:n.origin??"app",queueMessageId:f.id,pendingCount:(m().sessions[n.sessionId]??[]).length}),this.broadcast(n.sessionId),s.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queued:!0,queueMessageId:y?.id??h.id,...v(n.sessionId,y?.clientMessageId??y?.id??h.clientMessageId??h.id),queue:this.getSnapshot(n.sessionId),...r.localized?{localizedAttachments:!0}:{}}}),p?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=m(),i=o.sessions[t.sessionId]??[],n=i.findIndex(r=>r.id===t.queueMessageId);if(n<0){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}if(!q(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 a=x(t.attachments),d=s.resolvePath(i[n].workDir);a?.length&&L(d,a),i[n]={...i[n],text:t.text,attachments:a,updatedAt:A()},o.sessions[t.sessionId]=i,M(o),C(t.sessionId,w(i[n]),"queued"),i[n].attachments?.length&&W(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=m(),i=o.sessions[t.sessionId]??[],n=i.find(d=>d.id===t.queueMessageId);if(n&&(!q(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 a=i.filter(d=>d.id!==t.queueMessageId);if(a.length===i.length){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}a.length?o.sessions[t.sessionId]=a:delete o.sessions[t.sessionId],M(o),N(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=G(m().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 a="idle";if(n?.currentRunId)if(this.canInjectActiveBatch(n,i)){const d=i.length===1?i[0].text:E(i),r=i[0].clientMessageId??i[0].id;s.nativeFusion?.registerManagedSend({sessionId:t.sessionId,agentType:i[0].agentType,sourceAgentType:te(i[0].agentType,i[0].modelId),canonicalMessageId:r,sourceSessionKey:n.agentSessionId,text:d,managedEchoPolicy:"suppress_following"});try{await n.adapter.injectActiveInput({text:d,attachments:Q(i.flatMap(g=>g.attachments??[])),clientMessageId:r})}catch(g){throw s.nativeFusion?.cancelManagedSend(r),g}for(const g of i)C(t.sessionId,w(g),"executing");this.removeBatch(t.sessionId,i),a="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),a="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:a}})}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),m().sessions[t.sessionId]?.length&&this.wakeSession(t.sessionId)}}noteTerminal(e){this.wakeSession(e)}purgeSession(e){ee(e),this.retries.cancel(e)}discardPendingManagedRoomMessages(e){if(this.deletions.hasSession(e)){const a=m().sessions[e]?.length??0;return this.purgeSession(e),a}const s=m(),t=s.sessions[e]??[],o=this.inFlightMessageIds.get(e)??new Set,i=t.filter(a=>a.managedRoom&&!o.has(a.id));if(i.length===0)return 0;const n=t.filter(a=>!i.includes(a));n.length?s.sessions[e]=n:delete s.sessions[e],M(s);for(const a of i)N(e,a.clientMessageId??a.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=m();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=m().sessions[e]?.length??0,p=this.deferredLogState.get(e),c=Date.now();(!p||p.pendingCount!==g||c-p.at>=3e4)&&(this.logLifecycle("drain.deferred",e,{pendingCount:g}),this.deferredLogState.set(e,{at:c,pendingCount:g}));return}this.deferredLogState.delete(e);const i=m(),n=i.sessions[e]??[],a=n.filter(g=>{const p=g.clientMessageId??g.id,c=v(e,p).state;return c==="queued"||c==="not_found"});a.length!==n.length&&(a.length?i.sessions[e]=a:delete i.sessions[e],M(i),this.logLifecycle("drain.pruned-terminal",e,{prunedCount:n.length-a.length,pendingCount:a.length}));const d=J(a);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)C(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),s=r>0,this.broadcast(e)}catch(i){if(i instanceof $){const n=this.removeBatch(e,o);this.logLifecycle("dispatch.terminal-failure",e,{batchCount:o.length,queueMessageId:o[0]?.id,pendingCount:n,failureClass:T(i.message,"terminal_dispatch_failed")},"error"),s=n>0}else t=!0,this.logLifecycle("dispatch.transient-failure",e,{failureClass:T(i instanceof Error?i.message:String(i),"transient_dispatch_failed")},"error");this.broadcast(e)}finally{this.inFlightMessageIds.delete(e),this.draining.delete(e),m().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 m().sessions[e]?.length??0;const t=m(),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:A(),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,a=t.length>0?await this.prepareManagedRoomTurn?.(t):o?await this.prepareManagedAgentDirectTurn?.(s.sessionId):void 0;if(t.length>0&&!a)throw new Error("managed_room_turn_preparer_unavailable");if(o&&!a)throw new Error("managed_agent_context_unavailable");const d=new Set((m().sessions[s.sessionId]??[]).map(l=>l.id));if(this.disposed||this.deletions.hasSession(s.sessionId)||e.some(l=>!d.has(l.id)))throw a?.managedRoomContextToken&&this.opts.getRuntime().revokeManagedRoomContextToken?.(a.managedRoomContextToken),this.disposed?new Error("queue_stopped"):new $("queued_dispatch_cancelled");const r=a??null,g=r?.sessionMode==="manager"?r.managerConfig:null,p=e.length===1?s.text:E(e),c=Q(e.flatMap(l=>l.attachments??[])),I=o?a.agentSessionId:t.length>0?B(this.opts.getRuntime(),s):s.agentSessionId??null,h=a?b().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:p,agentType:o?a.agentType:s.agentType,sessionMode:r?.sessionMode??s.sessionMode,managerConfig:r?g:s.managerConfig,workDir:o?a.workDir:s.workDir,agentSessionId:I,modelId:h?.modelId??s.modelId??void 0,systemPrompt:a?.systemPrompt??s.systemPrompt??null,managerDefaultWorkerAgentType:r?r.managerDefaultWorkerAgentType:s.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:r?r.managerDefaultWorkerModelId:s.managerDefaultWorkerModelId,reasoningEffort:s.reasoningEffort??void 0,clientMessageId:s.clientMessageId??s.id,attachments:c,externalChannel:s.externalChannel,replyTarget:s.replyTarget,roomContextRegistryKey:a?.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:a?.managedRoomContextToken,suppressExternalOwnerEcho:t.length>0||void 0,queuedBatchItems:e.map(_),waitForDispatch:!0}},a??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{Ie as ChatQueueManager,$ as TerminalQueuedChatDispatchError,ke as purgeQueuedSessionData};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shennian",
3
- "version": "0.4.44",
3
+ "version": "0.4.46",
4
4
  "description": "Shennian — AI Agent Control Plane CLI",
5
5
  "type": "module",
6
6
  "bin": {