shennian 0.3.52 → 0.3.53

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.
@@ -591,8 +591,8 @@
591
591
  },
592
592
  {
593
593
  "file": "src/session/queue.js",
594
- "beforeBytes": 17581,
595
- "afterBytes": 8290
594
+ "beforeBytes": 18893,
595
+ "afterBytes": 8613
596
596
  },
597
597
  {
598
598
  "file": "src/session/store.js",
@@ -1,5 +1,5 @@
1
- import c from"node:fs";import{randomUUID as p}from"node:crypto";import{buildUserMessagePayload as R}from"@shennian/wire";import{mergeProjectedSessions as T}from"./projection.js";import{parsePersonalAttachmentReferences as m}from"./attachment-reference.js";import{assertFinishedAttachmentReferences as S,markAttachmentReferences as k}from"./attachment-transfer-store.js";import{resolvePersonalSyncV1Path as q}from"../personal-sync/paths.js";import{appendMessage as b,getMessageDeliveryStatus as v,recordSession as E,tombstoneMessage as P,upsertMessage as w}from"./store.js";import{requestSessionIndexPublish as y}from"./index-publisher.js";function A(){return{sessions:{}}}function I(){return new Date().toISOString()}function $(s){return`enqueue-send-${s}-${p()}`}function l(){try{const s=JSON.parse(c.readFileSync(D(),"utf-8"));return{sessions:s.sessions&&typeof s.sessions=="object"?s.sessions:{}}}catch(s){if(s.code==="ENOENT"||s instanceof Error&&s.message.startsWith("ENOENT:"))return A();throw s}}function h(s){const e=q();c.mkdirSync(e,{recursive:!0,mode:448});const n=D(),t=`${n}.tmp-${process.pid}-${p()}`;try{c.writeFileSync(t,`${JSON.stringify(s,null,2)}
2
- `,{encoding:"utf8",mode:384,flag:"wx"});const i=c.openSync(t,"r");if(typeof i=="number")try{c.fsyncSync(i)}finally{c.closeSync(i)}c.renameSync(t,n)}finally{try{c.unlinkSync(t)}catch{}}}function D(){return q("outbound-queue.json")}function Q(s){const e=I();return{id:s.queueMessageId||s.clientMessageId||`queue-${p()}`,sessionId:s.sessionId,text:s.text,agentType:s.agentType,sessionMode:s.sessionMode,managerConfig:s.managerConfig,workDir:s.workDir,agentSessionId:s.agentSessionId??null,modelId:s.modelId??null,systemPrompt:s.systemPrompt??null,managerDefaultWorkerAgentType:s.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:s.managerDefaultWorkerModelId??null,reasoningEffort:s.reasoningEffort??null,clientMessageId:s.clientMessageId??null,attachments:m(s.attachments),externalChannel:s.externalChannel??null,replyTarget:s.replyTarget??null,origin:s.origin,createdAt:e,updatedAt:e}}function M(s){return{id:s.clientMessageId??s.id,sessionId:s.sessionId,role:"user",ts:Date.parse(s.createdAt),payload:R(s.text,s.attachments)}}class H{opts;draining=new Set;constructor(e){this.opts=e,setTimeout(()=>this.drainIdleQueues(),0).unref?.()}getSnapshot(e){return{sessionId:e,busy:!!this.opts.getRuntime().sessions.get(e)?.currentRunId,pending:l().sessions[e]??[]}}async handleEnqueue(e){const n=this.opts.getRuntime(),t=e.params;if(T(t.sessionListProjection),!t.sessionId||!t.text||!t.agentType||!t.workDir){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId, text, agentType and workDir are required"});return}const i=n.resolvePath(t.workDir),o=m(t.attachments);o?.length&&S(i,o);const r={text:t.text,attachments:o,localized:!1},d=await this.isSessionBusy(t.sessionId),u=l(),f=u.sessions[t.sessionId]??[];if(!d&&!f.length){await this.opts.dispatchReq({...e,id:$(e.id),method:"chat.send",params:{...t,text:r.text,responseId:e.id,clientMessageId:t.clientMessageId??t.queueMessageId,waitForDispatch:!0,attachments:r.attachments}});return}const a=Q({...t,text:r.text,attachments:r.attachments}),g=t.origin==="external"?C(f,a):null;g||(u.sessions[t.sessionId]=[...f,a],h(u),E({sessionId:a.sessionId,agentType:a.agentType,sessionMode:a.sessionMode,managerConfig:a.managerConfig,workDir:a.workDir,agentSessionId:a.agentSessionId,modelId:a.modelId}),b(a.sessionId,M(a),"queued"),k(i,a.attachments??[]),y(n.client)),this.broadcast(t.sessionId),n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queued:!0,queueMessageId:g?.id??a.id,...v(t.sessionId,g?.clientMessageId??g?.id??a.clientMessageId??a.id),queue:this.getSnapshot(t.sessionId),...r.localized?{localizedAttachments:!0}:{}}}),d||this.drainNext(t.sessionId)}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)}}),this.getSnapshot(t.sessionId).busy||this.drainNext(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 i=l(),o=i.sessions[t.sessionId]??[],r=o.findIndex(f=>f.id===t.queueMessageId);if(r<0){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}const d=m(t.attachments),u=n.resolvePath(o[r].workDir);d?.length&&S(u,d),o[r]={...o[r],text:t.text,attachments:d,updatedAt:I()},i.sessions[t.sessionId]=o,h(i),w(t.sessionId,M(o[r]),"queued"),o[r].attachments?.length&&k(u,o[r].attachments??[]),y(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 i=l(),o=i.sessions[t.sessionId]??[],r=o.find(u=>u.id===t.queueMessageId),d=o.filter(u=>u.id!==t.queueMessageId);if(d.length===o.length){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}d.length?i.sessions[t.sessionId]=d:delete i.sessions[t.sessionId],h(i),P(t.sessionId,r?.clientMessageId??r?.id??t.queueMessageId),y(n.client),this.broadcast(t.sessionId),n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}})}noteTerminal(e){this.drainNext(e)}purgeSession(e){const n=l();n.sessions[e]&&(delete n.sessions[e],h(n),this.draining.delete(e))}drainIdleQueues(){const e=l();for(const n of Object.keys(e.sessions))this.drainNext(n)}async drainNext(e){if(this.draining.has(e)||await this.isSessionBusy(e))return;const i=(l().sessions[e]??[])[0];if(!i){this.broadcast(e);return}const o=i;this.draining.add(e);try{w(e,M(o),"queued"),await this.dispatchQueuedMessage(o);const r=l(),d=(r.sessions[e]??[]).filter(u=>u.id!==i.id);d.length?r.sessions[e]=d:delete r.sessions[e],h(r),this.broadcast(e)}finally{this.draining.delete(e)}}mergeExternalMessages(e,n){const t=[e];for(;n[0]?.origin==="external";)t.push(n.shift());const i=W(t);return i.length===1?i[0]:{...e,id:`external-batch-${e.id}`,text:i.map(o=>o.text).filter(Boolean).join(`
1
+ import l from"node:fs";import{randomUUID as y}from"node:crypto";import{buildUserMessagePayload as A}from"@shennian/wire";import{mergeProjectedSessions as E}from"./projection.js";import{parsePersonalAttachmentReferences as I}from"./attachment-reference.js";import{assertFinishedAttachmentReferences as D,markAttachmentReferences as w}from"./attachment-transfer-store.js";import{resolvePersonalSyncV1Path as R}from"../personal-sync/paths.js";import{appendMessage as $,getMessageDeliveryStatus as T,recordSession as Q,tombstoneMessage as W,upsertMessage as b}from"./store.js";import{requestSessionIndexPublish as M}from"./index-publisher.js";function C(){return{sessions:{}}}function x(){return new Date().toISOString()}function N(n){return`enqueue-send-${n}-${y()}`}function f(){try{const n=JSON.parse(l.readFileSync(v(),"utf-8"));return{sessions:n.sessions&&typeof n.sessions=="object"?n.sessions:{}}}catch(n){if(n.code==="ENOENT"||n instanceof Error&&n.message.startsWith("ENOENT:"))return C();throw n}}function p(n){const e=R();l.mkdirSync(e,{recursive:!0,mode:448});const s=v(),t=`${s}.tmp-${process.pid}-${y()}`;try{l.writeFileSync(t,`${JSON.stringify(n,null,2)}
2
+ `,{encoding:"utf8",mode:384,flag:"wx"});const i=l.openSync(t,"r");if(typeof i=="number")try{l.fsyncSync(i)}finally{l.closeSync(i)}l.renameSync(t,s)}finally{try{l.unlinkSync(t)}catch{}}}function v(){return R("outbound-queue.json")}function B(n){const e=x();return{id:n.queueMessageId||n.clientMessageId||`queue-${y()}`,sessionId:n.sessionId,text:n.text,agentType:n.agentType,sessionMode:n.sessionMode,managerConfig:n.managerConfig,workDir:n.workDir,agentSessionId:n.agentSessionId??null,modelId:n.modelId??null,systemPrompt:n.systemPrompt??null,managerDefaultWorkerAgentType:n.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:n.managerDefaultWorkerModelId??null,reasoningEffort:n.reasoningEffort??null,clientMessageId:n.clientMessageId??null,attachments:I(n.attachments),externalChannel:n.externalChannel??null,replyTarget:n.replyTarget??null,origin:n.origin,createdAt:e,updatedAt:e}}function S(n){return{id:n.clientMessageId??n.id,sessionId:n.sessionId,role:"user",ts:Date.parse(n.createdAt),payload:A(n.text,n.attachments)}}class Y{opts;draining=new Set;constructor(e){this.opts=e,setTimeout(()=>this.drainIdleQueues(),0).unref?.()}getSnapshot(e){return{sessionId:e,busy:!!this.opts.getRuntime().sessions.get(e)?.currentRunId,pending:f().sessions[e]??[]}}async handleEnqueue(e){const s=this.opts.getRuntime(),t=e.params;if(E(t.sessionListProjection),!t.sessionId||!t.text||!t.agentType||!t.workDir){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId, text, agentType and workDir are required"});return}const i=s.resolvePath(t.workDir),o=I(t.attachments);o?.length&&D(i,o);const r={text:t.text,attachments:o,localized:!1},a=await this.isSessionBusy(t.sessionId),d=f(),h=d.sessions[t.sessionId]??[];if(!a&&!h.length){await this.opts.dispatchReq({...e,id:N(e.id),method:"chat.send",params:{...t,text:r.text,responseId:e.id,clientMessageId:t.clientMessageId??t.queueMessageId,waitForDispatch:!0,attachments:r.attachments}});return}const c=B({...t,text:r.text,attachments:r.attachments}),P=c.clientMessageId??c.id,m=T(t.sessionId,P);if(m.state!=="not_found"){s.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queued:m.state==="queued",queueMessageId:c.id,...m,queue:this.getSnapshot(t.sessionId)}});return}const g=z(h,c);g||(d.sessions[t.sessionId]=[...h,c],p(d));const u=g??c;Q({sessionId:u.sessionId,agentType:u.agentType,sessionMode:u.sessionMode,managerConfig:u.managerConfig,workDir:u.workDir,agentSessionId:u.agentSessionId,modelId:u.modelId}),$(u.sessionId,S(u),"queued"),w(s.resolvePath(u.workDir),u.attachments??[]),M(s.client),this.broadcast(t.sessionId),s.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queued:!0,queueMessageId:g?.id??c.id,...T(t.sessionId,g?.clientMessageId??g?.id??c.clientMessageId??c.id),queue:this.getSnapshot(t.sessionId),...r.localized?{localizedAttachments:!0}:{}}}),a||this.drainNext(t.sessionId)}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)}}),this.getSnapshot(t.sessionId).busy||this.drainNext(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 i=f(),o=i.sessions[t.sessionId]??[],r=o.findIndex(h=>h.id===t.queueMessageId);if(r<0){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}const a=I(t.attachments),d=s.resolvePath(o[r].workDir);a?.length&&D(d,a),o[r]={...o[r],text:t.text,attachments:a,updatedAt:x()},i.sessions[t.sessionId]=o,p(i),b(t.sessionId,S(o[r]),"queued"),o[r].attachments?.length&&w(d,o[r].attachments??[]),M(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 i=f(),o=i.sessions[t.sessionId]??[],r=o.find(d=>d.id===t.queueMessageId),a=o.filter(d=>d.id!==t.queueMessageId);if(a.length===o.length){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}a.length?i.sessions[t.sessionId]=a:delete i.sessions[t.sessionId],p(i),W(t.sessionId,r?.clientMessageId??r?.id??t.queueMessageId),M(s.client),this.broadcast(t.sessionId),s.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}})}noteTerminal(e){this.drainNext(e)}purgeSession(e){const s=f();s.sessions[e]&&(delete s.sessions[e],p(s),this.draining.delete(e))}drainIdleQueues(){const e=f();for(const s of Object.keys(e.sessions))this.drainNext(s)}async drainNext(e){if(this.draining.has(e)||await this.isSessionBusy(e))return;const i=(f().sessions[e]??[])[0];if(!i){this.broadcast(e);return}const o=i;this.draining.add(e);try{b(e,S(o),"queued"),await this.dispatchQueuedMessage(o);const r=f(),a=(r.sessions[e]??[]).filter(d=>d.id!==i.id);a.length?r.sessions[e]=a:delete r.sessions[e],p(r),this.broadcast(e)}finally{this.draining.delete(e)}}mergeExternalMessages(e,s){const t=[e];for(;s[0]?.origin==="external";)t.push(s.shift());const i=j(t);return i.length===1?i[0]:{...e,id:`external-batch-${e.id}`,text:i.map(o=>o.text).filter(Boolean).join(`
3
3
 
4
- `),attachments:B(i.flatMap(o=>o.attachments??[])),updatedAt:I()}}async dispatchQueuedMessage(e){await this.opts.dispatchReq({type:"req",id:`queue-send-${e.id}-${Date.now()}`,method:"chat.send",params:{sessionId:e.sessionId,text:e.text,agentType:e.agentType,sessionMode:e.sessionMode,managerConfig:e.managerConfig,workDir:e.workDir,agentSessionId:e.agentSessionId??null,modelId:e.modelId??void 0,systemPrompt:e.systemPrompt??null,managerDefaultWorkerAgentType:e.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:e.managerDefaultWorkerModelId,reasoningEffort:e.reasoningEffort??void 0,clientMessageId:e.clientMessageId??e.id,attachments:e.attachments,externalChannel:e.externalChannel,replyTarget:e.replyTarget,waitForDispatch:!0}})}async isSessionBusy(e){const n=this.opts.getRuntime().sessions.get(e);if(!n)return!1;if(n.currentRunId)return!0;if(!n.adapter.getStatus)return!1;const t=await n.adapter.getStatus().catch(()=>null);return t?.active?(t.runId&&(n.currentRunId=t.runId,n.currentRunPhase=t.runPhase??n.currentRunPhase),!0):!1}broadcast(e){this.opts.getRuntime().client.sendEvent({type:"event",event:"session.queue.update",payload:{queue:this.getSnapshot(e)}})}}function W(s){const e=new Set,n=[];for(const t of s){const i=x(t);e.has(i)||(e.add(i),n.push(t))}return n}function C(s,e){const n=x(e);return s.find(t=>x(t)===n)??null}function x(s){const e=N(s);return e?`id:${e}`:["body",j(s.text),...(s.attachments??[]).map(n=>n.path||`${n.name}:${n.mimeType}`)].join(`
5
- `)}function N(s){const e=typeof s.clientMessageId=="string"?s.clientMessageId.trim():"";if(e)return e;const n=typeof s.id=="string"?s.id.trim():"";return n&&!n.startsWith("queue-")?n:""}function B(s){const e=new Set,n=[];for(const t of s){const i=t.path||`${t.name}:${t.mimeType}`;e.has(i)||(e.add(i),n.push(t))}return n.length?n:void 0}function j(s){return String(s||"").replace(/\s+/g," ").trim()}export{H as ChatQueueManager};
4
+ `),attachments:F(i.flatMap(o=>o.attachments??[])),updatedAt:x()}}async dispatchQueuedMessage(e){await this.opts.dispatchReq({type:"req",id:`queue-send-${e.id}-${Date.now()}`,method:"chat.send",params:{sessionId:e.sessionId,text:e.text,agentType:e.agentType,sessionMode:e.sessionMode,managerConfig:e.managerConfig,workDir:e.workDir,agentSessionId:e.agentSessionId??null,modelId:e.modelId??void 0,systemPrompt:e.systemPrompt??null,managerDefaultWorkerAgentType:e.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:e.managerDefaultWorkerModelId,reasoningEffort:e.reasoningEffort??void 0,clientMessageId:e.clientMessageId??e.id,attachments:e.attachments,externalChannel:e.externalChannel,replyTarget:e.replyTarget,waitForDispatch:!0}})}async isSessionBusy(e){const s=this.opts.getRuntime().sessions.get(e);if(!s)return!1;if(s.currentRunId)return!0;if(!s.adapter.getStatus)return!1;const t=await s.adapter.getStatus().catch(()=>null);return t?.active?(t.runId&&(s.currentRunId=t.runId,s.currentRunPhase=t.runPhase??s.currentRunPhase),!0):!1}broadcast(e){this.opts.getRuntime().client.sendEvent({type:"event",event:"session.queue.update",payload:{queue:this.getSnapshot(e)}})}}function j(n){const e=new Set,s=[];for(const t of n){const i=k(t);e.has(i)||(e.add(i),s.push(t))}return s}function z(n,e){const s=q(e);if(s)return n.find(i=>q(i)===s)??null;if(e.origin!=="external")return null;const t=k(e);return n.find(i=>k(i)===t)??null}function k(n){const e=q(n);return e?`id:${e}`:["body",O(n.text),...(n.attachments??[]).map(s=>s.path||`${s.name}:${s.mimeType}`)].join(`
5
+ `)}function q(n){const e=typeof n.clientMessageId=="string"?n.clientMessageId.trim():"";if(e)return e;const s=typeof n.id=="string"?n.id.trim():"";return s&&!s.startsWith("queue-")?s:""}function F(n){const e=new Set,s=[];for(const t of n){const i=t.path||`${t.name}:${t.mimeType}`;e.has(i)||(e.add(i),s.push(t))}return s.length?s:void 0}function O(n){return String(n||"").replace(/\s+/g," ").trim()}export{Y as ChatQueueManager};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shennian",
3
- "version": "0.3.52",
3
+ "version": "0.3.53",
4
4
  "description": "Shennian — AI Agent Control Plane CLI",
5
5
  "type": "module",
6
6
  "bin": {