shennian 0.4.27 → 0.4.29

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.
@@ -521,8 +521,8 @@
521
521
  },
522
522
  {
523
523
  "file": "src/native-fusion/service.js",
524
- "beforeBytes": 34698,
525
- "afterBytes": 15552
524
+ "beforeBytes": 36125,
525
+ "afterBytes": 16121
526
526
  },
527
527
  {
528
528
  "file": "src/native-fusion/session-header.js",
@@ -736,8 +736,8 @@
736
736
  },
737
737
  {
738
738
  "file": "src/session/handlers/chat.js",
739
- "beforeBytes": 56203,
740
- "afterBytes": 24136
739
+ "beforeBytes": 57908,
740
+ "afterBytes": 24817
741
741
  },
742
742
  {
743
743
  "file": "src/session/handlers/control.js",
@@ -796,8 +796,8 @@
796
796
  },
797
797
  {
798
798
  "file": "src/session/manager.js",
799
- "beforeBytes": 49105,
800
- "afterBytes": 25969
799
+ "beforeBytes": 49531,
800
+ "afterBytes": 26071
801
801
  },
802
802
  {
803
803
  "file": "src/session/native-cleanup-outbox.js",
@@ -826,18 +826,18 @@
826
826
  },
827
827
  {
828
828
  "file": "src/session/run-recovery/coordinator.js",
829
- "beforeBytes": 7193,
830
- "afterBytes": 3313
829
+ "beforeBytes": 8103,
830
+ "afterBytes": 3507
831
831
  },
832
832
  {
833
833
  "file": "src/session/run-recovery/journal.js",
834
- "beforeBytes": 8644,
835
- "afterBytes": 4226
834
+ "beforeBytes": 8713,
835
+ "afterBytes": 4267
836
836
  },
837
837
  {
838
838
  "file": "src/session/run-recovery/schema.js",
839
- "beforeBytes": 5964,
840
- "afterBytes": 3907
839
+ "beforeBytes": 6517,
840
+ "afterBytes": 4173
841
841
  },
842
842
  {
843
843
  "file": "src/session/store.js",
@@ -21,6 +21,13 @@ export declare class NativeSessionFusionService {
21
21
  start(): void;
22
22
  stop(): void;
23
23
  setExternalTerminalHandler(handler: ((sessionId: string) => void) | null): void;
24
+ restoreExternalObservation(params: {
25
+ sessionId: string;
26
+ sourceSessionKey: string;
27
+ runId: string;
28
+ canonicalMessageId: string;
29
+ createdAt: number;
30
+ }): boolean;
24
31
  handleConnected(): void;
25
32
  registerManagedSend(params: {
26
33
  sessionId: string;
@@ -1,2 +1,2 @@
1
- import N from"node:fs";import W from"node:crypto";import{publishPendingSessionIndexes as O}from"../session/index-publisher.js";import{NativeFusionFileWatcher as $}from"./file-watcher.js";import{NativeEventIdentityConflictError as z,NativeFusionLocalRepository as U}from"./local-repository.js";import{listClaudeTranscriptFiles as H,listCodexRolloutFiles as V,listOpenCodeSessionFiles as Q,listNativeTranscriptRoots as Y,inspectNativeSessionFile as j,lookupCodexThreadName as X,parseClaudeTranscriptChunkCooperatively as G,parseCodexRolloutChunkCooperatively as J,parseOpenCodeSessionFile as Z}from"./parsers.js";import{loadNativeScannerState as ee,saveNativeScannerState as P}from"./state.js";const se=5*6e4,te=2*6e4,ie=30*6e4,ne=2e3,oe=1,k=16,re=3e4,R=240*6e4;function ae(d){return d.replace(/\r\n/g,`
2
- `).trim()}function b(){return new Promise(d=>setImmediate(d))}function ce(d){return d.split(/[\\/]+/).includes(".codex")}function F(d){try{return N.statSync(d).mtimeMs}catch{return 0}}class ve{client;repository;timer=null;activeTimer=null;scanPromise=null;queuedTargetFiles=new Set;pendingBoundaryFiles=new Set;sourceFiles=new Map;fullScanQueued=!1;watchEnabled=!1;lifecycleRevision=0;watcher=new $(e=>{this.scanNow(e).catch(s=>{console.error("[native-fusion] watched file scan failed",s)})});pendingClaims;suppressionWindows;externalObservations=new Map;externalTerminalHandler=null;constructor(e,s=new U){this.client=e,this.repository=s,this.pendingClaims=new Map((this.repository.listManagedClaims?.()??[]).map(t=>[t.canonicalMessageId,t])),this.suppressionWindows=new Map((this.repository.listManagedSuppressions?.()??[]).map(t=>[M(t.agentType,t.sourceSessionKey),t]))}start(){this.timer||(this.timer=setInterval(()=>{this.scanNow().catch(e=>{console.error("[native-fusion] periodic scan failed",e)})},se))}stop(){this.lifecycleRevision+=1,this.timer&&clearInterval(this.timer),this.timer=null,this.activeTimer&&clearInterval(this.activeTimer),this.activeTimer=null,this.pendingBoundaryFiles.clear(),this.watchEnabled=!1,this.watcher.stop()}setExternalTerminalHandler(e){this.externalTerminalHandler=e}handleConnected(){const e=++this.lifecycleRevision;this.watchEnabled=!0,this.scanNow().catch(s=>{console.error("[native-fusion] initial scan failed",s)}).finally(()=>{this.lifecycleRevision===e&&this.start()})}registerManagedSend(e){if(!e.canonicalMessageId)return;const s=e.sourceAgentType??e.agentType;if(s!=="codex"&&s!=="claude"&&s!=="opencode")return;const t=e.canonicalMessageId,n=this.pendingClaims.get(t),c=D(e.text);if(n&&(n.sessionId!==e.sessionId||n.sourceAgentType!==s||n.textHash!==c))throw new Error(`managed echo claim identity conflict: ${t}`);const f={sessionId:e.sessionId,agentType:e.agentType,sourceAgentType:s,canonicalMessageId:e.canonicalMessageId,textHash:c,createdAt:n?.createdAt??Date.now(),sourceSessionKey:e.sourceSessionKey??n?.sourceSessionKey??null,managedEchoPolicy:e.managedEchoPolicy??"suppress_following",externalRunId:e.externalRunId??null};if(this.repository.upsertManagedClaim?.(f),this.pendingClaims.set(t,f),e.externalRunId&&e.sourceSessionKey){const h=`${s}:${e.sourceSessionKey}`,p=this.externalObservations.get(h)??[];p.push({sessionId:e.sessionId,sourceSessionKey:e.sourceSessionKey,runId:e.externalRunId,canonicalMessageId:e.canonicalMessageId,state:"queued",createdAt:Date.now(),lastPublishedAt:Date.now()}),this.externalObservations.set(h,p),this.emitExternalActivity(p[p.length-1],"queued"),this.ensureActiveScanning()}}cancelManagedSend(e){this.repository.removeManagedClaim?.(e),this.pendingClaims.delete(e)}noteManagedSourceSession(e,s,t){if(t){this.repository.updateManagedClaimSource?.(e,s,t);for(const n of this.pendingClaims.values())n.sessionId===e&&n.sourceAgentType===s&&(n.sourceSessionKey=t)}}handleSessionDeleted(e){for(const[s,t]of this.pendingClaims.entries())t.sessionId===e&&this.pendingClaims.delete(s);for(const[s,t]of this.suppressionWindows.entries())t.sessionId===e&&this.suppressionWindows.delete(s);for(const[s,t]of this.externalObservations.entries()){const n=t.filter(c=>c.sessionId!==e);n.length===0?this.externalObservations.delete(s):this.externalObservations.set(s,n)}this.repository.purgeSession(e)}ensureActiveScanning(e=!0){this.activeTimer||(this.activeTimer=setInterval(()=>{if(this.externalObservations.size===0&&this.pendingBoundaryFiles.size===0){this.activeTimer&&clearInterval(this.activeTimer),this.activeTimer=null;return}this.scanActiveTargets()},ne),e&&this.scanActiveTargets())}scanActiveTargets(){const e=this.activeScanTargets();e===void 0&&this.scanPromise||this.scanNow(e).catch(s=>{console.error("[native-fusion] active observation scan failed",s)})}activeScanTargets(){const e=[...this.externalObservations.keys()];if(!e.some(s=>!this.sourceFiles.has(s)))return[...new Set([...this.pendingBoundaryFiles,...e.flatMap(s=>{const t=this.sourceFiles.get(s);return t?[t]:[]})])]}emitExternalActivity(e,s){this.client.sendAgentEvent({type:"event",event:"agent",id:`external-owner-${e.runId}-${s}-${e.lastPublishedAt}`,payload:{state:s==="queued"?"start":"heartbeat",sessionId:e.sessionId,runId:e.runId,seq:s==="queued"?0:1,runPhase:"thinking",canStop:!1,owner:"external",ownerLabel:"Codex Desktop",lifecycle:s}})}emitExternalTerminal(e,s="completed"){const t=s==="completed"?"final":s==="aborted"?"aborted":"error";this.client.sendAgentEvent({type:"event",event:"agent",id:`external-owner-${e.runId}-${t}`,payload:{state:t,sessionId:e.sessionId,runId:e.runId,seq:2,...t==="error"?{message:"Codex Desktop turn failed"}:{},owner:"external",ownerLabel:"Codex Desktop"}}),this.externalTerminalHandler?.(e.sessionId)}pruneState(){const e=Date.now();for(const[s,t]of this.suppressionWindows.entries())t.endsAt>e||(this.repository.removeManagedSuppression?.(t.agentType,t.sourceSessionKey),this.suppressionWindows.delete(s))}async scanNow(e){if(this.client.getState()==="connected"){if(this.scanPromise){const s=this.scanPromise;if(e)for(const t of e)this.queuedTargetFiles.add(t);else this.fullScanQueued=!0;if(await s,this.fullScanQueued)return this.fullScanQueued=!1,this.queuedTargetFiles.clear(),this.scanNow();if(this.queuedTargetFiles.size>0){const t=[...this.queuedTargetFiles];return this.queuedTargetFiles.clear(),this.scanNow(t)}return}return this.scanPromise=this.runScanLoop(e).finally(()=>{this.scanPromise=null}),this.scanPromise}}async runScanLoop(e){let s=e?[...new Set(e)]:void 0;for(;;){if(await this.runScan(s),this.fullScanQueued){await b(),this.fullScanQueued=!1,this.queuedTargetFiles.clear(),s=void 0;continue}if(this.queuedTargetFiles.size>0){await b(),s=[...this.queuedTargetFiles],this.queuedTargetFiles.clear();continue}return}}async runScan(e){this.pruneState(),this.refreshExternalObservations();const s=ee(),t={...s.files},n=new Map;let c=!1;const f=o=>{const a=new Set,S=l=>{try{this.repository.commitEvents(l)}catch(r){if(!(r instanceof z))throw r;const y=l.find(E=>E.sourceEventKey===r.sourceEventKey);if(!y)throw r;a.add(y.sourceEventKey),console.warn(`[native-fusion] quarantined conflicting native event ${y.sourceEventKey}`);const T=l.filter(E=>E!==y);T.length>0&&S(T)}};return S(o),a},h=e?[...e]:[...V(),...H(),...Q()].sort((o,a)=>F(a)-F(o));!e&&this.watchEnabled&&this.watcher.refresh(h,Y());let p=0,w=!1;for(;h.length>0||w&&this.queuedTargetFiles.size>0;){let o;if(w&&this.queuedTargetFiles.size>0){if(o=[...this.queuedTargetFiles].sort((i,u)=>F(u)-F(i))[0],o){this.queuedTargetFiles.delete(o);const i=h.indexOf(o);i>=0&&h.splice(i,1)}w=!1}else o=h.shift(),w=!0;if(!o)break;if(p>0&&p%oe===0&&await b(),p+=1,!N.existsSync(o)){this.watchEnabled&&this.watcher.untrackFile(o),this.pendingBoundaryFiles.delete(o);for(const[i,u]of this.sourceFiles.entries())u===o&&this.sourceFiles.delete(i);continue}this.watchEnabled&&this.watcher.trackFile(o);const a=N.statSync(o),S=ce(o),l=o.endsWith(".opencode-session.json");let r=s.files[o];const y=`${String(a.dev)}:${String(a.ino)}:${String(a.birthtimeMs)}`;let T=!r;if(r&&!l&&(a.size<r.offset||r.fileIdentity&&r.fileIdentity!==y)&&(r=void 0,T=!0),!!(r&&r.fileIdentity===y&&r.mtimeMs===a.mtimeMs&&r.offset>=a.size&&r.sources?.length)&&r){for(const i of r.sources??[])this.sourceFiles.set(M(i.agentType,i.sourceSessionKey),o);r.classification==="live"&&r.awaitingUserBoundary?this.pendingBoundaryFiles.add(o):this.pendingBoundaryFiles.delete(o);continue}const g=r?.sources?.length&&!l?{status:"ready",createdAtMs:r.sessionCreatedAtMs??a.birthtimeMs,sources:r.sources}:j(o);if(g.status==="pending"){a.birthtimeMs>=s.fusionEpochMs&&this.pendingBoundaryFiles.add(o);continue}for(const i of g.sources)this.sourceFiles.set(M(i.agentType,i.sourceSessionKey),o);if(!l&&g.sources.length>0&&g.sources.every(i=>this.repository.isSourceDeleted?.(i.agentType,i.sourceSessionKey))){t[o]={...r??{daemonInstallationId:s.daemonInstallationId,classification:g.createdAtMs>=s.fusionEpochMs?"live":"historical",awaitingUserBoundary:!0,sessionCreatedAtMs:g.createdAtMs,sourceBoundaryEstablished:{}},offset:a.size,mtimeMs:a.mtimeMs,fileIdentity:y};continue}if(!r)r={daemonInstallationId:s.daemonInstallationId,offset:0,mtimeMs:a.mtimeMs,classification:g.createdAtMs>=s.fusionEpochMs?"live":"historical",awaitingUserBoundary:!0,sessionCreatedAtMs:g.createdAtMs,sourceBoundaryEstablished:{}};else if(l&&r.mtimeMs===a.mtimeMs)continue;const x=l?0:r.offset;let m;try{if(m=l?Z(o,x):S?await J(o,x):await G(o,x),!l&&m.nextOffset<x)throw new Error("native transcript cursor moved backwards")}catch(i){console.error("[native-fusion] target file scan failed; isolated for retry",i);continue}let A,v;if(l){const i=ue(m.events,r,s.fusionEpochMs,T,a.mtimeMs,m.nextOffset);A=i.events,v=i.cursor}else if(T&&r.classification==="historical")A=[],v={...r,offset:m.nextOffset,mtimeMs:a.mtimeMs,awaitingUserBoundary:!0};else{const i=le(m.events,r,m.nextOffset,a.mtimeMs);A=i.events,v=i.cursor}v.fileIdentity=y,v.sources=g.sources;const K=[],_=new Map,q=new Map;for(const i of A){if(this.repository.isSourceDeleted?.(i.agentType,i.sourceSessionKey))continue;const u=this.tryClaimManagedEcho(i);if(u){K.push(u.event),_.set(u.claimKey,u.suppression??null),u.suppression&&q.set(M(u.suppression.agentType,u.suppression.sourceSessionKey),{window:u.suppression,claimKey:u.claimKey});continue}const C=this.suppressionFor(i,q,_);if(C){K.push({...i,claimSessionId:C.sessionId,localDisposition:"managed_echo_suppressed"});continue}K.push(i)}const I=K.sort((i,u)=>i.ts-u.ts),B=new Set;try{for(let i=0;i<I.length;i+=k){const u=f(I.slice(i,i+k));for(const C of u)B.add(C);c=!0,i+k<I.length&&await b()}}catch(i){console.error("[native-fusion] target file commit failed; isolated for retry",i);continue}const L=new Set(I.filter(i=>B.has(i.sourceEventKey)).flatMap(i=>i.claimCanonicalMessageId?[i.claimCanonicalMessageId]:[]));for(const[i,u]of _)L.has(i)||(this.repository.completeManagedClaim?.(i,u??void 0),this.pendingClaims.delete(i),u&&this.suppressionWindows.set(M(u.agentType,u.sourceSessionKey),u));for(const i of m.events)this.repository.isSourceDeleted?.(i.agentType,i.sourceSessionKey)||i.title?.trim()&&i.titleSource==="agent_native"&&n.set(`${i.agentType}:${i.sourceSessionKey}`,i);for(const i of I)B.has(i.sourceEventKey)||this.observeExternalLifecycle(i);t[o]=v,s.files=t,P(s),v.classification==="live"&&v.awaitingUserBoundary?this.pendingBoundaryFiles.add(o):this.pendingBoundaryFiles.delete(o),I.length>0&&await O(this.client).catch(i=>{console.error("[native-fusion] index projection sync failed; retained for retry",i)})}c||this.repository.commitEvents([]);for(const o of n.values())this.repository.observeNativeTitle(o.agentType,o.sourceSessionKey,o.title??"");for(const o of this.repository.listSourceBindings()){if(o.agentType!=="codex")continue;const a=X(o.sourceSessionKey);a&&this.repository.observeNativeTitle("codex",o.sourceSessionKey,a)}s.files=t,P(s),this.pendingBoundaryFiles.size>0&&this.ensureActiveScanning(!1),this.repository.repairMissingUserBoundaries?.(),await O(this.client).catch(o=>{console.error("[native-fusion] index projection sync failed; retained for retry",o)})}refreshExternalObservations(){const e=Date.now();for(const[s,t]of this.externalObservations.entries()){const n=t.filter(f=>e-f.createdAt<R);if(n.length===0){this.externalObservations.delete(s);continue}this.externalObservations.set(s,n);const c=n[0];e-c.lastPublishedAt<re||(c.lastPublishedAt=e,this.emitExternalActivity(c,c.state))}}tryClaimManagedEcho(e){const s=Date.now();for(const[t,n]of this.pendingClaims.entries()){if(n.sourceAgentType!==e.agentType||e.role!=="user"||!n.sourceSessionKey||n.sourceSessionKey!==e.sourceSessionKey)continue;const c=n.externalRunId?R:te;if(Math.abs(e.ts-n.createdAt)>c||D(e.payload)!==n.textHash)continue;const f=n.managedEchoPolicy==="suppress_following"?{sessionId:n.sessionId,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,claimSourceEventKey:e.sourceEventKey,startedAt:e.ts,endsAt:Math.max(s,e.ts)+ie}:void 0;return{claimKey:t,suppression:f,event:{...e,sourceMode:"managed_echo_claim",claimCanonicalMessageId:n.canonicalMessageId,claimSessionId:n.sessionId,managedEchoPolicy:n.managedEchoPolicy}}}return null}observeExternalLifecycle(e){const s=`${e.agentType}:${e.sourceSessionKey}`,t=this.externalObservations.get(s);if(!t?.length)return;const n=t[0];if(e.claimCanonicalMessageId===n.canonicalMessageId&&n.state==="queued"&&(this.repository.updateMessageDeliveryState(n.sessionId,n.canonicalMessageId,"executing"),n.state="running",n.lastPublishedAt=Date.now(),this.emitExternalActivity(n,"running")),!e.terminal||n.state!=="running")return;const c=e.terminalState??"completed";this.repository.updateMessageDeliveryState(n.sessionId,n.canonicalMessageId,c==="aborted"?"cancelled":c==="failed"?"failed":"completed"),this.emitExternalTerminal(n,c),t.shift(),t.length===0?this.externalObservations.delete(s):(this.externalObservations.set(s,t),t[0].lastPublishedAt=Date.now(),this.emitExternalActivity(t[0],"queued"))}suppressionFor(e,s,t){const n=M(e.agentType,e.sourceSessionKey),c=s.get(n),f=c?.window??this.suppressionWindows.get(n);return!f||e.ts<f.startedAt?null:e.ts>f.endsAt?(this.clearSuppression(n,f,c,s,t),null):e.role==="user"?(e.sourceEventKey===f.claimSourceEventKey||this.clearSuppression(n,f,c,s,t),null):f}clearSuppression(e,s,t,n,c){if(t){n.delete(e),c.set(t.claimKey,null);return}this.repository.removeManagedSuppression?.(s.agentType,s.sourceSessionKey),this.suppressionWindows.delete(e)}}function D(d){return W.createHash("sha256").update(ae(d)).digest("hex")}function M(d,e){return`${d}:${e}`}function le(d,e,s,t){if(!e.awaitingUserBoundary)return{events:d,cursor:{...e,offset:s,mtimeMs:t}};if(d.length===0)return{events:[],cursor:{...e,offset:s,mtimeMs:t,awaitingUserBoundary:!0}};const n=d.findIndex(c=>c.role==="user");return n<0?{events:[],cursor:{...e,mtimeMs:t,awaitingUserBoundary:!0}}:{events:d.slice(n),cursor:{...e,offset:s,mtimeMs:t,awaitingUserBoundary:!1}}}function ue(d,e,s,t,n,c){const f={...e.sourceBoundaryEstablished??{}},h=e.eventWatermarkTs??-1,p=new Set(e.eventKeysAtWatermark??[]),w=t?d:d.filter(l=>l.ts>h||l.ts===h&&!p.has(l.sourceEventKey)),o=[];for(const l of w){const r=l.sourceSessionKey;if(f[r]){o.push(l);continue}!(!t||(l.sourceSessionCreatedAtMs??0)>=s)||l.role!=="user"||(f[r]=!0,o.push(l))}const a=d.reduce((l,r)=>Math.max(l,r.ts),h),S=new Set(a===h?e.eventKeysAtWatermark??[]:[]);for(const l of d)l.ts===a&&S.add(l.sourceEventKey);return{events:o,cursor:{...e,offset:c,mtimeMs:n,eventWatermarkTs:a>=0?a:void 0,eventKeysAtWatermark:a>=0?[...S]:void 0,sourceBoundaryEstablished:f}}}export{ve as NativeSessionFusionService};
1
+ import N from"node:fs";import $ from"node:crypto";import{publishPendingSessionIndexes as q}from"../session/index-publisher.js";import{NativeFusionFileWatcher as W}from"./file-watcher.js";import{NativeEventIdentityConflictError as z,NativeFusionLocalRepository as U}from"./local-repository.js";import{listClaudeTranscriptFiles as H,listCodexRolloutFiles as V,listOpenCodeSessionFiles as Q,listNativeTranscriptRoots as Y,inspectNativeSessionFile as j,lookupCodexThreadName as X,parseClaudeTranscriptChunkCooperatively as G,parseCodexRolloutChunkCooperatively as J,parseOpenCodeSessionFile as Z}from"./parsers.js";import{loadNativeScannerState as ee,saveNativeScannerState as P}from"./state.js";const se=5*6e4,te=2*6e4,ie=30*6e4,ne=2e3,oe=1,O=16,re=3e4,R=240*6e4;function ae(f){return f.replace(/\r\n/g,`
2
+ `).trim()}function C(){return new Promise(f=>setImmediate(f))}function ce(f){return f.split(/[\\/]+/).includes(".codex")}function F(f){try{return N.statSync(f).mtimeMs}catch{return 0}}class ve{client;repository;timer=null;activeTimer=null;scanPromise=null;queuedTargetFiles=new Set;pendingBoundaryFiles=new Set;sourceFiles=new Map;fullScanQueued=!1;watchEnabled=!1;lifecycleRevision=0;watcher=new W(e=>{this.scanNow(e).catch(s=>{console.error("[native-fusion] watched file scan failed",s)})});pendingClaims;suppressionWindows;externalObservations=new Map;externalTerminalHandler=null;constructor(e,s=new U){this.client=e,this.repository=s,this.pendingClaims=new Map((this.repository.listManagedClaims?.()??[]).map(t=>[t.canonicalMessageId,t])),this.suppressionWindows=new Map((this.repository.listManagedSuppressions?.()??[]).map(t=>[M(t.agentType,t.sourceSessionKey),t]))}start(){this.timer||(this.timer=setInterval(()=>{this.scanNow().catch(e=>{console.error("[native-fusion] periodic scan failed",e)})},se))}stop(){this.lifecycleRevision+=1,this.timer&&clearInterval(this.timer),this.timer=null,this.activeTimer&&clearInterval(this.activeTimer),this.activeTimer=null,this.pendingBoundaryFiles.clear(),this.watchEnabled=!1,this.watcher.stop()}setExternalTerminalHandler(e){this.externalTerminalHandler=e}restoreExternalObservation(e){if(!e.sessionId||!e.sourceSessionKey||!e.runId||!e.canonicalMessageId||!Number.isFinite(e.createdAt))return!1;const s=`codex:${e.sourceSessionKey}`,t=this.externalObservations.get(s)??[];if(t.some(u=>u.runId===e.runId))return!0;const n=Date.now(),a={...e,state:this.pendingClaims.has(e.canonicalMessageId)?"queued":"running",createdAt:Math.min(e.createdAt,n),lastPublishedAt:n};return t.push(a),t.sort((u,h)=>u.createdAt-h.createdAt),this.externalObservations.set(s,t),this.emitExternalActivity(a,a.state),this.ensureActiveScanning(!1),!0}handleConnected(){const e=++this.lifecycleRevision;this.watchEnabled=!0,this.scanNow().catch(s=>{console.error("[native-fusion] initial scan failed",s)}).finally(()=>{this.lifecycleRevision===e&&this.start()})}registerManagedSend(e){if(!e.canonicalMessageId)return;const s=e.sourceAgentType??e.agentType;if(s!=="codex"&&s!=="claude"&&s!=="opencode")return;const t=e.canonicalMessageId,n=this.pendingClaims.get(t),a=D(e.text);if(n&&(n.sessionId!==e.sessionId||n.sourceAgentType!==s||n.textHash!==a))throw new Error(`managed echo claim identity conflict: ${t}`);const u={sessionId:e.sessionId,agentType:e.agentType,sourceAgentType:s,canonicalMessageId:e.canonicalMessageId,textHash:a,createdAt:n?.createdAt??Date.now(),sourceSessionKey:e.sourceSessionKey??n?.sourceSessionKey??null,managedEchoPolicy:e.managedEchoPolicy??"suppress_following",externalRunId:e.externalRunId??null};if(this.repository.upsertManagedClaim?.(u),this.pendingClaims.set(t,u),e.externalRunId&&e.sourceSessionKey){const h=`${s}:${e.sourceSessionKey}`,p=this.externalObservations.get(h)??[];p.push({sessionId:e.sessionId,sourceSessionKey:e.sourceSessionKey,runId:e.externalRunId,canonicalMessageId:e.canonicalMessageId,state:"queued",createdAt:Date.now(),lastPublishedAt:Date.now()}),this.externalObservations.set(h,p),this.emitExternalActivity(p[p.length-1],"queued"),this.ensureActiveScanning()}}cancelManagedSend(e){this.repository.removeManagedClaim?.(e),this.pendingClaims.delete(e)}noteManagedSourceSession(e,s,t){if(t){this.repository.updateManagedClaimSource?.(e,s,t);for(const n of this.pendingClaims.values())n.sessionId===e&&n.sourceAgentType===s&&(n.sourceSessionKey=t)}}handleSessionDeleted(e){for(const[s,t]of this.pendingClaims.entries())t.sessionId===e&&this.pendingClaims.delete(s);for(const[s,t]of this.suppressionWindows.entries())t.sessionId===e&&this.suppressionWindows.delete(s);for(const[s,t]of this.externalObservations.entries()){const n=t.filter(a=>a.sessionId!==e);n.length===0?this.externalObservations.delete(s):this.externalObservations.set(s,n)}this.repository.purgeSession(e)}ensureActiveScanning(e=!0){this.activeTimer||(this.activeTimer=setInterval(()=>{if(this.externalObservations.size===0&&this.pendingBoundaryFiles.size===0){this.activeTimer&&clearInterval(this.activeTimer),this.activeTimer=null;return}this.scanActiveTargets()},ne),e&&this.scanActiveTargets())}scanActiveTargets(){const e=this.activeScanTargets();e===void 0&&this.scanPromise||this.scanNow(e).catch(s=>{console.error("[native-fusion] active observation scan failed",s)})}activeScanTargets(){const e=[...this.externalObservations.keys()];if(!e.some(s=>!this.sourceFiles.has(s)))return[...new Set([...this.pendingBoundaryFiles,...e.flatMap(s=>{const t=this.sourceFiles.get(s);return t?[t]:[]})])]}emitExternalActivity(e,s){this.client.sendAgentEvent({type:"event",event:"agent",id:`external-owner-${e.runId}-${s}-${e.lastPublishedAt}`,payload:{state:s==="queued"?"start":"heartbeat",sessionId:e.sessionId,runId:e.runId,seq:s==="queued"?0:1,runPhase:"thinking",canStop:!1,owner:"external",ownerLabel:"Codex Desktop",lifecycle:s}})}emitExternalTerminal(e,s="completed"){const t=s==="completed"?"final":s==="aborted"?"aborted":"error";this.client.sendAgentEvent({type:"event",event:"agent",id:`external-owner-${e.runId}-${t}`,payload:{state:t,sessionId:e.sessionId,runId:e.runId,seq:2,...t==="error"?{message:"Codex Desktop turn failed"}:{},owner:"external",ownerLabel:"Codex Desktop"}}),this.externalTerminalHandler?.(e.sessionId)}pruneState(){const e=Date.now();for(const[s,t]of this.suppressionWindows.entries())t.endsAt>e||(this.repository.removeManagedSuppression?.(t.agentType,t.sourceSessionKey),this.suppressionWindows.delete(s))}async scanNow(e){if(this.client.getState()==="connected"){if(this.scanPromise){const s=this.scanPromise;if(e)for(const t of e)this.queuedTargetFiles.add(t);else this.fullScanQueued=!0;if(await s,this.fullScanQueued)return this.fullScanQueued=!1,this.queuedTargetFiles.clear(),this.scanNow();if(this.queuedTargetFiles.size>0){const t=[...this.queuedTargetFiles];return this.queuedTargetFiles.clear(),this.scanNow(t)}return}return this.scanPromise=this.runScanLoop(e).finally(()=>{this.scanPromise=null}),this.scanPromise}}async runScanLoop(e){let s=e?[...new Set(e)]:void 0;for(;;){if(await this.runScan(s),this.fullScanQueued){await C(),this.fullScanQueued=!1,this.queuedTargetFiles.clear(),s=void 0;continue}if(this.queuedTargetFiles.size>0){await C(),s=[...this.queuedTargetFiles],this.queuedTargetFiles.clear();continue}return}}async runScan(e){this.pruneState(),this.refreshExternalObservations();const s=ee(),t={...s.files},n=new Map;let a=!1;const u=o=>{const c=new Set,S=l=>{try{this.repository.commitEvents(l)}catch(r){if(!(r instanceof z))throw r;const y=l.find(x=>x.sourceEventKey===r.sourceEventKey);if(!y)throw r;c.add(y.sourceEventKey),console.warn(`[native-fusion] quarantined conflicting native event ${y.sourceEventKey}`);const T=l.filter(x=>x!==y);T.length>0&&S(T)}};return S(o),c},h=e?[...e]:[...V(),...H(),...Q()].sort((o,c)=>F(c)-F(o));!e&&this.watchEnabled&&this.watcher.refresh(h,Y());let p=0,w=!1;for(;h.length>0||w&&this.queuedTargetFiles.size>0;){let o;if(w&&this.queuedTargetFiles.size>0){if(o=[...this.queuedTargetFiles].sort((i,d)=>F(d)-F(i))[0],o){this.queuedTargetFiles.delete(o);const i=h.indexOf(o);i>=0&&h.splice(i,1)}w=!1}else o=h.shift(),w=!0;if(!o)break;if(p>0&&p%oe===0&&await C(),p+=1,!N.existsSync(o)){this.watchEnabled&&this.watcher.untrackFile(o),this.pendingBoundaryFiles.delete(o);for(const[i,d]of this.sourceFiles.entries())d===o&&this.sourceFiles.delete(i);continue}this.watchEnabled&&this.watcher.trackFile(o);const c=N.statSync(o),S=ce(o),l=o.endsWith(".opencode-session.json");let r=s.files[o];const y=`${String(c.dev)}:${String(c.ino)}:${String(c.birthtimeMs)}`;let T=!r;if(r&&!l&&(c.size<r.offset||r.fileIdentity&&r.fileIdentity!==y)&&(r=void 0,T=!0),!!(r&&r.fileIdentity===y&&r.mtimeMs===c.mtimeMs&&r.offset>=c.size&&r.sources?.length)&&r){for(const i of r.sources??[])this.sourceFiles.set(M(i.agentType,i.sourceSessionKey),o);r.classification==="live"&&r.awaitingUserBoundary?this.pendingBoundaryFiles.add(o):this.pendingBoundaryFiles.delete(o);continue}const g=r?.sources?.length&&!l?{status:"ready",createdAtMs:r.sessionCreatedAtMs??c.birthtimeMs,sources:r.sources}:j(o);if(g.status==="pending"){c.birthtimeMs>=s.fusionEpochMs&&this.pendingBoundaryFiles.add(o);continue}for(const i of g.sources)this.sourceFiles.set(M(i.agentType,i.sourceSessionKey),o);if(!l&&g.sources.length>0&&g.sources.every(i=>this.repository.isSourceDeleted?.(i.agentType,i.sourceSessionKey))){t[o]={...r??{daemonInstallationId:s.daemonInstallationId,classification:g.createdAtMs>=s.fusionEpochMs?"live":"historical",awaitingUserBoundary:!0,sessionCreatedAtMs:g.createdAtMs,sourceBoundaryEstablished:{}},offset:c.size,mtimeMs:c.mtimeMs,fileIdentity:y};continue}if(!r)r={daemonInstallationId:s.daemonInstallationId,offset:0,mtimeMs:c.mtimeMs,classification:g.createdAtMs>=s.fusionEpochMs?"live":"historical",awaitingUserBoundary:!0,sessionCreatedAtMs:g.createdAtMs,sourceBoundaryEstablished:{}};else if(l&&r.mtimeMs===c.mtimeMs)continue;const E=l?0:r.offset;let m;try{if(m=l?Z(o,E):S?await J(o,E):await G(o,E),!l&&m.nextOffset<E)throw new Error("native transcript cursor moved backwards")}catch(i){console.error("[native-fusion] target file scan failed; isolated for retry",i);continue}let A,v;if(l){const i=ue(m.events,r,s.fusionEpochMs,T,c.mtimeMs,m.nextOffset);A=i.events,v=i.cursor}else if(T&&r.classification==="historical")A=[],v={...r,offset:m.nextOffset,mtimeMs:c.mtimeMs,awaitingUserBoundary:!0};else{const i=le(m.events,r,m.nextOffset,c.mtimeMs);A=i.events,v=i.cursor}v.fileIdentity=y,v.sources=g.sources;const K=[],_=new Map,k=new Map;for(const i of A){if(this.repository.isSourceDeleted?.(i.agentType,i.sourceSessionKey))continue;const d=this.tryClaimManagedEcho(i);if(d){K.push(d.event),_.set(d.claimKey,d.suppression??null),d.suppression&&k.set(M(d.suppression.agentType,d.suppression.sourceSessionKey),{window:d.suppression,claimKey:d.claimKey});continue}const b=this.suppressionFor(i,k,_);if(b){K.push({...i,claimSessionId:b.sessionId,localDisposition:"managed_echo_suppressed"});continue}K.push(i)}const I=K.sort((i,d)=>i.ts-d.ts),B=new Set;try{for(let i=0;i<I.length;i+=O){const d=u(I.slice(i,i+O));for(const b of d)B.add(b);a=!0,i+O<I.length&&await C()}}catch(i){console.error("[native-fusion] target file commit failed; isolated for retry",i);continue}const L=new Set(I.filter(i=>B.has(i.sourceEventKey)).flatMap(i=>i.claimCanonicalMessageId?[i.claimCanonicalMessageId]:[]));for(const[i,d]of _)L.has(i)||(this.repository.completeManagedClaim?.(i,d??void 0),this.pendingClaims.delete(i),d&&this.suppressionWindows.set(M(d.agentType,d.sourceSessionKey),d));for(const i of m.events)this.repository.isSourceDeleted?.(i.agentType,i.sourceSessionKey)||i.title?.trim()&&i.titleSource==="agent_native"&&n.set(`${i.agentType}:${i.sourceSessionKey}`,i);for(const i of I)B.has(i.sourceEventKey)||this.observeExternalLifecycle(i);t[o]=v,s.files=t,P(s),v.classification==="live"&&v.awaitingUserBoundary?this.pendingBoundaryFiles.add(o):this.pendingBoundaryFiles.delete(o),I.length>0&&await q(this.client).catch(i=>{console.error("[native-fusion] index projection sync failed; retained for retry",i)})}a||this.repository.commitEvents([]);for(const o of n.values())this.repository.observeNativeTitle(o.agentType,o.sourceSessionKey,o.title??"");for(const o of this.repository.listSourceBindings()){if(o.agentType!=="codex")continue;const c=X(o.sourceSessionKey);c&&this.repository.observeNativeTitle("codex",o.sourceSessionKey,c)}s.files=t,P(s),this.pendingBoundaryFiles.size>0&&this.ensureActiveScanning(!1),this.repository.repairMissingUserBoundaries?.(),await q(this.client).catch(o=>{console.error("[native-fusion] index projection sync failed; retained for retry",o)})}refreshExternalObservations(){const e=Date.now();for(const[s,t]of this.externalObservations.entries()){const n=t.filter(u=>e-u.createdAt<R);if(n.length===0){this.externalObservations.delete(s);continue}this.externalObservations.set(s,n);const a=n[0];e-a.lastPublishedAt<re||(a.lastPublishedAt=e,this.emitExternalActivity(a,a.state))}}tryClaimManagedEcho(e){const s=Date.now();for(const[t,n]of this.pendingClaims.entries()){if(n.sourceAgentType!==e.agentType||e.role!=="user"||!n.sourceSessionKey||n.sourceSessionKey!==e.sourceSessionKey)continue;const a=n.externalRunId?R:te;if(Math.abs(e.ts-n.createdAt)>a||D(e.payload)!==n.textHash)continue;const u=n.managedEchoPolicy==="suppress_following"?{sessionId:n.sessionId,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,claimSourceEventKey:e.sourceEventKey,startedAt:e.ts,endsAt:Math.max(s,e.ts)+ie}:void 0;return{claimKey:t,suppression:u,event:{...e,sourceMode:"managed_echo_claim",claimCanonicalMessageId:n.canonicalMessageId,claimSessionId:n.sessionId,managedEchoPolicy:n.managedEchoPolicy}}}return null}observeExternalLifecycle(e){const s=`${e.agentType}:${e.sourceSessionKey}`,t=this.externalObservations.get(s);if(!t?.length)return;const n=t[0];if(e.claimCanonicalMessageId===n.canonicalMessageId&&n.state==="queued"&&(this.repository.updateMessageDeliveryState(n.sessionId,n.canonicalMessageId,"executing"),n.state="running",n.lastPublishedAt=Date.now(),this.emitExternalActivity(n,"running")),!e.terminal||n.state!=="running")return;const a=e.terminalState??"completed";this.repository.updateMessageDeliveryState(n.sessionId,n.canonicalMessageId,a==="aborted"?"cancelled":a==="failed"?"failed":"completed"),this.emitExternalTerminal(n,a),t.shift(),t.length===0?this.externalObservations.delete(s):(this.externalObservations.set(s,t),t[0].lastPublishedAt=Date.now(),this.emitExternalActivity(t[0],"queued"))}suppressionFor(e,s,t){const n=M(e.agentType,e.sourceSessionKey),a=s.get(n),u=a?.window??this.suppressionWindows.get(n);return!u||e.ts<u.startedAt?null:e.ts>u.endsAt?(this.clearSuppression(n,u,a,s,t),null):e.role==="user"?(e.sourceEventKey===u.claimSourceEventKey||this.clearSuppression(n,u,a,s,t),null):u}clearSuppression(e,s,t,n,a){if(t){n.delete(e),a.set(t.claimKey,null);return}this.repository.removeManagedSuppression?.(s.agentType,s.sourceSessionKey),this.suppressionWindows.delete(e)}}function D(f){return $.createHash("sha256").update(ae(f)).digest("hex")}function M(f,e){return`${f}:${e}`}function le(f,e,s,t){if(!e.awaitingUserBoundary)return{events:f,cursor:{...e,offset:s,mtimeMs:t}};if(f.length===0)return{events:[],cursor:{...e,offset:s,mtimeMs:t,awaitingUserBoundary:!0}};const n=f.findIndex(a=>a.role==="user");return n<0?{events:[],cursor:{...e,mtimeMs:t,awaitingUserBoundary:!0}}:{events:f.slice(n),cursor:{...e,offset:s,mtimeMs:t,awaitingUserBoundary:!1}}}function ue(f,e,s,t,n,a){const u={...e.sourceBoundaryEstablished??{}},h=e.eventWatermarkTs??-1,p=new Set(e.eventKeysAtWatermark??[]),w=t?f:f.filter(l=>l.ts>h||l.ts===h&&!p.has(l.sourceEventKey)),o=[];for(const l of w){const r=l.sourceSessionKey;if(u[r]){o.push(l);continue}!(!t||(l.sourceSessionCreatedAtMs??0)>=s)||l.role!=="user"||(u[r]=!0,o.push(l))}const c=f.reduce((l,r)=>Math.max(l,r.ts),h),S=new Set(c===h?e.eventKeysAtWatermark??[]:[]);for(const l of f)l.ts===c&&S.add(l.sourceEventKey);return{events:o,cursor:{...e,offset:a,mtimeMs:n,eventWatermarkTs:c>=0?c:void 0,eventKeysAtWatermark:c>=0?[...S]:void 0,sourceBoundaryEstablished:u}}}export{ve as NativeSessionFusionService};
@@ -1,2 +1,2 @@
1
- import pe from"node:os";import{createHash as q,randomUUID as De}from"node:crypto";import{createAgent as be}from"../../agents/adapter.js";import{buildApprovalPendingPayload as qe,buildUserMessagePayload as me}from"@shennian/wire";import{ManagerModeAdapter as Ce}from"../../agents/manager.js";import{reportLog as C}from"../../log-reporter.js";import{lookupClaudeTranscriptCwd as $e}from"../../native-fusion/parsers.js";import{appendMessage as $,getDaemonInstallationId as ae,getMessageDeliveryStatus as je,recordSession as U,upsertMessage as j}from"../store.js";import{mergeProjectedSessions as Oe}from"../projection.js";import{buildManagedAgentEnv as We}from"../../agents/config-status.js";import{parsePersonalAttachmentReferences as He}from"../attachment-reference.js";import{assertFinishedAttachmentReferences as Je,markAttachmentReferences as Be}from"../attachment-transfer-store.js";import{requestSessionIndexPublish as _}from"../index-publisher.js";import{writeToolDetail as Ne}from"../tool-detail-store.js";import{getPersonalSyncIdentity as fe}from"../../personal-sync/epoch.js";import{buildManagedAgentPermissionPrompt as ye}from"../../room/managed-prompt-composer.js";import{getManagedRoomIpcRoot as Fe,MANAGED_ROOM_IPC_ROOT_ENV as ze}from"../../daemon-ipc/paths.js";import{refreshMachineAgents as Ve}from"./agents.js";function he(e,n,a){if(!e.managedAgentPolicy)throw new Error("managed_agent_policy_missing");const s=e.managedRoomPromptSnapshot;if(!s)throw new Error("managed_room_prompt_snapshot_missing");if(!n?.trim())throw new Error("managed_room_prompt_incomplete");if(!a||!/^mrc_[A-Za-z0-9_-]{43}$/.test(a))throw new Error("managed_room_context_unavailable");const r=q("sha256").update(JSON.stringify({requestedPolicy:e.managedAgentPolicy.requestedPolicy,enforcement:e.managedAgentPolicy.enforcement})).digest("hex"),u=q("sha256").update(e.managedAgentPolicy.adapterPolicy.workDir).digest("hex");if(s.runtimePermissionPolicyHash!==r||s.resolvedWorkDirHash!==u||s.enforcementLevel!==e.managedAgentPolicy.enforcement.level)throw new Error("managed_room_prompt_snapshot_mismatch")}function Ue(e){const n=e.indexOf(`
2
- `),a=n>0?Math.min(n,80):Math.min(e.length,80);return e.slice(0,a)}function Ie(e,n,a){return`${e}:${q("sha256").update(`${n}\0${a??""}`).digest("hex")}`}function Le(e,n,a={}){if(e.state==="tool-call"||e.state==="tool-result"){const s={runId:e.runId,sourceSeq:e.seq};return{state:e.state,runId:e.runId,seq:e.seq,sessionId:n,detailRef:s,status:e.state==="tool-call"?"running":"completed",detailAvailable:!0,...e.durationMs!==void 0?{durationMs:e.durationMs}:{},...e.name?{name:e.name}:{},...e.source?{source:e.source}:{},...e.agentSessionId?{agentSessionId:e.agentSessionId}:{},...a}}return{...e,sessionId:n,...a}}const Ke=3e4;function Qe(e){return e.state==="heartbeat"?e.runPhase??null:e.state==="tool-call"||e.state==="tool-result"?"tool_running":e.state==="approval-pending"?"waiting_approval":e.state==="delta"?e.thinking?"thinking":"streaming_text":e.state==="init"||e.state==="start"?"thinking":null}function Re(e,n){return`Agent send failed: ${n instanceof Error?n.message:String(n)}`}function Se(e,n){return e!=="manager"?e:n==="claude"?"claude":"codex"}function Ge(e,n){U({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 ke(e){return We(e)}function we(e,n,a){return{}}function L(e,n,a,s,r,u,c,m){const l=a==="codex";e.configure?.({sessionId:n,externalChannel:s??null,env:{...ke(a),...we(n,s,r),...u&&!l?{SHENNIAN_MANAGED_ROOM_CONTEXT:u,[ze]:Fe()}:{}},...c?{managedAgentPolicy:c}:{},...l?{managedRoomContextToken:u??null,managedRoomExecutor:u?m??null:null}:{}})}function oe(e,n,a){const s=n.managedRoomContextToken;s&&s!==a&&e.revokeManagedRoomContextToken?.(s),n.managedRoomContextToken=a}function B(e,n){const a=n?.managedRoomContextToken;!n||!a||(n.managedRoomContextToken=null,e.revokeManagedRoomContextToken?.(a))}function Xe(e,n,a){if(e!=="claude"||!a)return n;const s=n.trim();return!!s&&s.startsWith("-")&&!s.includes("/")?$e(a)??n:n}function Ye(e,n,a,s){let r=null;function u(t,o={}){e.client.sendAgentEvent({type:"event",event:"agent",payload:Le(t,n,o),seq:t.seq,id:`agent-evt-${t.runId}-${t.seq}`})}function c(t){t?.heartbeatTimer&&(clearInterval(t.heartbeatTimer),t.heartbeatTimer=null)}function m(t){if(!t.currentRunId||!t.currentRunPhase)return;const o=t.heartbeatSeq++;e.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:n,runId:t.currentRunId,seq:o,runPhase:t.currentRunPhase},seq:o,id:`agent-heartbeat-${t.currentRunId}-${o}-${Date.now()}`})}function l(t){!t||t.heartbeatTimer||(t.heartbeatTimer=setInterval(()=>{m(t)},Ke),t.heartbeatTimer.unref?.())}function y(t){const o=t?.pendingTextEvent;!t||!o||!o.text||($(n,{id:`agent-${o.runId}-${o.seq}`,sessionId:n,role:"agent",ts:Date.now(),payload:o.text}),_(e.client),u({state:"delta",runId:o.runId,seq:o.seq,text:o.text,thinking:o.thinking||void 0}),t.pendingTextEvent=null)}s.on("agentEvent",t=>{t.errorCode==="acp_auth_required"&&Ve(e).catch(()=>{});const o=e.sessions.get(n),k=Qe(t),v=t.state==="final"||t.state==="error"||t.state==="aborted";if(o&&(o.nextEventSeq=t.seq+1,v||(o.currentRunId=t.runId),!v&&k&&(o.currentRunPhase=k,l(o)),t.agentSessionId&&(o.agentSessionId=t.agentSessionId),o.journalRunId)){const h={agentSessionId:t.agentSessionId??o.agentSessionId,providerRunId:t.runId||null,lastObservedEventSeq:t.seq,...v?{}:{phase:k==="waiting_approval"?"waiting_approval":"running"}};e.runJournal?.update(n,o.journalRunId,h)}if(e.managerRuntime?.noteAgentEvent(n,t),t.state==="delta"&&t.thinking)return;t.state!=="delta"&&C({level:"info",sessionId:n,wsEvent:`agent.${t.state}`,wsDirection:"out",metadata:{runId:t.runId,seq:t.seq,agentType:a}});let E=!1;if(t.state==="tool-call"||t.state==="tool-result"){const h=t.toolCallId?`agent-tool-${q("sha256").update(`${t.runId}\0${t.toolCallId}`).digest("hex")}`:`agent-${t.runId}-${t.seq}`;Ne({sessionId:n,toolCallId:h,detailRevision:t.seq,ts:Date.now(),name:t.name??"tool",...t.args!==void 0?{args:t.args}:{},...t.result!==void 0?{result:t.result}:{},status:t.toolStatus??(t.state==="tool-call"?"running":"completed"),...t.durationMs!==void 0?{durationMs:t.durationMs}:{},detailRef:{runId:t.runId,sourceSeq:t.seq}}),(t.toolCallId?j:$)(n,{id:h,sessionId:n,role:"agent",ts:Date.now(),payload:JSON.stringify({v:1,type:t.state==="tool-call"?"tool_use":"tool_result",name:t.name,toolCallId:h,status:t.toolStatus??(t.state==="tool-call"?"running":"completed"),...t.durationMs!==void 0?{durationMs:t.durationMs}:{},detailRef:{runId:t.runId,sourceSeq:t.seq},detailRevision:t.seq,detailAvailable:!0,resultOmitted:t.state==="tool-result"})}),E=!0}else t.state==="approval-pending"?($(n,{id:`agent-${t.runId||"run"}-${t.seq}`,sessionId:n,role:"agent",ts:Date.now(),payload:qe({...t.approval,permission:t.permission})}),E=!0,o&&U({sessionId:n,agentType:a,workDir:o.workDir,agentSessionId:o.agentSessionId,attentionId:Ie("approval",t.runId,t.seq)})):(t.state==="error"||t.state==="aborted")&&t.message&&($(n,{id:`agent-${t.runId||"run"}-${t.seq}`,sessionId:n,role:"agent",ts:Date.now(),payload:t.message}),E=!0);E&&_(e.client);const w=`${n}:${t.runId}`;if(t.state==="delta"&&!t.thinking&&t.text&&e.runTextAcc.set(w,(e.runTextAcc.get(w)??"")+t.text),t.agentSessionId&&t.agentSessionId!==r&&(r=t.agentSessionId,e.nativeFusion?.noteManagedSourceSession(n,Se(a,t.source),t.agentSessionId),o&&U({sessionId:n,agentType:a,workDir:o.workDir,agentSessionId:t.agentSessionId}),e.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:n,agentType:a,agentSessionId:t.agentSessionId}}})),t.state==="delta"){const h=t.text??"";if(!h)return;o?.pendingTextEvent&&o.pendingTextEvent.runId!==t.runId&&y(o),o&&!o.pendingTextEvent&&(o.pendingTextEvent={runId:t.runId,seq:t.seq,text:"",thinking:!1}),o?.pendingTextEvent&&(o.pendingTextEvent.text+=h,o.pendingTextEvent.seq=t.seq);return}let N={};if(t.state==="final"){y(o);const h=e.runTextAcc.get(w)??"";h&&(N={messageSummary:Ue(h)}),e.runTextAcc.delete(w)}else t.state==="error"||t.state==="aborted"?(y(o),e.runTextAcc.delete(w)):(t.state==="tool-call"||t.state==="tool-result"||t.state==="approval-pending")&&y(o);v&&o?.currentRunId===t.runId&&(B(e,o),o.currentRunId=null,o.currentRunPhase=null,o.nextEventSeq=0,c(o),e.chatQueue?.noteTerminal(n),U({sessionId:n,agentType:a,workDir:o.workDir,agentSessionId:o.agentSessionId,status:t.state==="final"?"completed":"failed",attentionId:Ie("terminal",t.runId),lastActivityAt:new Date().toISOString()}),_(e.client),o.journalRunId&&(e.runJournal?.remove(n,o.journalRunId),e.recoveryBarrier?.end(n),o.journalRunId=null)),u(t,N)}),s.on("error",t=>{console.error(`[chat.send] adapter error sessionId=${n} agentType=${a}: ${t.message}`);const o=e.sessions.get(n);B(e,o),o?.journalRunId&&(e.runJournal?.update(n,o.journalRunId,{phase:"terminal"}),e.runJournal?.remove(n,o.journalRunId),e.recoveryBarrier?.end(n),o.journalRunId=null),e.sessions.delete(n),e.chatQueue?.noteTerminal(n),e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:n,message:t.message,runId:"",seq:0}})})}function Ze(e,n){if(e.processedReqIds.add(n),e.processedReqIds.size>1e3){const a=e.processedReqIds.values().next().value;e.processedReqIds.delete(a)}}async function K(e,n){B(e,n),n.heartbeatTimer&&(clearInterval(n.heartbeatTimer),n.heartbeatTimer=null),n.adapter.removeAllListeners(),await n.adapter.stop().catch(()=>{})}function Ae(e,n,a,s,r,u,c){return c?e.send(n,a,s,r,u,c):u?.trim()?e.send(n,a,s,r,u):r?.length?e.send(n,a,s,r):s?e.send(n,a,s):e.send(n,a)}function en(e,n,a,s,r,u,c){return e.prepareSend?e.prepareSend(n,a,s,r,u,c):Promise.resolve()}async function Pe(e,n,a,s,r,u,c,m,l,y="user"){e.evictIdleSessions();const t=be(a);if(!t)throw new Error(`Unsupported agent: ${a}`);const o=s==="manager"&&a!=="manager"?new Ce(t,a):t;L(o,n,a,c,m,null,l,e.managedRoomExecutor),y==="internal"?await o.start(n,r,u,{nativeSessionVisibility:y}):await o.start(n,r,u);const k={adapter:o,workDir:r,agentType:a,sessionMode:s,nativeSessionVisibility:y,agentSessionId:u??null,lastActiveAt:Date.now(),currentRunId:null,currentRunPhase:null,nextEventSeq:0,heartbeatSeq:0,heartbeatTimer:null,pendingTextEvent:null,externalChannel:c??null,externalReplyTarget:m??null,externalChannelEnv:{...ke(a),...we(n,c,m)},managedRoomContextToken:null,journalRunId:null};return e.sessions.set(n,k),Ye(e,n,a,o),k}async function An(e,n,a=()=>{}){let s=null,r=null,u=null,c;if(n.source==="managed_room"||n.source==="managed_direct"){const l=await e.prepareManagedRecovery?.(n);if(!l)return{result:"recovery_failed",reason:"permission_context_unavailable",message:"managed recovery requires a fresh permission context"};if(l.agentType!==n.agentType||l.sessionMode!==n.sessionMode||e.resolvePath(l.workDir)!==e.resolvePath(n.workDir)||l.managedRoomPromptSnapshot.managedRoomPolicyVersion!==n.managedPolicyVersion||l.managedRoomPromptSnapshot.runtimePermissionPolicyHash!==n.managedRuntimePolicyHash)return e.revokeManagedRoomContextToken?.(l.managedRoomContextToken),{result:"recovery_failed",reason:"permission_context_unavailable",message:"managed recovery authority no longer matches the interrupted run"};const y={source:"room_activation",managedAgentPolicy:l.managedAgentPolicy,managedRoomPromptSnapshot:l.managedRoomPromptSnapshot};he(y,l.systemPrompt,l.managedRoomContextToken),s=l.managedAgentPolicy,r=l.managedRoomContextToken,u=l.systemPrompt,c={...y,recovery:"upgrade"}}else if(n.source==="manager_worker"){const l=n.parentSessionId;if(s=l?e.managerRuntime?.getManagedAgentPolicyForWorker(l,n.workDir)??null:null,!s)return{result:"recovery_failed",reason:"permission_context_unavailable",message:"manager worker recovery requires the current parent policy"};if(q("sha256").update(JSON.stringify({requestedPolicy:s.requestedPolicy,enforcement:s.enforcement})).digest("hex")!==n.managedRuntimePolicyHash)return{result:"recovery_failed",reason:"permission_context_unavailable",message:"manager worker policy changed after interruption"};u=ye({requestedPolicy:s.requestedPolicy,enforcement:s.enforcement,resolvedWorkDir:s.adapterPolicy.workDir})}c??={source:"upgrade_recovery",recovery:"upgrade"};let m=e.sessions.get(n.sessionId);return m||(m=await Pe(e,n.sessionId,n.agentType,n.sessionMode,n.workDir,n.agentSessionId,null,null,s,n.nativeSessionVisibility)),r&&(oe(e,m,r),L(m.adapter,n.sessionId,n.agentType,null,null,r,s,e.managedRoomExecutor)),m.journalRunId=n.runId,m.currentRunId=n.providerRunId??`recovery:${n.runId}`,m.currentRunPhase="thinking",m.adapter.recoverInterruptedRun({sessionId:n.sessionId,shennianRunId:n.runId,workDir:n.workDir,agentSessionId:n.agentSessionId,modelId:n.modelId,reasoningEffort:n.reasoningEffort,providerRunId:n.providerRunId,lastObservedEventSeq:n.lastObservedEventSeq,continueDispatchState:n.continueDispatchState,systemPrompt:u,sendContext:c,beforeContinueDispatch:a})}function nn(e,n){const a=e.sessions.get(n),s=a?.currentRunId;if(!a||!s)return;const r=a.nextEventSeq;e.runTextAcc.delete(`${n}:${s}`),B(e,a),a.pendingTextEvent=null,a.currentRunId=null,a.currentRunPhase=null,a.nextEventSeq=0,a.heartbeatTimer&&(clearInterval(a.heartbeatTimer),a.heartbeatTimer=null),e.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"aborted",sessionId:n,runId:s,seq:r},seq:r,id:`agent-evt-${s}-${r}`}),a.journalRunId&&(e.runJournal?.remove(n,a.journalRunId),e.recoveryBarrier?.end(n),a.journalRunId=null),e.chatQueue?.noteTerminal(n)}async function Pn(e,n,a){const s=a?n.params.managedRoomContextToken??null:null;let r=!1;try{await tn(e,n,a,()=>{r=!0})}finally{s&&!r&&e.revokeManagedRoomContextToken?.(s)}}async function tn(e,n,a,s){if(e.processedReqIds.has(n.id)){e.client.sendRes({type:"res",id:n.id,ok:!0});return}const{sessionId:r,text:u,agentType:c,sessionMode:m,managerConfig:l,workDir:y,agentSessionId:t,modelId:o,systemPrompt:k,managerDefaultWorkerAgentType:v,managerDefaultWorkerModelId:E,reasoningEffort:w,clientMessageId:N,sessionListProjection:h,waitForDispatch:Q,responseId:_e,managedRoomContextToken:O,suppressExternalOwnerEcho:ve,queuedBatchItems:G,managedWorkerParentSessionId:M,managedRecoverySource:Ee}=n.params,I=_e||n.id;Oe(h);const F=null,X="";if(a?.source==="room_activation"&&(he(a,k,O),c==="manager"))throw new Error("managed_agent_manager_mode_unsupported");if(Ze(e,n.id),!r||!u){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:I,ok:!1,error:"sessionId and text are required"});return}if(e.recoveryBarrier?.isDispatchFrozen()){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:I,ok:!1,error:"upgrade_in_progress"});return}e.recoveryBarrier?.isRecovering(r)&&await e.recoveryBarrier.wait(r);const p=c,D=p==="manager"||m==="manager"?"manager":"standard";D==="manager"&&e.managerRuntime?.setManagerWorkerDefaults(r,v??null,E??null);const W=(p==="claude"||p==="codex"||p==="workbuddy"||p==="pi-coding-agent"||p.startsWith("acp:"))&&typeof w=="string"&&w.trim()?w.trim():void 0,A=e.resolvePath(Xe(p,y||pe.homedir(),t)||pe.homedir()),P=a?.managedAgentPolicy??(M?e.managerRuntime?.getManagedAgentPolicyForWorker(M,A)??null:null);if(M&&!P)throw new Error("managed_worker_permission_context_unavailable");const Y=p==="codex"&&(a?.source==="room_activation"||M)?"internal":"user",Z=M&&P?ye({requestedPolicy:P.requestedPolicy,enforcement:P.enforcement,resolvedWorkDir:P.adapterPolicy.workDir}):k,xe=A,z=He(n.params.attachments);z?.length&&Je(A,z);const R={text:u,attachments:z,localized:!1};let d=e.sessions.get(r);if(d){if(d.lastActiveAt=Date.now(),d.agentType!==p||(d.sessionMode??(d.agentType==="manager"?"manager":"standard"))!==D||d.workDir!==A||(d.nativeSessionVisibility??"user")!==Y||JSON.stringify(d.externalChannel??null)!==JSON.stringify(F??null)){e.sessions.delete(r);try{await K(e,d)}catch{e.processedReqIds.delete(n.id)}d=void 0}else if(t&&d.agentSessionId!==t)try{await d.adapter.resume(t),d.agentSessionId=t}catch{e.sessions.delete(r);try{await K(e,d)}catch{e.processedReqIds.delete(n.id)}d=void 0}}if(!d)try{d=await Pe(e,r,p,D,A,t,F,X,P,Y)}catch(i){const f=i instanceof Error&&i.message.startsWith("Unsupported agent:")?i.message:`Failed to start ${c}: ${i instanceof Error?i.message:String(i)}`;console.error(`[chat.send] start failed reqId=${n.id} sessionId=${r} agentType=${c} workDir=${A} agentSessionId=${t??""}: ${f}`),e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:r,message:f,runId:"",seq:0}}),e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:I,ok:!1,error:f});return}const S={id:N??`user-${n.id}`,sessionId:r,role:"user",ts:Date.now(),payload:me(R.text,R.attachments)},re=Array.isArray(G)&&G.length>0?G.map(i=>({id:i.clientMessageId??i.id,sessionId:r,role:"user",ts:Date.parse(i.createdAt),payload:me(i.text,i.attachments)})):null,V=re??[S],x=je(r,S.id),se=x.state==="queued";if(x.state!=="not_found"&&!se){e.client.sendRes({type:"res",id:I,ok:!0,payload:x});return}if(Q){a?.source==="room_activation"&&L(d.adapter,r,p,F,X,O,a.managedAgentPolicy,e.managedRoomExecutor);try{await en(d.adapter,R.text,o,W,R.attachments,Z,a?{source:a.source}:void 0)}catch(i){e.processedReqIds.delete(n.id),e.sessions.delete(r),await K(e,d),e.client.sendRes({type:"res",id:I,ok:!1,error:i instanceof Error?i.message:String(i)});return}}C({level:"info",sessionId:r,wsEvent:"chat.send.start",metadata:{reqId:n.id,agentType:p,sessionMode:D,managerConfig:l??null,modelId:o,managerDefaultWorkerAgentType:v??null,managerDefaultWorkerModelId:E??null,reasoningEffort:W,...a?.source==="room_activation"?{managedRoomPromptSnapshot:a.managedRoomPromptSnapshot}:{}}});const H=(i,f)=>({...fe(ae()),sessionId:r,clientMessageId:S.id,messageId:S.id,deliveryState:i,bodyRevision:f,committedAt:new Date(S.ts).toISOString()}),ie=i=>{e.nativeFusion?.registerManagedSend({sessionId:r,agentType:p,sourceAgentType:Se(p,o),canonicalMessageId:S.id,sourceSessionKey:d.agentSessionId??t??null,text:R.text,managedEchoPolicy:i?.deliveryMode==="external_owner_queue"&&!ve?"import_following":"suppress_following",externalRunId:i?.deliveryMode==="external_owner_queue"?`codex-external-${i.queuedSubmissionId??S.id}`:null})},de=()=>{d.currentRunId||(d.currentRunId=`dispatch:${n.id}`,d.currentRunPhase="thinking",d.nextEventSeq=0)},le=()=>{if(!e.runJournal||d.journalRunId)return;const i=`run:${De()}`;e.runJournal.begin({daemonInstallationId:ae(),sessionId:r,runId:i,clientMessageId:S.id,agentType:p,sessionMode:D,workDir:A,agentSessionId:d.agentSessionId??t??null,modelId:o??null,reasoningEffort:W??null,nativeSessionVisibility:Y,providerRunId:null,source:a?.source==="room_activation"?Ee==="managed_direct"?"managed_direct":"managed_room":M?"manager_worker":"personal",parentSessionId:M??null,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:a?.managedRoomPromptSnapshot.managedRoomPolicyVersion??null,managedRuntimePolicyHash:a?.managedRoomPromptSnapshot.runtimePermissionPolicyHash??(P?q("sha256").update(JSON.stringify({requestedPolicy:P.requestedPolicy,enforcement:P.enforcement})).digest("hex"):null)}),d.journalRunId=i},ue=()=>{if(ie(),se)return{...fe(ae()),sessionId:r,clientMessageId:x.clientMessageId,messageId:x.messageId,deliveryState:x.deliveryState,bodyRevision:x.bodyRevision,committedAt:x.committedAt};try{Ge(e,{sessionId:r,agentType:p,sessionMode:D,managerConfig:l??null,workDir:xe,agentSessionId:d.agentSessionId??t??null,modelId:o,managerDefaultWorkerAgentType:v??null,managerDefaultWorkerModelId:E??null});let i=0;for(const[f,g]of V.entries()){const T=re?j(r,g,"committed"):$(r,g,"committed");f===0&&(i=T)}return Be(A,z??[]),_(e.client),H("committed",i)}catch(i){throw e.nativeFusion?.cancelManagedSend?.(S.id),i}},ce=i=>{d.currentRunId||(d.nextEventSeq=0),ie(i);const f=i?.deliveryMode==="external_owner_queue"?"queued":"executing";let g=0;for(const[T,J]of V.entries()){const b=j(r,J,f);T===0&&(g=b)}return _(e.client),H(f,g)},ee=async(i,f)=>{e.nativeFusion?.cancelManagedSend?.(S.id);const g=Re(p,i);if(console.error(`[chat.send] send failed reqId=${n.id} sessionId=${r} agentType=${c} workDir=${A} agentSessionId=${d.agentSessionId??t??""}: ${g}`),d.adapter.hasPendingAuthenticationRetry?.()===!0){f&&e.client.sendRes({type:"res",id:I,ok:!0,payload:H("queued",0)});return}e.sessions.delete(r),d.journalRunId&&(e.runJournal?.update(r,d.journalRunId,{phase:"terminal"}),e.runJournal?.remove(r,d.journalRunId),e.recoveryBarrier?.end(r),d.journalRunId=null);try{await K(e,d)}catch{}if(e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:r,message:g,runId:"",seq:0}}),!f){const J={id:`agent-error-${n.id}-${Date.now()}`,sessionId:r,role:"agent",ts:Date.now(),payload:g};$(r,J),_(e.client)}f&&(e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:I,ok:!1,error:g}))};if(Q){try{ue()}catch(g){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:I,ok:!1,error:`Durable message commit failed: ${g instanceof Error?g.message:String(g)}`});return}de(),le();let i;try{a?.source==="room_activation"&&(oe(e,d,O),s());const g=R.attachments;i=await Ae(d.adapter,R.text,o,W,g,Z,a?{source:a.source}:void 0),C({level:"info",sessionId:r,wsEvent:"chat.send.done",metadata:{reqId:n.id}})}catch(g){if(d.adapter.hasPendingAuthenticationRetry?.()){let b=0;for(const[ne,te]of V.entries()){const Te=j(r,te,"queued");ne===0&&(b=Te)}_(e.client),await ee(g,!1),e.client.sendRes({type:"res",id:I,ok:!0,payload:H("queued",b)});return}let T=0;for(const[b,ne]of V.entries()){const te=j(r,ne,"failed");b===0&&(T=te)}_(e.client);const J=Re(p,g);await ee(g,!1),e.client.sendRes({type:"res",id:I,ok:!0,payload:{...H("failed",T),failureMessage:J}});return}const f=ce(i??void 0);e.client.sendRes({type:"res",id:I,ok:!0,payload:{...f,...R.localized?{localizedAttachments:!0}:{},...i?.deliveryMode==="external_owner_queue"?{queued:!0,deliveryMode:i.deliveryMode,queuedSubmissionId:i.queuedSubmissionId}:{}}}),C({level:"info",sessionId:r,wsEvent:"chat.send.res",metadata:{reqId:n.id,ok:!0}});return}let ge;try{ge=ue()}catch(i){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:I,ok:!1,error:`Durable message commit failed: ${i instanceof Error?i.message:String(i)}`});return}de(),le(),a?.source==="room_activation"&&(L(d.adapter,r,p,F,X,O,a.managedAgentPolicy,e.managedRoomExecutor),oe(e,d,O),s()),ce(),e.client.sendRes({type:"res",id:I,ok:!0,payload:{...ge,...R.localized?{localizedAttachments:!0}:{}}}),C({level:"info",sessionId:r,wsEvent:"chat.send.res",metadata:{reqId:n.id,ok:!0}});const Me=R.attachments;Ae(d.adapter,R.text,o,W,Me,Z,a?{source:a.source}:void 0).then(()=>{C({level:"info",sessionId:r,wsEvent:"chat.send.done",metadata:{reqId:n.id}})}).catch(i=>{j(r,S,d.adapter.hasPendingAuthenticationRetry?.()?"queued":"failed"),_(e.client),ee(i,!1)})}async function _n(e,n){const{sessionId:a}=n.params,s=e.sessions.get(a);if(s){s.journalRunId&&e.runJournal?.markAborted(a,s.journalRunId),B(e,s);try{await s.adapter.stop()}catch{e.client.sendRes({type:"res",id:n.id,ok:!1,error:"agent_stop_failed"});return}nn(e,a),e.activityPublisher?.publish(a,null)}e.client.sendRes({type:"res",id:n.id,ok:!0})}export{_n as handleChatAbort,Pn as handleChatSend,An as recoverJournaledRun,Ge as sendSessionUpdateEvent};
1
+ import fe from"node:os";import{createHash as C,randomUUID as be}from"node:crypto";import{createAgent as qe}from"../../agents/adapter.js";import{buildApprovalPendingPayload as Ce,buildUserMessagePayload as me}from"@shennian/wire";import{ManagerModeAdapter as $e}from"../../agents/manager.js";import{reportLog as $}from"../../log-reporter.js";import{lookupClaudeTranscriptCwd as je}from"../../native-fusion/parsers.js";import{appendMessage as j,getDaemonInstallationId as oe,getMessageDeliveryStatus as Oe,recordSession as U,upsertMessage as D}from"../store.js";import{mergeProjectedSessions as We}from"../projection.js";import{buildManagedAgentEnv as He}from"../../agents/config-status.js";import{parsePersonalAttachmentReferences as Je}from"../attachment-reference.js";import{assertFinishedAttachmentReferences as Be,markAttachmentReferences as Ne}from"../attachment-transfer-store.js";import{requestSessionIndexPublish as w}from"../index-publisher.js";import{writeToolDetail as Fe}from"../tool-detail-store.js";import{getPersonalSyncIdentity as ye}from"../../personal-sync/epoch.js";import{buildManagedAgentPermissionPrompt as Ie}from"../../room/managed-prompt-composer.js";import{getManagedRoomIpcRoot as ze,MANAGED_ROOM_IPC_ROOT_ENV as Ve}from"../../daemon-ipc/paths.js";import{refreshMachineAgents as Ue}from"./agents.js";function he(e,n,a){if(!e.managedAgentPolicy)throw new Error("managed_agent_policy_missing");const s=e.managedRoomPromptSnapshot;if(!s)throw new Error("managed_room_prompt_snapshot_missing");if(!n?.trim())throw new Error("managed_room_prompt_incomplete");if(!a||!/^mrc_[A-Za-z0-9_-]{43}$/.test(a))throw new Error("managed_room_context_unavailable");const o=C("sha256").update(JSON.stringify({requestedPolicy:e.managedAgentPolicy.requestedPolicy,enforcement:e.managedAgentPolicy.enforcement})).digest("hex"),u=C("sha256").update(e.managedAgentPolicy.adapterPolicy.workDir).digest("hex");if(s.runtimePermissionPolicyHash!==o||s.resolvedWorkDirHash!==u||s.enforcementLevel!==e.managedAgentPolicy.enforcement.level)throw new Error("managed_room_prompt_snapshot_mismatch")}function Le(e){const n=e.indexOf(`
2
+ `),a=n>0?Math.min(n,80):Math.min(e.length,80);return e.slice(0,a)}function Re(e,n,a){return`${e}:${C("sha256").update(`${n}\0${a??""}`).digest("hex")}`}function Ke(e,n,a={}){if(e.state==="tool-call"||e.state==="tool-result"){const s={runId:e.runId,sourceSeq:e.seq};return{state:e.state,runId:e.runId,seq:e.seq,sessionId:n,detailRef:s,status:e.state==="tool-call"?"running":"completed",detailAvailable:!0,...e.durationMs!==void 0?{durationMs:e.durationMs}:{},...e.name?{name:e.name}:{},...e.source?{source:e.source}:{},...e.agentSessionId?{agentSessionId:e.agentSessionId}:{},...a}}return{...e,sessionId:n,...a}}const Qe=3e4;function Ge(e){return e.state==="heartbeat"?e.runPhase??null:e.state==="tool-call"||e.state==="tool-result"?"tool_running":e.state==="approval-pending"?"waiting_approval":e.state==="delta"?e.thinking?"thinking":"streaming_text":e.state==="init"||e.state==="start"?"thinking":null}function Se(e,n){return`Agent send failed: ${n instanceof Error?n.message:String(n)}`}function we(e,n){return e!=="manager"?e:n==="claude"?"claude":"codex"}function Xe(e,n){U({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 ke(e){return He(e)}function _e(e,n,a){return{}}function L(e,n,a,s,o,u,c,f){const l=a==="codex";e.configure?.({sessionId:n,externalChannel:s??null,env:{...ke(a),..._e(n,s,o),...u&&!l?{SHENNIAN_MANAGED_ROOM_CONTEXT:u,[Ve]:ze()}:{}},...c?{managedAgentPolicy:c}:{},...l?{managedRoomContextToken:u??null,managedRoomExecutor:u?f??null:null}:{}})}function re(e,n,a){const s=n.managedRoomContextToken;s&&s!==a&&e.revokeManagedRoomContextToken?.(s),n.managedRoomContextToken=a}function N(e,n){const a=n?.managedRoomContextToken;!n||!a||(n.managedRoomContextToken=null,e.revokeManagedRoomContextToken?.(a))}function Ye(e,n,a){if(e!=="claude"||!a)return n;const s=n.trim();return!!s&&s.startsWith("-")&&!s.includes("/")?je(a)??n:n}function Ze(e,n,a,s){let o=null;function u(t,r={}){e.client.sendAgentEvent({type:"event",event:"agent",payload:Ke(t,n,r),seq:t.seq,id:`agent-evt-${t.runId}-${t.seq}`})}function c(t){t?.heartbeatTimer&&(clearInterval(t.heartbeatTimer),t.heartbeatTimer=null)}function f(t){if(!t.currentRunId||!t.currentRunPhase)return;const r=t.heartbeatSeq++;e.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:n,runId:t.currentRunId,seq:r,runPhase:t.currentRunPhase},seq:r,id:`agent-heartbeat-${t.currentRunId}-${r}-${Date.now()}`})}function l(t){!t||t.heartbeatTimer||(t.heartbeatTimer=setInterval(()=>{f(t)},Qe),t.heartbeatTimer.unref?.())}function y(t){const r=t?.pendingTextEvent;!t||!r||!r.text||(j(n,{id:`agent-${r.runId}-${r.seq}`,sessionId:n,role:"agent",ts:Date.now(),payload:r.text}),w(e.client),u({state:"delta",runId:r.runId,seq:r.seq,text:r.text,thinking:r.thinking||void 0}),t.pendingTextEvent=null)}s.on("agentEvent",t=>{t.errorCode==="acp_auth_required"&&Ue(e).catch(()=>{});const r=e.sessions.get(n),k=Ge(t),x=t.state==="final"||t.state==="error"||t.state==="aborted";if(r&&(r.nextEventSeq=t.seq+1,x||(r.currentRunId=t.runId),!x&&k&&(r.currentRunPhase=k,l(r)),t.agentSessionId&&(r.agentSessionId=t.agentSessionId),r.journalRunId)){const I={agentSessionId:t.agentSessionId??r.agentSessionId,providerRunId:t.runId||null,lastObservedEventSeq:t.seq,...x?{}:{phase:k==="waiting_approval"?"waiting_approval":"running"}};e.runJournal?.update(n,r.journalRunId,I)}if(e.managerRuntime?.noteAgentEvent(n,t),t.state==="delta"&&t.thinking)return;t.state!=="delta"&&$({level:"info",sessionId:n,wsEvent:`agent.${t.state}`,wsDirection:"out",metadata:{runId:t.runId,seq:t.seq,agentType:a}});let P=!1;if(t.state==="tool-call"||t.state==="tool-result"){const I=t.toolCallId?`agent-tool-${C("sha256").update(`${t.runId}\0${t.toolCallId}`).digest("hex")}`:`agent-${t.runId}-${t.seq}`;Fe({sessionId:n,toolCallId:I,detailRevision:t.seq,ts:Date.now(),name:t.name??"tool",...t.args!==void 0?{args:t.args}:{},...t.result!==void 0?{result:t.result}:{},status:t.toolStatus??(t.state==="tool-call"?"running":"completed"),...t.durationMs!==void 0?{durationMs:t.durationMs}:{},detailRef:{runId:t.runId,sourceSeq:t.seq}}),(t.toolCallId?D:j)(n,{id:I,sessionId:n,role:"agent",ts:Date.now(),payload:JSON.stringify({v:1,type:t.state==="tool-call"?"tool_use":"tool_result",name:t.name,toolCallId:I,status:t.toolStatus??(t.state==="tool-call"?"running":"completed"),...t.durationMs!==void 0?{durationMs:t.durationMs}:{},detailRef:{runId:t.runId,sourceSeq:t.seq},detailRevision:t.seq,detailAvailable:!0,resultOmitted:t.state==="tool-result"})}),P=!0}else t.state==="approval-pending"?(j(n,{id:`agent-${t.runId||"run"}-${t.seq}`,sessionId:n,role:"agent",ts:Date.now(),payload:Ce({...t.approval,permission:t.permission})}),P=!0,r&&U({sessionId:n,agentType:a,workDir:r.workDir,agentSessionId:r.agentSessionId,attentionId:Re("approval",t.runId,t.seq)})):(t.state==="error"||t.state==="aborted")&&t.message&&(j(n,{id:`agent-${t.runId||"run"}-${t.seq}`,sessionId:n,role:"agent",ts:Date.now(),payload:t.message}),P=!0);P&&w(e.client);const _=`${n}:${t.runId}`;if(t.state==="delta"&&!t.thinking&&t.text&&e.runTextAcc.set(_,(e.runTextAcc.get(_)??"")+t.text),t.agentSessionId&&t.agentSessionId!==o&&(o=t.agentSessionId,e.nativeFusion?.noteManagedSourceSession(n,we(a,t.source),t.agentSessionId),r&&U({sessionId:n,agentType:a,workDir:r.workDir,agentSessionId:t.agentSessionId}),e.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:n,agentType:a,agentSessionId:t.agentSessionId}}})),t.state==="delta"){const I=t.text??"";if(!I)return;r?.pendingTextEvent&&r.pendingTextEvent.runId!==t.runId&&y(r),r&&!r.pendingTextEvent&&(r.pendingTextEvent={runId:t.runId,seq:t.seq,text:"",thinking:!1}),r?.pendingTextEvent&&(r.pendingTextEvent.text+=I,r.pendingTextEvent.seq=t.seq);return}let F={};if(t.state==="final"){y(r);const I=e.runTextAcc.get(_)??"";I&&(F={messageSummary:Le(I)}),e.runTextAcc.delete(_)}else t.state==="error"||t.state==="aborted"?(y(r),e.runTextAcc.delete(_)):(t.state==="tool-call"||t.state==="tool-result"||t.state==="approval-pending")&&y(r);x&&r?.currentRunId===t.runId&&(N(e,r),r.currentRunId=null,r.currentRunPhase=null,r.nextEventSeq=0,c(r),e.chatQueue?.noteTerminal(n),U({sessionId:n,agentType:a,workDir:r.workDir,agentSessionId:r.agentSessionId,status:t.state==="final"?"completed":"failed",attentionId:Re("terminal",t.runId),lastActivityAt:new Date().toISOString()}),w(e.client),r.journalRunId&&(e.runJournal?.remove(n,r.journalRunId),e.recoveryBarrier?.end(n),r.journalRunId=null)),u(t,F)}),s.on("error",t=>{console.error(`[chat.send] adapter error sessionId=${n} agentType=${a}: ${t.message}`);const r=e.sessions.get(n);N(e,r),r?.journalRunId&&(e.runJournal?.update(n,r.journalRunId,{phase:"terminal"}),e.runJournal?.remove(n,r.journalRunId),e.recoveryBarrier?.end(n),r.journalRunId=null),e.sessions.delete(n),e.chatQueue?.noteTerminal(n),e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:n,message:t.message,runId:"",seq:0}})})}function en(e,n){if(e.processedReqIds.add(n),e.processedReqIds.size>1e3){const a=e.processedReqIds.values().next().value;e.processedReqIds.delete(a)}}async function K(e,n){N(e,n),n.heartbeatTimer&&(clearInterval(n.heartbeatTimer),n.heartbeatTimer=null),n.adapter.removeAllListeners(),await n.adapter.stop().catch(()=>{})}function ve(e,n,a,s,o,u,c){return c?e.send(n,a,s,o,u,c):u?.trim()?e.send(n,a,s,o,u):o?.length?e.send(n,a,s,o):s?e.send(n,a,s):e.send(n,a)}function nn(e,n,a,s,o,u,c){return e.prepareSend?e.prepareSend(n,a,s,o,u,c):Promise.resolve()}async function Ae(e,n,a,s,o,u,c,f,l,y="user"){e.evictIdleSessions();const t=qe(a);if(!t)throw new Error(`Unsupported agent: ${a}`);const r=s==="manager"&&a!=="manager"?new $e(t,a):t;L(r,n,a,c,f,null,l,e.managedRoomExecutor),y==="internal"?await r.start(n,o,u,{nativeSessionVisibility:y}):await r.start(n,o,u);const k={adapter:r,workDir:o,agentType:a,sessionMode:s,nativeSessionVisibility:y,agentSessionId:u??null,lastActiveAt:Date.now(),currentRunId:null,currentRunPhase:null,nextEventSeq:0,heartbeatSeq:0,heartbeatTimer:null,pendingTextEvent:null,externalChannel:c??null,externalReplyTarget:f??null,externalChannelEnv:{...ke(a),..._e(n,c,f)},managedRoomContextToken:null,journalRunId:null};return e.sessions.set(n,k),Ze(e,n,a,r),k}async function vn(e,n,a=()=>{}){let s=null,o=null,u=null,c;if(n.source==="managed_room"||n.source==="managed_direct"){const l=await e.prepareManagedRecovery?.(n);if(!l)return{result:"recovery_failed",reason:"permission_context_unavailable",message:"managed recovery requires a fresh permission context"};if(l.agentType!==n.agentType||l.sessionMode!==n.sessionMode||e.resolvePath(l.workDir)!==e.resolvePath(n.workDir)||l.managedRoomPromptSnapshot.managedRoomPolicyVersion!==n.managedPolicyVersion||l.managedRoomPromptSnapshot.runtimePermissionPolicyHash!==n.managedRuntimePolicyHash)return e.revokeManagedRoomContextToken?.(l.managedRoomContextToken),{result:"recovery_failed",reason:"permission_context_unavailable",message:"managed recovery authority no longer matches the interrupted run"};const y={source:"room_activation",managedAgentPolicy:l.managedAgentPolicy,managedRoomPromptSnapshot:l.managedRoomPromptSnapshot};he(y,l.systemPrompt,l.managedRoomContextToken),s=l.managedAgentPolicy,o=l.managedRoomContextToken,u=l.systemPrompt,c={...y,recovery:"upgrade"}}else if(n.source==="manager_worker"){const l=n.parentSessionId;if(s=l?e.managerRuntime?.getManagedAgentPolicyForWorker(l,n.workDir)??null:null,!s)return{result:"recovery_failed",reason:"permission_context_unavailable",message:"manager worker recovery requires the current parent policy"};if(C("sha256").update(JSON.stringify({requestedPolicy:s.requestedPolicy,enforcement:s.enforcement})).digest("hex")!==n.managedRuntimePolicyHash)return{result:"recovery_failed",reason:"permission_context_unavailable",message:"manager worker policy changed after interruption"};u=Ie({requestedPolicy:s.requestedPolicy,enforcement:s.enforcement,resolvedWorkDir:s.adapterPolicy.workDir})}c??={source:"upgrade_recovery",recovery:"upgrade"};let f=e.sessions.get(n.sessionId);return f||(f=await Ae(e,n.sessionId,n.agentType,n.sessionMode,n.workDir,n.agentSessionId,null,null,s,n.nativeSessionVisibility)),o&&(re(e,f,o),L(f.adapter,n.sessionId,n.agentType,null,null,o,s,e.managedRoomExecutor)),f.journalRunId=n.runId,f.currentRunId=n.providerRunId??`recovery:${n.runId}`,f.currentRunPhase="thinking",n.agentType==="codex"&&n.deliveryMode==="external_owner_queue"&&n.agentSessionId&&n.externalRunId&&e.nativeFusion?.restoreExternalObservation({sessionId:n.sessionId,sourceSessionKey:n.agentSessionId,runId:n.externalRunId,canonicalMessageId:n.clientMessageId,createdAt:Date.parse(n.createdAt)})?{result:"reattached"}:f.adapter.recoverInterruptedRun({sessionId:n.sessionId,shennianRunId:n.runId,workDir:n.workDir,agentSessionId:n.agentSessionId,modelId:n.modelId,reasoningEffort:n.reasoningEffort,providerRunId:n.providerRunId,lastObservedEventSeq:n.lastObservedEventSeq,continueDispatchState:n.continueDispatchState,systemPrompt:u,sendContext:c,beforeContinueDispatch:a})}function tn(e,n){const a=e.sessions.get(n),s=a?.currentRunId;if(!a||!s)return;const o=a.nextEventSeq;e.runTextAcc.delete(`${n}:${s}`),N(e,a),a.pendingTextEvent=null,a.currentRunId=null,a.currentRunPhase=null,a.nextEventSeq=0,a.heartbeatTimer&&(clearInterval(a.heartbeatTimer),a.heartbeatTimer=null),e.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"aborted",sessionId:n,runId:s,seq:o},seq:o,id:`agent-evt-${s}-${o}`}),a.journalRunId&&(e.runJournal?.remove(n,a.journalRunId),e.recoveryBarrier?.end(n),a.journalRunId=null),e.chatQueue?.noteTerminal(n)}async function An(e,n,a){const s=a?n.params.managedRoomContextToken??null:null;let o=!1;try{await an(e,n,a,()=>{o=!0})}finally{s&&!o&&e.revokeManagedRoomContextToken?.(s)}}async function an(e,n,a,s){if(e.processedReqIds.has(n.id)){e.client.sendRes({type:"res",id:n.id,ok:!0});return}const{sessionId:o,text:u,agentType:c,sessionMode:f,managerConfig:l,workDir:y,agentSessionId:t,modelId:r,systemPrompt:k,managerDefaultWorkerAgentType:x,managerDefaultWorkerModelId:P,reasoningEffort:_,clientMessageId:F,sessionListProjection:I,waitForDispatch:Q,responseId:xe,managedRoomContextToken:O,suppressExternalOwnerEcho:Pe,queuedBatchItems:G,managedWorkerParentSessionId:E,managedRecoverySource:Me}=n.params,h=xe||n.id;We(I);const z=null,X="";if(a?.source==="room_activation"&&(he(a,k,O),c==="manager"))throw new Error("managed_agent_manager_mode_unsupported");if(en(e,n.id),!o||!u){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:h,ok:!1,error:"sessionId and text are required"});return}if(e.recoveryBarrier?.isDispatchFrozen()){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:h,ok:!1,error:"upgrade_in_progress"});return}e.recoveryBarrier?.isRecovering(o)&&await e.recoveryBarrier.wait(o);const p=c,b=p==="manager"||f==="manager"?"manager":"standard";b==="manager"&&e.managerRuntime?.setManagerWorkerDefaults(o,x??null,P??null);const W=(p==="claude"||p==="codex"||p==="workbuddy"||p==="pi-coding-agent"||p.startsWith("acp:"))&&typeof _=="string"&&_.trim()?_.trim():void 0,v=e.resolvePath(Ye(p,y||fe.homedir(),t)||fe.homedir()),A=a?.managedAgentPolicy??(E?e.managerRuntime?.getManagedAgentPolicyForWorker(E,v)??null:null);if(E&&!A)throw new Error("managed_worker_permission_context_unavailable");const Y=p==="codex"&&(a?.source==="room_activation"||E)?"internal":"user",Z=E&&A?Ie({requestedPolicy:A.requestedPolicy,enforcement:A.enforcement,resolvedWorkDir:A.adapterPolicy.workDir}):k,Ee=v,V=Je(n.params.attachments);V?.length&&Be(v,V);const S={text:u,attachments:V,localized:!1};let d=e.sessions.get(o);if(d){if(d.lastActiveAt=Date.now(),d.agentType!==p||(d.sessionMode??(d.agentType==="manager"?"manager":"standard"))!==b||d.workDir!==v||(d.nativeSessionVisibility??"user")!==Y||JSON.stringify(d.externalChannel??null)!==JSON.stringify(z??null)){e.sessions.delete(o);try{await K(e,d)}catch{e.processedReqIds.delete(n.id)}d=void 0}else if(t&&d.agentSessionId!==t)try{await d.adapter.resume(t),d.agentSessionId=t}catch{e.sessions.delete(o);try{await K(e,d)}catch{e.processedReqIds.delete(n.id)}d=void 0}}if(!d)try{d=await Ae(e,o,p,b,v,t,z,X,A,Y)}catch(i){const m=i instanceof Error&&i.message.startsWith("Unsupported agent:")?i.message:`Failed to start ${c}: ${i instanceof Error?i.message:String(i)}`;console.error(`[chat.send] start failed reqId=${n.id} sessionId=${o} agentType=${c} workDir=${v} agentSessionId=${t??""}: ${m}`),e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:o,message:m,runId:"",seq:0}}),e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:h,ok:!1,error:m});return}const R={id:F??`user-${n.id}`,sessionId:o,role:"user",ts:Date.now(),payload:me(S.text,S.attachments)},se=Array.isArray(G)&&G.length>0?G.map(i=>({id:i.clientMessageId??i.id,sessionId:o,role:"user",ts:Date.parse(i.createdAt),payload:me(i.text,i.attachments)})):null,H=se??[R],M=Oe(o,R.id),ie=M.state==="queued";if(M.state!=="not_found"&&!ie){e.client.sendRes({type:"res",id:h,ok:!0,payload:M});return}if(Q){a?.source==="room_activation"&&L(d.adapter,o,p,z,X,O,a.managedAgentPolicy,e.managedRoomExecutor);try{await nn(d.adapter,S.text,r,W,S.attachments,Z,a?{source:a.source}:void 0)}catch(i){e.processedReqIds.delete(n.id),e.sessions.delete(o),await K(e,d),e.client.sendRes({type:"res",id:h,ok:!1,error:i instanceof Error?i.message:String(i)});return}}$({level:"info",sessionId:o,wsEvent:"chat.send.start",metadata:{reqId:n.id,agentType:p,sessionMode:b,managerConfig:l??null,modelId:r,managerDefaultWorkerAgentType:x??null,managerDefaultWorkerModelId:P??null,reasoningEffort:W,...a?.source==="room_activation"?{managedRoomPromptSnapshot:a.managedRoomPromptSnapshot}:{}}});const J=(i,m)=>({...ye(oe()),sessionId:o,clientMessageId:R.id,messageId:R.id,deliveryState:i,bodyRevision:m,committedAt:new Date(R.ts).toISOString()}),ee=i=>{e.nativeFusion?.registerManagedSend({sessionId:o,agentType:p,sourceAgentType:we(p,r),canonicalMessageId:R.id,sourceSessionKey:d.agentSessionId??t??null,text:S.text,managedEchoPolicy:i?.deliveryMode==="external_owner_queue"&&!Pe?"import_following":"suppress_following",externalRunId:i?.deliveryMode==="external_owner_queue"?`codex-external-${i.queuedSubmissionId??R.id}`:null})},de=()=>{d.currentRunId||(d.currentRunId=`dispatch:${n.id}`,d.currentRunPhase="thinking",d.nextEventSeq=0)},le=()=>{if(!e.runJournal||d.journalRunId)return;const i=`run:${be()}`;e.runJournal.begin({daemonInstallationId:oe(),sessionId:o,runId:i,clientMessageId:R.id,agentType:p,sessionMode:b,workDir:v,agentSessionId:d.agentSessionId??t??null,modelId:r??null,reasoningEffort:W??null,nativeSessionVisibility:Y,providerRunId:null,source:a?.source==="room_activation"?Me==="managed_direct"?"managed_direct":"managed_room":E?"manager_worker":"personal",parentSessionId:E??null,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:a?.managedRoomPromptSnapshot.managedRoomPolicyVersion??null,managedRuntimePolicyHash:a?.managedRoomPromptSnapshot.runtimePermissionPolicyHash??(A?C("sha256").update(JSON.stringify({requestedPolicy:A.requestedPolicy,enforcement:A.enforcement})).digest("hex"):null)}),d.journalRunId=i},ue=()=>{if(ee(),ie)return{...ye(oe()),sessionId:o,clientMessageId:M.clientMessageId,messageId:M.messageId,deliveryState:M.deliveryState,bodyRevision:M.bodyRevision,committedAt:M.committedAt};try{Xe(e,{sessionId:o,agentType:p,sessionMode:b,managerConfig:l??null,workDir:Ee,agentSessionId:d.agentSessionId??t??null,modelId:r,managerDefaultWorkerAgentType:x??null,managerDefaultWorkerModelId:P??null});let i=0;for(const[m,g]of H.entries()){const T=se?D(o,g,"committed"):j(o,g,"committed");m===0&&(i=T)}return Ne(v,V??[]),w(e.client),J("committed",i)}catch(i){throw e.nativeFusion?.cancelManagedSend?.(R.id),i}},ce=i=>{d.journalRunId&&e.runJournal?.update(o,d.journalRunId,{deliveryMode:i?.deliveryMode==="external_owner_queue"?"external_owner_queue":"direct",externalRunId:i?.deliveryMode==="external_owner_queue"?`codex-external-${i.queuedSubmissionId??R.id}`:null})},ge=i=>{d.currentRunId||(d.nextEventSeq=0),ce(i),ee(i);const m=i?.deliveryMode==="external_owner_queue"?"queued":"executing";let g=0;for(const[T,B]of H.entries()){const q=D(o,B,m);T===0&&(g=q)}return w(e.client),J(m,g)},ne=async(i,m)=>{e.nativeFusion?.cancelManagedSend?.(R.id);const g=Se(p,i);if(console.error(`[chat.send] send failed reqId=${n.id} sessionId=${o} agentType=${c} workDir=${v} agentSessionId=${d.agentSessionId??t??""}: ${g}`),d.adapter.hasPendingAuthenticationRetry?.()===!0){m&&e.client.sendRes({type:"res",id:h,ok:!0,payload:J("queued",0)});return}e.sessions.delete(o),d.journalRunId&&(e.runJournal?.update(o,d.journalRunId,{phase:"terminal"}),e.runJournal?.remove(o,d.journalRunId),e.recoveryBarrier?.end(o),d.journalRunId=null);try{await K(e,d)}catch{}if(e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:o,message:g,runId:"",seq:0}}),!m){const B={id:`agent-error-${n.id}-${Date.now()}`,sessionId:o,role:"agent",ts:Date.now(),payload:g};j(o,B),w(e.client)}m&&(e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:h,ok:!1,error:g}))};if(Q){try{ue()}catch(g){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:h,ok:!1,error:`Durable message commit failed: ${g instanceof Error?g.message:String(g)}`});return}de(),le();let i;try{a?.source==="room_activation"&&(re(e,d,O),s());const g=S.attachments;i=await ve(d.adapter,S.text,r,W,g,Z,a?{source:a.source}:void 0),$({level:"info",sessionId:o,wsEvent:"chat.send.done",metadata:{reqId:n.id}})}catch(g){if(d.adapter.hasPendingAuthenticationRetry?.()){let q=0;for(const[te,ae]of H.entries()){const De=D(o,ae,"queued");te===0&&(q=De)}w(e.client),await ne(g,!1),e.client.sendRes({type:"res",id:h,ok:!0,payload:J("queued",q)});return}let T=0;for(const[q,te]of H.entries()){const ae=D(o,te,"failed");q===0&&(T=ae)}w(e.client);const B=Se(p,g);await ne(g,!1),e.client.sendRes({type:"res",id:h,ok:!0,payload:{...J("failed",T),failureMessage:B}});return}const m=ge(i??void 0);e.client.sendRes({type:"res",id:h,ok:!0,payload:{...m,...S.localized?{localizedAttachments:!0}:{},...i?.deliveryMode==="external_owner_queue"?{queued:!0,deliveryMode:i.deliveryMode,queuedSubmissionId:i.queuedSubmissionId}:{}}}),$({level:"info",sessionId:o,wsEvent:"chat.send.res",metadata:{reqId:n.id,ok:!0}});return}let pe;try{pe=ue()}catch(i){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:h,ok:!1,error:`Durable message commit failed: ${i instanceof Error?i.message:String(i)}`});return}de(),le(),a?.source==="room_activation"&&(L(d.adapter,o,p,z,X,O,a.managedAgentPolicy,e.managedRoomExecutor),re(e,d,O),s()),ge(),e.client.sendRes({type:"res",id:h,ok:!0,payload:{...pe,...S.localized?{localizedAttachments:!0}:{}}}),$({level:"info",sessionId:o,wsEvent:"chat.send.res",metadata:{reqId:n.id,ok:!0}});const Te=S.attachments;ve(d.adapter,S.text,r,W,Te,Z,a?{source:a.source}:void 0).then(i=>{if(i?.deliveryMode==="external_owner_queue"){ce(i),ee(i);for(const m of H)D(o,m,"queued");w(e.client)}$({level:"info",sessionId:o,wsEvent:"chat.send.done",metadata:{reqId:n.id}})}).catch(i=>{D(o,R,d.adapter.hasPendingAuthenticationRetry?.()?"queued":"failed"),w(e.client),ne(i,!1)})}async function xn(e,n){const{sessionId:a}=n.params,s=e.sessions.get(a);if(s){s.journalRunId&&e.runJournal?.markAborted(a,s.journalRunId),N(e,s);try{await s.adapter.stop()}catch{e.client.sendRes({type:"res",id:n.id,ok:!1,error:"agent_stop_failed"});return}tn(e,a),e.activityPublisher?.publish(a,null)}e.client.sendRes({type:"res",id:n.id,ok:!0})}export{xn as handleChatAbort,An as handleChatSend,vn as recoverJournaledRun,Xe as sendSessionUpdateEvent};
@@ -1,3 +1,3 @@
1
- import{SESSION_SYNC_PROTOCOL_VERSION as R,SESSION_SYNC_DATA_EPOCH as b,isAvailableAgentType as S}from"@shennian/wire";import g from"node:crypto";import{createAgent as E,getRegisteredAgents as x,unregisterAgent as M}from"../agents/adapter.js";import{resetCommandSpecCache as D}from"../agents/command-spec.js";import{loadConfig as _}from"../config/index.js";import{handleUpgradeStart as O,handleUpgradeStatus as F}from"../commands/upgrade.js";import{handleAgentsRefresh as $,handleModelsRefresh as U,refreshMachineAgents as Q}from"./handlers/agents.js";import{AcpDiscoveryWatcher as z}from"../agents/acp/watcher.js";import{handleAgentCapabilitiesList as B}from"./handlers/agent-capabilities.js";import{handleAgentConfigClear as N,handleAgentConfigGet as W,handleAgentConfigTest as J,handleAgentConfigUpsert as V}from"./handlers/agent-config.js";import{handleChatAbort as j,handleChatSend as f,recoverJournaledRun as H}from"./handlers/chat.js";import{handleSessionRefresh as L}from"./handlers/session-refresh.js";import{handleSessionToolDetail as G}from"./handlers/tool-detail.js";import{handleSessionTitleSet as Y}from"./handlers/title.js";import{handleSessionMessageStatus as K}from"./handlers/message-status.js";import{handleAgentWorkspaceValidation as X}from"./handlers/agent-workspace-validation.js";import{handleVoiceSessionBodyWindow as Z}from"./handlers/voice-window.js";import{handleRemoteAccessPause as q}from"./handlers/remote-access.js";import{cleanupPendingTransfers as ee,handleFsLs as se,handleFsRead as te,handleFsRename as ae,handleFsWrite as ne,handleFsTransferAbort as ie,handleFsTransferChunk as re,handleFsTransferFinish as oe,handleFsTransferStart as ce,handleFsExportMarkdownPdf as de,handleFsExportMarkdownPdfSetup as le,handleFsArchiveZip as he}from"./handlers/fs.js";import{handleRegionProbe as ue,handleRegionSwitch as pe,handleUpgradeSetPolicy as me}from"./handlers/control.js";import{ManagerRuntimeService as ge,setManagerRuntimeService as I}from"../manager/runtime.js";import{ChatQueueManager as fe,TerminalQueuedChatDispatchError as ye}from"./queue.js";import{createAuthorizedFsRoot as ve,createAuthorizedFsRootAsync as we,resolveAuthorizedPath as Re,resolveAuthorizedPathAsync as be,resolveSessionWorkDir as Se}from"../fs/boundary.js";import"../agents/claude.js";import"../agents/codex.js";import"../agents/workbuddy.js";import"../agents/gemini.js";import"../agents/cursor.js";import"../agents/opencode.js";import"../agents/pi-coding-agent.js";import"../agents/manager.js";import{registerCustomAgent as Ie}from"../agents/custom.js";import{handleManagedRoomBind as ke,handleManagedRoomRestart as Ae}from"./handlers/room-managed.js";import{getDaemonInstallationId as y,listSessionRecords as v,purgeCanonicalSession as Te,purgeSessionRecord as Ce,recordSession as k,upsertMessage as A}from"./store.js";import{requestSessionIndexPublish as T}from"./index-publisher.js";import{reportUpgradeAggregateMetric as Pe}from"../log-reporter.js";import{PersonalHistorySubscriptions as Ee}from"./history-subscriptions.js";import{sweepAttachmentTransfers as xe}from"./attachment-transfer-store.js";import{NativeSourceDeletionStore as Me}from"../native-fusion/native-source-deletions.js";import{purgeToolDetails as De}from"./tool-detail-store.js";import{readDeletionSyncRevision as _e,writeDeletionSyncRevision as C}from"./deletion-sync-state.js";import{assertDeletionSyncRunnable as Oe,clearDeletionSyncFailure as Fe,DeletionSyncRetryWaitError as $e,DeletionSyncTerminalError as Ue,recordDeletionSyncFailure as w}from"./deletion-sync-state.js";import{NativeSessionCleanupOutbox as Qe}from"./native-cleanup-outbox.js";import{assertPersonalSyncIdentity as u}from"../personal-sync/epoch.js";import{ActiveRunJournal as ze}from"./run-recovery/journal.js";import{RunRecoveryBarrier as Be}from"./run-recovery/barrier.js";import{RunRecoveryCoordinator as Ne}from"./run-recovery/coordinator.js";const We=50;function Je(l,e){if(!l||!Number.isSafeInteger(l.baselineRevision)||l.baselineRevision<e||!Number.isSafeInteger(l.latestRevision)||l.latestRevision<l.baselineRevision||!Array.isArray(l.deletions))throw new Error("invalid deletion sync response");let t=l.baselineRevision;for(const s of l.deletions){if(!s?.sessionId||!Number.isSafeInteger(s.deletionRevision)||s.deletionRevision!==t+1||!Array.isArray(s.sources)||Number.isNaN(Date.parse(s.deletedAt))||s.nativeCleanupPolicy!==void 0&&s.nativeCleanupPolicy!=="none"&&s.nativeCleanupPolicy!=="auto")throw new Error("invalid session deletion command");for(const a of s.sources)if(!a?.sourceSessionKey||!S(a.agentType))throw new Error("invalid native source deletion command");t=s.deletionRevision}if(t!==l.latestRevision)throw new Error("invalid deletion sync revision range")}import{resolveSessionWorkDir as Bs}from"../fs/boundary.js";class Us{client;nativeFusion;cliVersion;upgradePolicyController;managedRoomBinder;remoteAccessPauseController;managedRoomContextRevoker;managedRoomExecutor;sessions=new Map;processedReqIds=new Set;runTextAcc=new Map;pendingTransfers=new Map;managerRuntime;chatQueue;historySubscriptions;nativeCleanupOutbox;activityProbeTimer=null;attachmentCleanupTimer=null;activeRequests=0;acpDiscoveryWatcher;runJournal;recoveryBarrier=new Be;recoveryCoordinator;recoveryPass=null;prepareManagedRecovery;constructor(e,t=null,s,a=null,i=null,d=null,r=null,o=null,h=null){this.client=e,this.nativeFusion=t,this.cliVersion=s,this.upgradePolicyController=a,this.managedRoomBinder=i,this.remoteAccessPauseController=d,this.managedRoomContextRevoker=r,this.managedRoomExecutor=o,this.runJournal=h??new ze(y());for(const n of this.runJournal.loadPendingReconciliation().records)this.recoveryBarrier.begin(n.sessionId);this.recoveryCoordinator=new Ne({journal:this.runJournal,barrier:this.recoveryBarrier,recover:(n,c)=>H(this.getRuntime(),n,c),commitTerminal:(n,c)=>this.commitRecoveryTerminal(n,c),isCanonicalTerminal:n=>{const c=v().find(m=>m.sessionId===n.sessionId),p=`terminal:${g.createHash("sha256").update(`${n.runId}\0`).digest("hex")}`;return c?.lastAttentionId===p},onOutcome:n=>Pe({kind:"recovery",adapterType:n.adapterType,result:n.result,stage:n.result==="continued"?"continue":n.result==="reattached"?"reattach":n.result==="resumed"?"resume":n.result==="terminal_reconciled"?"inspect":"terminal",durationMs:n.durationMs,retries:Math.max(0,n.attempts-1),...n.reason?{errorCode:n.reason}:{}})}),this.managerRuntime=new ge({getRuntime:()=>this.getRuntime(),dispatchReq:n=>this.handleReq(n,{propagateErrors:!0})}),this.chatQueue=new fe({getRuntime:()=>this.getRuntime(),dispatchReq:n=>this.handleReq(n),dispatchManagedReq:(n,c)=>this.dispatchManagedChat(n,c),dispatchQueuedReq:(n,c)=>this.dispatchQueuedChat(n,c)}),this.historySubscriptions=new Ee(this.client),this.nativeCleanupOutbox=new Qe({logger:console.info}),this.acpDiscoveryWatcher=new z(async()=>{D(),await Q(this.getRuntime())}),this.nativeFusion?.setExternalTerminalHandler?.(n=>{this.handleExternalTerminal(n)}),this.nativeCleanupOutbox.start(),I(this.managerRuntime),this.managerRuntime.start(),this.reloadCustomAgents();try{this.acpDiscoveryWatcher.start()}catch(n){console.warn(`[acp.discovery] watcher unavailable: ${n instanceof Error?n.message:String(n)}`)}this.activityProbeTimer=setInterval(()=>{this.publishManagedActivitySnapshots().catch(n=>{console.error("[session.activity] managed probe failed",n)})},15e3),this.activityProbeTimer.unref?.(),this.attachmentCleanupTimer=setInterval(()=>{const n=new Set([...v().map(c=>c.workDir),...Array.from(this.pendingTransfers.values()).map(c=>c.rootPath)]);for(const c of n)try{xe(this.resolvePath(c))}catch(p){console.error("[attachment.cleanup] failed",p instanceof Error?p.message:String(p))}},6e4),this.attachmentCleanupTimer.unref?.()}getRuntime(){return{client:this.client,pendingTransfers:this.pendingTransfers,processedReqIds:this.processedReqIds,reloadCustomAgents:()=>this.reloadCustomAgents(),resolvePath:e=>this.resolvePath(e),resolveAuthorizedPath:(e,t)=>this.resolveAuthorizedPath(e,t),resolveAuthorizedPathAsync:(e,t)=>this.resolveAuthorizedPathAsync(e,t),runTextAcc:this.runTextAcc,sessions:this.sessions,evictIdleSessions:()=>this.evictIdleSessions(),nativeFusion:this.nativeFusion,managerRuntime:this.managerRuntime,chatQueue:this.chatQueue,activityPublisher:{publish:(e,t)=>this.publishSessionActivity(e,t)},upgradePolicyController:this.upgradePolicyController,revokeManagedRoomContextToken:this.managedRoomContextRevoker,managedRoomExecutor:this.managedRoomExecutor,runJournal:this.runJournal,recoveryBarrier:this.recoveryBarrier,prepareManagedRecovery:this.prepareManagedRecovery}}async resumePendingAcpAuthentication(e){const t=[...this.sessions.values()].find(s=>s.agentType===e&&s.adapter.hasPendingAuthenticationRetry?.());return t?.adapter.resumeAfterExternalAuthentication?(await t.adapter.resumeAfterExternalAuthentication(),!0):!1}getUpgradeIdleState(){const e=[];return[...this.sessions.values()].some(t=>t.currentRunId!==null)&&e.push("active-agent-turn"),this.pendingTransfers.size>0&&e.push("file-transfer"),this.activeRequests>0&&e.push("active-daemon-request"),{idle:e.length===0,reasons:e}}prepareForUpgrade(e){this.recoveryBarrier.freezeDispatch();const t=this.runJournal.markPlannedUpgrade(e);return this.runJournal.sync(),{activeRunCount:t}}cancelUpgradePreparation(){this.recoveryBarrier.unfreezeDispatch()}async recoverInterruptedRuns(){this.recoveryPass??=this.recoveryCoordinator.recoverAll(),await this.recoveryPass}getPendingRecoveryCount(){return this.runJournal.loadPendingReconciliation().records.length}async commitRecoveryTerminal(e,t){const s=this.sessions.get(e.sessionId);s&&(s.currentRunId=null,s.currentRunPhase=null,s.journalRunId=null,s.heartbeatTimer&&(clearInterval(s.heartbeatTimer),s.heartbeatTimer=null)),this.runTextAcc.delete(`${e.sessionId}:${e.providerRunId??e.runId}`),this.publishSessionActivity(e.sessionId,null);const a=t.result==="interrupted_by_upgrade"?`\u4EFB\u52A1\u56E0\u795E\u5FF5\u5347\u7EA7\u800C\u4E2D\u65AD\uFF08${t.reason}\uFF09\u3002\u539F\u4F1A\u8BDD\u5DF2\u4FDD\u7559\uFF0C\u53EF\u7EE7\u7EED\u53D1\u9001\u65B0\u6D88\u606F\u3002
1
+ import{SESSION_SYNC_PROTOCOL_VERSION as w,SESSION_SYNC_DATA_EPOCH as b,isAvailableAgentType as S}from"@shennian/wire";import g from"node:crypto";import{createAgent as E,getRegisteredAgents as x,unregisterAgent as M}from"../agents/adapter.js";import{resetCommandSpecCache as D}from"../agents/command-spec.js";import{loadConfig as _}from"../config/index.js";import{handleUpgradeStart as O,handleUpgradeStatus as F}from"../commands/upgrade.js";import{handleAgentsRefresh as $,handleModelsRefresh as U,refreshMachineAgents as Q}from"./handlers/agents.js";import{AcpDiscoveryWatcher as z}from"../agents/acp/watcher.js";import{handleAgentCapabilitiesList as B}from"./handlers/agent-capabilities.js";import{handleAgentConfigClear as N,handleAgentConfigGet as W,handleAgentConfigTest as J,handleAgentConfigUpsert as j}from"./handlers/agent-config.js";import{handleChatAbort as V,handleChatSend as f,recoverJournaledRun as H}from"./handlers/chat.js";import{handleSessionRefresh as L}from"./handlers/session-refresh.js";import{handleSessionToolDetail as G}from"./handlers/tool-detail.js";import{handleSessionTitleSet as Y}from"./handlers/title.js";import{handleSessionMessageStatus as K}from"./handlers/message-status.js";import{handleAgentWorkspaceValidation as X}from"./handlers/agent-workspace-validation.js";import{handleVoiceSessionBodyWindow as Z}from"./handlers/voice-window.js";import{handleRemoteAccessPause as q}from"./handlers/remote-access.js";import{cleanupPendingTransfers as ee,handleFsLs as se,handleFsRead as te,handleFsRename as ae,handleFsWrite as ne,handleFsTransferAbort as ie,handleFsTransferChunk as re,handleFsTransferFinish as oe,handleFsTransferStart as ce,handleFsExportMarkdownPdf as de,handleFsExportMarkdownPdfSetup as le,handleFsArchiveZip as he}from"./handlers/fs.js";import{handleRegionProbe as ue,handleRegionSwitch as pe,handleUpgradeSetPolicy as me}from"./handlers/control.js";import{ManagerRuntimeService as ge,setManagerRuntimeService as I}from"../manager/runtime.js";import{ChatQueueManager as fe,TerminalQueuedChatDispatchError as ye}from"./queue.js";import{createAuthorizedFsRoot as ve,createAuthorizedFsRootAsync as Re,resolveAuthorizedPath as we,resolveAuthorizedPathAsync as be,resolveSessionWorkDir as Se}from"../fs/boundary.js";import"../agents/claude.js";import"../agents/codex.js";import"../agents/workbuddy.js";import"../agents/gemini.js";import"../agents/cursor.js";import"../agents/opencode.js";import"../agents/pi-coding-agent.js";import"../agents/manager.js";import{registerCustomAgent as Ie}from"../agents/custom.js";import{handleManagedRoomBind as ke,handleManagedRoomRestart as Ae}from"./handlers/room-managed.js";import{getDaemonInstallationId as y,listSessionRecords as v,purgeCanonicalSession as Te,purgeSessionRecord as Ce,recordSession as k,upsertMessage as A}from"./store.js";import{requestSessionIndexPublish as T}from"./index-publisher.js";import{reportUpgradeAggregateMetric as Pe}from"../log-reporter.js";import{PersonalHistorySubscriptions as Ee}from"./history-subscriptions.js";import{sweepAttachmentTransfers as xe}from"./attachment-transfer-store.js";import{NativeSourceDeletionStore as Me}from"../native-fusion/native-source-deletions.js";import{purgeToolDetails as De}from"./tool-detail-store.js";import{readDeletionSyncRevision as _e,writeDeletionSyncRevision as C}from"./deletion-sync-state.js";import{assertDeletionSyncRunnable as Oe,clearDeletionSyncFailure as Fe,DeletionSyncRetryWaitError as $e,DeletionSyncTerminalError as Ue,recordDeletionSyncFailure as R}from"./deletion-sync-state.js";import{NativeSessionCleanupOutbox as Qe}from"./native-cleanup-outbox.js";import{assertPersonalSyncIdentity as u}from"../personal-sync/epoch.js";import{ActiveRunJournal as ze}from"./run-recovery/journal.js";import{RunRecoveryBarrier as Be}from"./run-recovery/barrier.js";import{RunRecoveryCoordinator as Ne}from"./run-recovery/coordinator.js";const We=50;function Je(l,e){if(!l||!Number.isSafeInteger(l.baselineRevision)||l.baselineRevision<e||!Number.isSafeInteger(l.latestRevision)||l.latestRevision<l.baselineRevision||!Array.isArray(l.deletions))throw new Error("invalid deletion sync response");let t=l.baselineRevision;for(const s of l.deletions){if(!s?.sessionId||!Number.isSafeInteger(s.deletionRevision)||s.deletionRevision!==t+1||!Array.isArray(s.sources)||Number.isNaN(Date.parse(s.deletedAt))||s.nativeCleanupPolicy!==void 0&&s.nativeCleanupPolicy!=="none"&&s.nativeCleanupPolicy!=="auto")throw new Error("invalid session deletion command");for(const a of s.sources)if(!a?.sourceSessionKey||!S(a.agentType))throw new Error("invalid native source deletion command");t=s.deletionRevision}if(t!==l.latestRevision)throw new Error("invalid deletion sync revision range")}import{resolveSessionWorkDir as Bs}from"../fs/boundary.js";class Us{client;nativeFusion;cliVersion;upgradePolicyController;managedRoomBinder;remoteAccessPauseController;managedRoomContextRevoker;managedRoomExecutor;sessions=new Map;processedReqIds=new Set;runTextAcc=new Map;pendingTransfers=new Map;managerRuntime;chatQueue;historySubscriptions;nativeCleanupOutbox;activityProbeTimer=null;attachmentCleanupTimer=null;activeRequests=0;acpDiscoveryWatcher;runJournal;recoveryBarrier=new Be;recoveryCoordinator;recoveryPass=null;prepareManagedRecovery;constructor(e,t=null,s,a=null,i=null,d=null,r=null,o=null,h=null){this.client=e,this.nativeFusion=t,this.cliVersion=s,this.upgradePolicyController=a,this.managedRoomBinder=i,this.remoteAccessPauseController=d,this.managedRoomContextRevoker=r,this.managedRoomExecutor=o,this.runJournal=h??new ze(y());for(const n of this.runJournal.loadPendingReconciliation().records)this.recoveryBarrier.begin(n.sessionId);this.recoveryCoordinator=new Ne({journal:this.runJournal,barrier:this.recoveryBarrier,recover:(n,c)=>H(this.getRuntime(),n,c),commitTerminal:(n,c)=>this.commitRecoveryTerminal(n,c),isCanonicalTerminal:n=>{const c=v().find(m=>m.sessionId===n.sessionId),p=`terminal:${g.createHash("sha256").update(`${n.runId}\0`).digest("hex")}`;return c?.lastAttentionId===p},onOutcome:n=>Pe({kind:"recovery",adapterType:n.adapterType,result:n.result,stage:n.result==="continued"?"continue":n.result==="reattached"?"reattach":n.result==="resumed"?"resume":n.result==="terminal_reconciled"?"inspect":"terminal",durationMs:n.durationMs,retries:Math.max(0,n.attempts-1),...n.reason?{errorCode:n.reason}:{}})}),this.managerRuntime=new ge({getRuntime:()=>this.getRuntime(),dispatchReq:n=>this.handleReq(n,{propagateErrors:!0})}),this.chatQueue=new fe({getRuntime:()=>this.getRuntime(),dispatchReq:n=>this.handleReq(n),dispatchManagedReq:(n,c)=>this.dispatchManagedChat(n,c),dispatchQueuedReq:(n,c)=>this.dispatchQueuedChat(n,c)}),this.historySubscriptions=new Ee(this.client),this.nativeCleanupOutbox=new Qe({logger:console.info}),this.acpDiscoveryWatcher=new z(async()=>{D(),await Q(this.getRuntime())}),this.nativeFusion?.setExternalTerminalHandler?.(n=>{this.handleExternalTerminal(n)}),this.nativeCleanupOutbox.start(),I(this.managerRuntime),this.managerRuntime.start(),this.reloadCustomAgents();try{this.acpDiscoveryWatcher.start()}catch(n){console.warn(`[acp.discovery] watcher unavailable: ${n instanceof Error?n.message:String(n)}`)}this.activityProbeTimer=setInterval(()=>{this.publishManagedActivitySnapshots().catch(n=>{console.error("[session.activity] managed probe failed",n)})},15e3),this.activityProbeTimer.unref?.(),this.attachmentCleanupTimer=setInterval(()=>{const n=new Set([...v().map(c=>c.workDir),...Array.from(this.pendingTransfers.values()).map(c=>c.rootPath)]);for(const c of n)try{xe(this.resolvePath(c))}catch(p){console.error("[attachment.cleanup] failed",p instanceof Error?p.message:String(p))}},6e4),this.attachmentCleanupTimer.unref?.()}getRuntime(){return{client:this.client,pendingTransfers:this.pendingTransfers,processedReqIds:this.processedReqIds,reloadCustomAgents:()=>this.reloadCustomAgents(),resolvePath:e=>this.resolvePath(e),resolveAuthorizedPath:(e,t)=>this.resolveAuthorizedPath(e,t),resolveAuthorizedPathAsync:(e,t)=>this.resolveAuthorizedPathAsync(e,t),runTextAcc:this.runTextAcc,sessions:this.sessions,evictIdleSessions:()=>this.evictIdleSessions(),nativeFusion:this.nativeFusion,managerRuntime:this.managerRuntime,chatQueue:this.chatQueue,activityPublisher:{publish:(e,t)=>this.publishSessionActivity(e,t)},upgradePolicyController:this.upgradePolicyController,revokeManagedRoomContextToken:this.managedRoomContextRevoker,managedRoomExecutor:this.managedRoomExecutor,runJournal:this.runJournal,recoveryBarrier:this.recoveryBarrier,prepareManagedRecovery:this.prepareManagedRecovery}}async resumePendingAcpAuthentication(e){const t=[...this.sessions.values()].find(s=>s.agentType===e&&s.adapter.hasPendingAuthenticationRetry?.());return t?.adapter.resumeAfterExternalAuthentication?(await t.adapter.resumeAfterExternalAuthentication(),!0):!1}getUpgradeIdleState(){const e=[];return[...this.sessions.values()].some(t=>t.currentRunId!==null)&&e.push("active-agent-turn"),this.pendingTransfers.size>0&&e.push("file-transfer"),this.activeRequests>0&&e.push("active-daemon-request"),{idle:e.length===0,reasons:e}}prepareForUpgrade(e){this.recoveryBarrier.freezeDispatch();const t=this.runJournal.markPlannedUpgrade(e);return this.runJournal.sync(),{activeRunCount:t}}cancelUpgradePreparation(){this.recoveryBarrier.unfreezeDispatch()}async recoverInterruptedRuns(){this.recoveryPass??=this.recoveryCoordinator.recoverAll(),await this.recoveryPass}getPendingRecoveryCount(){return this.runJournal.loadPendingReconciliation().records.length}async commitRecoveryTerminal(e,t){const s=this.sessions.get(e.sessionId);s&&(s.currentRunId=null,s.currentRunPhase=null,s.journalRunId=null,s.heartbeatTimer&&(clearInterval(s.heartbeatTimer),s.heartbeatTimer=null)),this.runTextAcc.delete(`${e.sessionId}:${e.providerRunId??e.runId}`),this.publishSessionActivity(e.sessionId,null);const a=t.result==="interrupted_by_upgrade"?`\u4EFB\u52A1\u56E0\u795E\u5FF5\u5347\u7EA7\u800C\u4E2D\u65AD\uFF08${t.reason}\uFF09\u3002\u539F\u4F1A\u8BDD\u5DF2\u4FDD\u7559\uFF0C\u53EF\u7EE7\u7EED\u53D1\u9001\u65B0\u6D88\u606F\u3002
2
2
  Task interrupted by the Shennian upgrade (${t.reason}). The session is preserved and ready for a new message.`:`\u795E\u5FF5\u5347\u7EA7\u540E\u672A\u80FD\u6062\u590D\u4EFB\u52A1\uFF08${t.reason}\uFF09\u3002\u539F\u4F1A\u8BDD\u5DF2\u4FDD\u7559\uFF0C\u53EF\u91CD\u8BD5\u6216\u7EE7\u7EED\u53D1\u9001\u65B0\u6D88\u606F\u3002
3
- The task could not be recovered after the Shennian upgrade (${t.reason}). The session is preserved; retry or send a new message.`;A(e.sessionId,{id:`upgrade-recovery-terminal-${e.runId}`,sessionId:e.sessionId,role:"agent",ts:Date.now(),payload:JSON.stringify({v:1,type:"error",content:a,error:!0,recoveryState:t.result,recoveryReason:t.reason})}),k({sessionId:e.sessionId,agentType:e.agentType,sessionMode:e.sessionMode,workDir:e.workDir,agentSessionId:e.agentSessionId,modelId:e.modelId,status:"failed",attentionId:`upgrade-recovery:${e.runId}`,lastActivityAt:new Date().toISOString()}),T(this.client);const i=e.lastObservedEventSeq+1;this.client.sendAgentEvent({type:"event",event:"agent",payload:t.result==="interrupted_by_upgrade"?{state:"interrupted_by_upgrade",sessionId:e.sessionId,runId:e.providerRunId??e.runId,seq:i,reason:t.reason}:{state:"recovery_failed",sessionId:e.sessionId,runId:e.providerRunId??e.runId,seq:i,reason:t.reason,...t.message?{message:t.message}:{}},seq:i,id:`agent-recovery-terminal-${e.runId}`}),this.chatQueue.noteTerminal(e.sessionId)}setManagedRoomTurnPreparer(e){this.chatQueue.setManagedRoomTurnPreparer(e)}setManagedAgentDirectTurnPreparer(e){this.chatQueue.setManagedAgentDirectTurnPreparer(e)}setManagedRecoveryPreparer(e){this.prepareManagedRecovery=e}async enqueueManagedRoomBatch(e){const t=e.map(s=>{const a=v().find(i=>i.sessionId===s.params.sessionId);return{context:s.context,params:{...s.params,modelId:a?.modelId??s.params.modelId,sessionMode:a?.sessionMode??s.params.sessionMode,managerConfig:a?.managerConfig??s.params.managerConfig,managerDefaultWorkerAgentType:a?.managerDefaultWorkerAgentType??s.params.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:a?.managerDefaultWorkerModelId??s.params.managerDefaultWorkerModelId}}});await this.chatQueue.enqueueManagedRoomBatch(t)}discardPendingManagedRoomMessages(e){return this.chatQueue.discardPendingManagedRoomMessages(e)}managedChatContext(e){return{source:"room_activation",managedAgentPolicy:e.managedAgentPolicy,managedRoomPromptSnapshot:e.managedRoomPromptSnapshot}}async dispatchManagedChat(e,t){await f(this.getRuntime(),e,this.managedChatContext(t))}async dispatchQueuedChat(e,t){const s={value:null},a=new Proxy(this.client,{get:(i,d)=>{if(d==="sendRes")return o=>{s.value=o};const r=Reflect.get(i,d,i);return typeof r=="function"?r.bind(i):r}});if(await f({...this.getRuntime(),client:a},e,t?this.managedChatContext(t):void 0),s.value?.payload?.deliveryState==="failed")throw new ye(s.value.payload.failureMessage??s.value.error??"queued_chat_dispatch_failed");if(!s.value?.ok)throw new Error(s.value?.error??"queued_chat_dispatch_failed")}async synchronizeSessionDeletions(){Oe();try{await this.synchronizeSessionDeletionsOnce(),Fe()}catch(e){if(e instanceof $e||e instanceof Ue)throw e;const t=e instanceof Error?e.message:String(e),s=/offline|not connected|disconnect|timed out|timeout|temporar|unavailable|ECONN|ENOTFOUND|EIO|EBUSY/i.test(t)?"transport_unavailable":/invalid (?:deletion sync|session deletion)|deletion revision gap/i.test(t)?"invalid_deletion_sync_response":"deletion_sync_failed";throw console.warn("[session.deletions] retrying failed synchronization",{code:s}),w({code:s,retryable:!0}),e}}async synchronizeSessionDeletionsOnce(){const e=y(),t=_e(),s=await this.client.sendReq({type:"req",id:`session-deletions-sync-${g.randomUUID()}`,method:"session.deletions.sync",params:{protocolVersion:R,dataEpoch:b,daemonInstallationId:e,afterRevision:t}});if(!s.ok){const r=s.rejection;throw r?(w({code:r.code,retryable:r.retryable&&r.requiredAction==="retry"}),new Error(r.code)):(w({code:"unstructured_rejection",retryable:!0}),new Error(s.error??"unstructured_rejection"))}const a=s.payload;u(a),Je(a,t);let i=a.baselineRevision;i>t&&C(i);for(const r of a.deletions)r.deletionRevision<=i||(await this.applySessionDeletion(r),i=r.deletionRevision,C(i));if(i!==a.latestRevision)throw new Error(`deletion revision gap: applied ${i}, latest ${a.latestRevision}`);if(i===0)return;const d=await this.client.sendReq({type:"req",id:`session-deletions-ack-${g.randomUUID()}`,method:"session.deletions.ack",params:{protocolVersion:R,dataEpoch:b,daemonInstallationId:e,deletionRevision:i}});if(!d.ok)throw new Error(d.error??"session deletion acknowledgement failed")}async applySessionDeletion(e){const t=new Me({daemonInstallationId:y()});t.recordSession(e);for(const a of e.sources){const i=t.find(a.agentType,a.sourceSessionKey);i&&i.sessionId!==e.sessionId||t.record({...e,...a})}this.runJournal.markSessionAborted(e.sessionId),this.recoveryBarrier.end(e.sessionId);const s=this.sessions.get(e.sessionId);if(s){this.revokeManagedRoomToken(s);try{await s.adapter.stop()}catch{}this.sessions.delete(e.sessionId)}this.historySubscriptions.closeSession(e.sessionId),this.nativeFusion?.handleSessionDeleted(e.sessionId),this.chatQueue.purgeSession(e.sessionId),De(e.sessionId),Te(e.sessionId),Ce(e.sessionId),this.runTextAcc.delete(e.sessionId),this.publishSessionActivity(e.sessionId,null),this.nativeCleanupOutbox.enqueue(e),this.nativeCleanupOutbox.kick()}publishSessionActivity(e,t){this.client.sendEvent({type:"event",event:"session.activity",payload:{sessionId:e,activity:t}})}async handleExternalTerminal(e){const t=this.sessions.get(e);t&&(this.sessions.delete(e),this.revokeManagedRoomToken(t),t.heartbeatTimer&&clearInterval(t.heartbeatTimer),t.adapter.removeAllListeners(),await t.adapter.stop().catch(()=>{})),this.chatQueue.noteTerminal(e)}async publishManagedActivitySnapshots(){for(const[e,t]of this.sessions.entries()){if(t.heartbeatTimer||!t.currentRunId||!t.adapter.getStatus)continue;const s=await t.adapter.getStatus().catch(()=>null);if(!s?.active||!s.runPhase)continue;const a=new Date().toISOString();this.publishSessionActivity(e,{sessionId:e,runId:s.runId||t.currentRunId,runPhase:s.runPhase,startedAt:new Date(t.lastActiveAt).toISOString(),updatedAt:a,canStop:s.canStop??!0});const i=t.heartbeatSeq++;this.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:e,runId:s.runId||t.currentRunId,seq:i,runPhase:s.runPhase,canStop:s.canStop??!0},seq:i,id:`agent-status-${s.runId||t.currentRunId}-${Date.now()}`})}}reloadCustomAgents(){for(const t of x())t.startsWith("custom:")&&M(t);const e=_();for(const[t,s]of Object.entries(e.customAgents??{}))Ie(t,s)}async handleAgentPermissionResponse(e){const t=typeof e.params.sessionId=="string"?e.params.sessionId:"",s=typeof e.params.permissionId=="string"?e.params.permissionId:"",a=typeof e.params.optionId=="string"?e.params.optionId:"",i=this.sessions.get(t);if(!t||!s||!a||!i?.adapter.respondToPermission){this.client.sendRes({type:"res",id:e.id,ok:!1,error:"permission_request_unavailable"});return}try{await i.adapter.respondToPermission(s,a),this.client.sendRes({type:"res",id:e.id,ok:!0,payload:{accepted:!0}})}catch(d){this.client.sendRes({type:"res",id:e.id,ok:!1,error:d instanceof Error?d.message:"permission_response_failed"})}}async handleAcpAgentRequest(e){const t=typeof e.params.agentType=="string"?e.params.agentType:"";if(!t.startsWith("acp:")||!S(t)){this.client.sendRes({type:"res",id:e.id,ok:!1,error:"invalid_acp_agent"});return}const s=e.method==="agent.auth.start"?[...this.sessions.values()].find(r=>r.agentType===t&&r.adapter.hasPendingAuthenticationRetry?.())?.adapter:void 0;if(String(e.method)==="agent.session-options.get"){const r=typeof e.params.sessionId=="string"?e.params.sessionId:"",o=this.sessions.get(r);if(!o||o.agentType!==t||!o.adapter.getSessionOptions){this.client.sendRes({type:"res",id:e.id,ok:!1,error:"acp_session_not_active"});return}this.client.sendRes({type:"res",id:e.id,ok:!0,payload:o.adapter.getSessionOptions()});return}const a=s??E(t);if(!a){this.client.sendRes({type:"res",id:e.id,ok:!1,error:"acp_agent_not_installed"});return}const i=typeof e.params.cwd=="string"&&e.params.cwd?e.params.cwd:process.cwd(),d=!s;try{d&&await a.start(`acp-control-${g.randomUUID()}`,i);let r={ok:!0};if(e.method==="agent.external-sessions.list")r={sessions:await a.listExternalSessions?.({cwd:typeof e.params.cwd=="string"?e.params.cwd:void 0})??[]};else if(e.method==="agent.external-sessions.import"){if(!a.loadExternalSession)throw new Error("ACP session history import is unsupported");const o=String(e.params.agentSessionId??""),h=String(e.params.sessionId??"");if(!o||!h)throw new Error("ACP and Shennian session ids are required");const n=await a.loadExternalSession(o),c=Date.now();for(const[p,m]of n.entries()){const P=`acp-import-${g.createHash("sha256").update(`${t}\0${o}\0${m.sourceId}`).digest("hex")}`;A(h,{id:P,sessionId:h,role:m.role,ts:c+p,payload:m.payload})}k({sessionId:h,agentType:t,workDir:i,agentSessionId:o,title:typeof e.params.title=="string"?e.params.title.slice(0,120):void 0,titleSource:typeof e.params.title=="string"?"agent_native":void 0,status:"active"}),T(this.client),this.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:h,agentType:t,agentSessionId:o,workDir:i,status:"active"}}}),r={sessionId:h,agentSessionId:o,importedMessageCount:n.length}}else if(e.method==="agent.auth.start"){if(!a.authenticate)throw new Error("ACP authentication is unsupported");await a.authenticate(String(e.params.methodId??""))}else if(e.method==="agent.auth.logout"){if(!a.logout)throw new Error("ACP logout is unsupported");await a.logout()}else{const o=String(e.params.agentSessionId??"");if(!o)throw new Error("ACP session id is required");if(e.method==="agent.external-sessions.close"){if(!a.closeExternalSession)throw new Error("ACP session close is unsupported");await a.closeExternalSession(o)}else{if(!a.deleteExternalSession)throw new Error("ACP session delete is unsupported");await a.deleteExternalSession(o)}}this.client.sendRes({type:"res",id:e.id,ok:!0,payload:r})}catch(r){this.client.sendRes({type:"res",id:e.id,ok:!1,error:r instanceof Error?r.message:"acp_request_failed"})}finally{d&&await a.stop().catch(()=>{})}}async handleReq(e,t={}){const s=this.getRuntime();this.activeRequests++;try{switch(e.method){case"chat.send":u(e.params),this.recoveryBarrier.isDispatchFrozen()||this.recoveryBarrier.isRecovering(String(e.params.sessionId??""))?await this.chatQueue.handleEnqueue({...e,method:"chat.enqueue"}):await f(s,e);break;case"chat.enqueue":u(e.params),await this.chatQueue.handleEnqueue(e);break;case"chat.queue.get":u(e.params),await this.chatQueue.handleGet(e);break;case"chat.queue.edit":u(e.params),await this.chatQueue.handleEdit(e);break;case"chat.queue.delete":u(e.params),await this.chatQueue.handleDelete(e);break;case"chat.abort":u(e.params),await j(s,e);break;case"session.refresh":u(e.params),await L(s,e);break;case"session.history.open":this.historySubscriptions.handleOpen(e);break;case"session.history.sync":this.historySubscriptions.handleSync(e);break;case"session.history.page":this.historySubscriptions.handlePage(e);break;case"session.history.close":this.historySubscriptions.handleClose(e);break;case"session.body.replica.subscribe":this.historySubscriptions.handleReplicaSubscribe(e);break;case"session.body.replica.unsubscribe":this.historySubscriptions.handleReplicaUnsubscribe(e);break;case"session.history":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"upgrade_required"});break;case"session.message.status":K(s,e);break;case"session.tool.detail":await G(s,e);break;case"voice.session.body.window":Z(s,e);break;case"session.title.set":await Y(s,e);break;case"room.agent.bind-managed":await ke(s,this.managedRoomBinder,e);break;case"room.agent.restart-managed":await Ae(s,this.managedRoomBinder,e);break;case"fs.ls":await se(s,e);break;case"fs.read":await te(s,e);break;case"fs.write":await ne(s,e);break;case"fs.export.markdown-pdf":await de(s,e);break;case"fs.export.markdown-pdf.setup":await le(s,e);break;case"fs.archive.zip":await he(s,e);break;case"fs.rename":await ae(s,e);break;case"fs.transfer.start":await ce(s,e);break;case"fs.transfer.chunk":await re(s,e);break;case"fs.transfer.finish":await oe(s,e);break;case"fs.transfer.abort":await ie(s,e);break;case"region.probe":await ue(s,e);break;case"region.switch":await pe(s,e);break;case"upgrade.start":await O(this.client,e.id,e.params.version,{currentVersion:this.cliVersion,confirmedMajor:e.params.confirmedMajor===!0,prepareForUpgrade:a=>this.prepareForUpgrade(a),cancelUpgradePreparation:()=>this.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()}});break;case"upgrade.status":await F(this.client,e.id,{currentVersion:this.cliVersion});break;case"upgrade.set-policy":await me(s,e);break;case"machine.remote-access.pause":await q(this.client,this.remoteAccessPauseController,e);break;case"agents.refresh":await $(s,e);break;case"agent.permission.respond":await this.handleAgentPermissionResponse(e);break;case"agent.auth.start":case"agent.auth.logout":case"agent.session-options.get":case"agent.external-sessions.list":case"agent.external-sessions.import":case"agent.external-sessions.close":case"agent.external-sessions.delete":await this.handleAcpAgentRequest(e);break;case"models.refresh":await U(s,e);break;case"agent.config.get":await W(s,e);break;case"agent.config.upsert":await V(s,e);break;case"agent.config.clear":await N(s,e);break;case"agent.config.test":await J(s,e);break;case"agent.workspace.validate":await X(s,e);break;case"agent.capabilities.list":await B(s,e);break;case"skill.list":case"skill.install":case"skill.doctor":case"skill.setup":case"skill.use":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;case"session.wechat-rpa.channel.get":case"session.wechat-rpa.channel.upsert":case"session.wechat-rpa.channel.sync":case"manager.wechat-rpa.channel.get":case"manager.wechat-rpa.channel.upsert":case"manager.wechat-rpa.channel.sync":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;default:this.client.sendRes({type:"res",id:e.id,ok:!1,error:`Unknown method: ${e.method}`})}}catch(a){if(t.propagateErrors)throw a;this.client.sendRes({type:"res",id:e.id,ok:!1,error:a instanceof Error?a.message:String(a)})}finally{this.activeRequests--}}evictIdleSessions(){if(this.sessions.size<We)return;let e=null;for(const[t,s]of this.sessions)(!e||s.lastActiveAt<e.session.lastActiveAt)&&(e={key:t,session:s});e&&(this.revokeManagedRoomToken(e.session),e.session.heartbeatTimer&&(clearInterval(e.session.heartbeatTimer),e.session.heartbeatTimer=null),e.session.adapter.removeAllListeners(),e.session.adapter.stop().catch(()=>{}),this.sessions.delete(e.key))}resolvePath(e){return Se(e)}resolveAuthorizedPath(e,t){return Re(e,ve(t))}async resolveAuthorizedPathAsync(e,t){return be(e,await we(t))}async cleanup(){this.acpDiscoveryWatcher.stop(),this.nativeFusion?.setExternalTerminalHandler?.(null),this.nativeCleanupOutbox.stop(),this.historySubscriptions.closeAll(),this.activityProbeTimer&&(clearInterval(this.activityProbeTimer),this.activityProbeTimer=null),this.attachmentCleanupTimer&&(clearInterval(this.attachmentCleanupTimer),this.attachmentCleanupTimer=null);for(const[,e]of this.sessions)this.revokeManagedRoomToken(e),e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),e.adapter.removeAllListeners(),await e.adapter.stop().catch(()=>{});this.sessions.clear(),this.runTextAcc.clear(),ee(this.getRuntime()),await this.managerRuntime.stop(),I(null)}revokeManagedRoomToken(e){const t=e.managedRoomContextToken;e.managedRoomContextToken=null,t&&this.managedRoomContextRevoker?.(t)}}export{Us as SessionManager,Bs as resolveSessionWorkDir};
3
+ The task could not be recovered after the Shennian upgrade (${t.reason}). The session is preserved; retry or send a new message.`;A(e.sessionId,{id:`upgrade-recovery-terminal-${e.runId}`,sessionId:e.sessionId,role:"agent",ts:Date.now(),payload:JSON.stringify({v:1,type:"error",content:a,error:!0,recoveryState:t.result,recoveryReason:t.reason})}),k({sessionId:e.sessionId,agentType:e.agentType,sessionMode:e.sessionMode,workDir:e.workDir,agentSessionId:e.agentSessionId,modelId:e.modelId,status:"failed",attentionId:`upgrade-recovery:${e.runId}`,lastActivityAt:new Date().toISOString()}),T(this.client);const i=e.lastObservedEventSeq+1;this.client.sendAgentEvent({type:"event",event:"agent",payload:t.result==="interrupted_by_upgrade"?{state:"interrupted_by_upgrade",sessionId:e.sessionId,runId:e.providerRunId??e.runId,seq:i,reason:t.reason}:{state:"recovery_failed",sessionId:e.sessionId,runId:e.providerRunId??e.runId,seq:i,reason:t.reason,...t.message?{message:t.message}:{}},seq:i,id:`agent-recovery-terminal-${e.runId}`}),this.chatQueue.noteTerminal(e.sessionId)}setManagedRoomTurnPreparer(e){this.chatQueue.setManagedRoomTurnPreparer(e)}setManagedAgentDirectTurnPreparer(e){this.chatQueue.setManagedAgentDirectTurnPreparer(e)}setManagedRecoveryPreparer(e){this.prepareManagedRecovery=e}async enqueueManagedRoomBatch(e){const t=e.map(s=>{const a=v().find(i=>i.sessionId===s.params.sessionId);return{context:s.context,params:{...s.params,modelId:a?.modelId??s.params.modelId,sessionMode:a?.sessionMode??s.params.sessionMode,managerConfig:a?.managerConfig??s.params.managerConfig,managerDefaultWorkerAgentType:a?.managerDefaultWorkerAgentType??s.params.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:a?.managerDefaultWorkerModelId??s.params.managerDefaultWorkerModelId}}});await this.chatQueue.enqueueManagedRoomBatch(t)}discardPendingManagedRoomMessages(e){return this.chatQueue.discardPendingManagedRoomMessages(e)}managedChatContext(e){return{source:"room_activation",managedAgentPolicy:e.managedAgentPolicy,managedRoomPromptSnapshot:e.managedRoomPromptSnapshot}}async dispatchManagedChat(e,t){await f(this.getRuntime(),e,this.managedChatContext(t))}async dispatchQueuedChat(e,t){const s={value:null},a=new Proxy(this.client,{get:(i,d)=>{if(d==="sendRes")return o=>{s.value=o};const r=Reflect.get(i,d,i);return typeof r=="function"?r.bind(i):r}});if(await f({...this.getRuntime(),client:a},e,t?this.managedChatContext(t):void 0),s.value?.payload?.deliveryState==="failed")throw new ye(s.value.payload.failureMessage??s.value.error??"queued_chat_dispatch_failed");if(!s.value?.ok)throw new Error(s.value?.error??"queued_chat_dispatch_failed")}async synchronizeSessionDeletions(){Oe();try{await this.synchronizeSessionDeletionsOnce(),Fe()}catch(e){if(e instanceof $e||e instanceof Ue)throw e;const t=e instanceof Error?e.message:String(e),s=/offline|not connected|disconnect|timed out|timeout|temporar|unavailable|ECONN|ENOTFOUND|EIO|EBUSY/i.test(t)?"transport_unavailable":/invalid (?:deletion sync|session deletion)|deletion revision gap/i.test(t)?"invalid_deletion_sync_response":"deletion_sync_failed";throw console.warn("[session.deletions] retrying failed synchronization",{code:s}),R({code:s,retryable:!0}),e}}async synchronizeSessionDeletionsOnce(){const e=y(),t=_e(),s=await this.client.sendReq({type:"req",id:`session-deletions-sync-${g.randomUUID()}`,method:"session.deletions.sync",params:{protocolVersion:w,dataEpoch:b,daemonInstallationId:e,afterRevision:t}});if(!s.ok){const r=s.rejection;throw r?(R({code:r.code,retryable:r.retryable&&r.requiredAction==="retry"}),new Error(r.code)):(R({code:"unstructured_rejection",retryable:!0}),new Error(s.error??"unstructured_rejection"))}const a=s.payload;u(a),Je(a,t);let i=a.baselineRevision;i>t&&C(i);for(const r of a.deletions)r.deletionRevision<=i||(await this.applySessionDeletion(r),i=r.deletionRevision,C(i));if(i!==a.latestRevision)throw new Error(`deletion revision gap: applied ${i}, latest ${a.latestRevision}`);if(i===0)return;const d=await this.client.sendReq({type:"req",id:`session-deletions-ack-${g.randomUUID()}`,method:"session.deletions.ack",params:{protocolVersion:w,dataEpoch:b,daemonInstallationId:e,deletionRevision:i}});if(!d.ok)throw new Error(d.error??"session deletion acknowledgement failed")}async applySessionDeletion(e){const t=new Me({daemonInstallationId:y()});t.recordSession(e);for(const a of e.sources){const i=t.find(a.agentType,a.sourceSessionKey);i&&i.sessionId!==e.sessionId||t.record({...e,...a})}this.runJournal.markSessionAborted(e.sessionId),this.recoveryBarrier.end(e.sessionId);const s=this.sessions.get(e.sessionId);if(s){this.revokeManagedRoomToken(s);try{await s.adapter.stop()}catch{}this.sessions.delete(e.sessionId)}this.historySubscriptions.closeSession(e.sessionId),this.nativeFusion?.handleSessionDeleted(e.sessionId),this.chatQueue.purgeSession(e.sessionId),De(e.sessionId),Te(e.sessionId),Ce(e.sessionId),this.runTextAcc.delete(e.sessionId),this.publishSessionActivity(e.sessionId,null),this.nativeCleanupOutbox.enqueue(e),this.nativeCleanupOutbox.kick()}publishSessionActivity(e,t){this.client.sendEvent({type:"event",event:"session.activity",payload:{sessionId:e,activity:t}})}async handleExternalTerminal(e){const t=this.sessions.get(e);t&&(t.journalRunId&&(this.recoveryCoordinator.completeFromProvider(e,t.journalRunId),t.journalRunId=null),this.sessions.delete(e),this.revokeManagedRoomToken(t),t.heartbeatTimer&&clearInterval(t.heartbeatTimer),t.adapter.removeAllListeners(),await t.adapter.stop().catch(()=>{})),this.chatQueue.noteTerminal(e)}async publishManagedActivitySnapshots(){for(const[e,t]of this.sessions.entries()){if(t.heartbeatTimer||!t.currentRunId||!t.adapter.getStatus)continue;const s=await t.adapter.getStatus().catch(()=>null);if(!s?.active||!s.runPhase)continue;const a=new Date().toISOString();this.publishSessionActivity(e,{sessionId:e,runId:s.runId||t.currentRunId,runPhase:s.runPhase,startedAt:new Date(t.lastActiveAt).toISOString(),updatedAt:a,canStop:s.canStop??!0});const i=t.heartbeatSeq++;this.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:e,runId:s.runId||t.currentRunId,seq:i,runPhase:s.runPhase,canStop:s.canStop??!0},seq:i,id:`agent-status-${s.runId||t.currentRunId}-${Date.now()}`})}}reloadCustomAgents(){for(const t of x())t.startsWith("custom:")&&M(t);const e=_();for(const[t,s]of Object.entries(e.customAgents??{}))Ie(t,s)}async handleAgentPermissionResponse(e){const t=typeof e.params.sessionId=="string"?e.params.sessionId:"",s=typeof e.params.permissionId=="string"?e.params.permissionId:"",a=typeof e.params.optionId=="string"?e.params.optionId:"",i=this.sessions.get(t);if(!t||!s||!a||!i?.adapter.respondToPermission){this.client.sendRes({type:"res",id:e.id,ok:!1,error:"permission_request_unavailable"});return}try{await i.adapter.respondToPermission(s,a),this.client.sendRes({type:"res",id:e.id,ok:!0,payload:{accepted:!0}})}catch(d){this.client.sendRes({type:"res",id:e.id,ok:!1,error:d instanceof Error?d.message:"permission_response_failed"})}}async handleAcpAgentRequest(e){const t=typeof e.params.agentType=="string"?e.params.agentType:"";if(!t.startsWith("acp:")||!S(t)){this.client.sendRes({type:"res",id:e.id,ok:!1,error:"invalid_acp_agent"});return}const s=e.method==="agent.auth.start"?[...this.sessions.values()].find(r=>r.agentType===t&&r.adapter.hasPendingAuthenticationRetry?.())?.adapter:void 0;if(String(e.method)==="agent.session-options.get"){const r=typeof e.params.sessionId=="string"?e.params.sessionId:"",o=this.sessions.get(r);if(!o||o.agentType!==t||!o.adapter.getSessionOptions){this.client.sendRes({type:"res",id:e.id,ok:!1,error:"acp_session_not_active"});return}this.client.sendRes({type:"res",id:e.id,ok:!0,payload:o.adapter.getSessionOptions()});return}const a=s??E(t);if(!a){this.client.sendRes({type:"res",id:e.id,ok:!1,error:"acp_agent_not_installed"});return}const i=typeof e.params.cwd=="string"&&e.params.cwd?e.params.cwd:process.cwd(),d=!s;try{d&&await a.start(`acp-control-${g.randomUUID()}`,i);let r={ok:!0};if(e.method==="agent.external-sessions.list")r={sessions:await a.listExternalSessions?.({cwd:typeof e.params.cwd=="string"?e.params.cwd:void 0})??[]};else if(e.method==="agent.external-sessions.import"){if(!a.loadExternalSession)throw new Error("ACP session history import is unsupported");const o=String(e.params.agentSessionId??""),h=String(e.params.sessionId??"");if(!o||!h)throw new Error("ACP and Shennian session ids are required");const n=await a.loadExternalSession(o),c=Date.now();for(const[p,m]of n.entries()){const P=`acp-import-${g.createHash("sha256").update(`${t}\0${o}\0${m.sourceId}`).digest("hex")}`;A(h,{id:P,sessionId:h,role:m.role,ts:c+p,payload:m.payload})}k({sessionId:h,agentType:t,workDir:i,agentSessionId:o,title:typeof e.params.title=="string"?e.params.title.slice(0,120):void 0,titleSource:typeof e.params.title=="string"?"agent_native":void 0,status:"active"}),T(this.client),this.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:h,agentType:t,agentSessionId:o,workDir:i,status:"active"}}}),r={sessionId:h,agentSessionId:o,importedMessageCount:n.length}}else if(e.method==="agent.auth.start"){if(!a.authenticate)throw new Error("ACP authentication is unsupported");await a.authenticate(String(e.params.methodId??""))}else if(e.method==="agent.auth.logout"){if(!a.logout)throw new Error("ACP logout is unsupported");await a.logout()}else{const o=String(e.params.agentSessionId??"");if(!o)throw new Error("ACP session id is required");if(e.method==="agent.external-sessions.close"){if(!a.closeExternalSession)throw new Error("ACP session close is unsupported");await a.closeExternalSession(o)}else{if(!a.deleteExternalSession)throw new Error("ACP session delete is unsupported");await a.deleteExternalSession(o)}}this.client.sendRes({type:"res",id:e.id,ok:!0,payload:r})}catch(r){this.client.sendRes({type:"res",id:e.id,ok:!1,error:r instanceof Error?r.message:"acp_request_failed"})}finally{d&&await a.stop().catch(()=>{})}}async handleReq(e,t={}){const s=this.getRuntime();this.activeRequests++;try{switch(e.method){case"chat.send":u(e.params),this.recoveryBarrier.isDispatchFrozen()||this.recoveryBarrier.isRecovering(String(e.params.sessionId??""))?await this.chatQueue.handleEnqueue({...e,method:"chat.enqueue"}):await f(s,e);break;case"chat.enqueue":u(e.params),await this.chatQueue.handleEnqueue(e);break;case"chat.queue.get":u(e.params),await this.chatQueue.handleGet(e);break;case"chat.queue.edit":u(e.params),await this.chatQueue.handleEdit(e);break;case"chat.queue.delete":u(e.params),await this.chatQueue.handleDelete(e);break;case"chat.abort":u(e.params),await V(s,e);break;case"session.refresh":u(e.params),await L(s,e);break;case"session.history.open":this.historySubscriptions.handleOpen(e);break;case"session.history.sync":this.historySubscriptions.handleSync(e);break;case"session.history.page":this.historySubscriptions.handlePage(e);break;case"session.history.close":this.historySubscriptions.handleClose(e);break;case"session.body.replica.subscribe":this.historySubscriptions.handleReplicaSubscribe(e);break;case"session.body.replica.unsubscribe":this.historySubscriptions.handleReplicaUnsubscribe(e);break;case"session.history":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"upgrade_required"});break;case"session.message.status":K(s,e);break;case"session.tool.detail":await G(s,e);break;case"voice.session.body.window":Z(s,e);break;case"session.title.set":await Y(s,e);break;case"room.agent.bind-managed":await ke(s,this.managedRoomBinder,e);break;case"room.agent.restart-managed":await Ae(s,this.managedRoomBinder,e);break;case"fs.ls":await se(s,e);break;case"fs.read":await te(s,e);break;case"fs.write":await ne(s,e);break;case"fs.export.markdown-pdf":await de(s,e);break;case"fs.export.markdown-pdf.setup":await le(s,e);break;case"fs.archive.zip":await he(s,e);break;case"fs.rename":await ae(s,e);break;case"fs.transfer.start":await ce(s,e);break;case"fs.transfer.chunk":await re(s,e);break;case"fs.transfer.finish":await oe(s,e);break;case"fs.transfer.abort":await ie(s,e);break;case"region.probe":await ue(s,e);break;case"region.switch":await pe(s,e);break;case"upgrade.start":await O(this.client,e.id,e.params.version,{currentVersion:this.cliVersion,confirmedMajor:e.params.confirmedMajor===!0,prepareForUpgrade:a=>this.prepareForUpgrade(a),cancelUpgradePreparation:()=>this.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()}});break;case"upgrade.status":await F(this.client,e.id,{currentVersion:this.cliVersion});break;case"upgrade.set-policy":await me(s,e);break;case"machine.remote-access.pause":await q(this.client,this.remoteAccessPauseController,e);break;case"agents.refresh":await $(s,e);break;case"agent.permission.respond":await this.handleAgentPermissionResponse(e);break;case"agent.auth.start":case"agent.auth.logout":case"agent.session-options.get":case"agent.external-sessions.list":case"agent.external-sessions.import":case"agent.external-sessions.close":case"agent.external-sessions.delete":await this.handleAcpAgentRequest(e);break;case"models.refresh":await U(s,e);break;case"agent.config.get":await W(s,e);break;case"agent.config.upsert":await j(s,e);break;case"agent.config.clear":await N(s,e);break;case"agent.config.test":await J(s,e);break;case"agent.workspace.validate":await X(s,e);break;case"agent.capabilities.list":await B(s,e);break;case"skill.list":case"skill.install":case"skill.doctor":case"skill.setup":case"skill.use":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;case"session.wechat-rpa.channel.get":case"session.wechat-rpa.channel.upsert":case"session.wechat-rpa.channel.sync":case"manager.wechat-rpa.channel.get":case"manager.wechat-rpa.channel.upsert":case"manager.wechat-rpa.channel.sync":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;default:this.client.sendRes({type:"res",id:e.id,ok:!1,error:`Unknown method: ${e.method}`})}}catch(a){if(t.propagateErrors)throw a;this.client.sendRes({type:"res",id:e.id,ok:!1,error:a instanceof Error?a.message:String(a)})}finally{this.activeRequests--}}evictIdleSessions(){if(this.sessions.size<We)return;let e=null;for(const[t,s]of this.sessions)(!e||s.lastActiveAt<e.session.lastActiveAt)&&(e={key:t,session:s});e&&(this.revokeManagedRoomToken(e.session),e.session.heartbeatTimer&&(clearInterval(e.session.heartbeatTimer),e.session.heartbeatTimer=null),e.session.adapter.removeAllListeners(),e.session.adapter.stop().catch(()=>{}),this.sessions.delete(e.key))}resolvePath(e){return Se(e)}resolveAuthorizedPath(e,t){return we(e,ve(t))}async resolveAuthorizedPathAsync(e,t){return be(e,await Re(t))}async cleanup(){this.acpDiscoveryWatcher.stop(),this.nativeFusion?.setExternalTerminalHandler?.(null),this.nativeCleanupOutbox.stop(),this.historySubscriptions.closeAll(),this.activityProbeTimer&&(clearInterval(this.activityProbeTimer),this.activityProbeTimer=null),this.attachmentCleanupTimer&&(clearInterval(this.attachmentCleanupTimer),this.attachmentCleanupTimer=null);for(const[,e]of this.sessions)this.revokeManagedRoomToken(e),e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),e.adapter.removeAllListeners(),await e.adapter.stop().catch(()=>{});this.sessions.clear(),this.runTextAcc.clear(),ee(this.getRuntime()),await this.managerRuntime.stop(),I(null)}revokeManagedRoomToken(e){const t=e.managedRoomContextToken;e.managedRoomContextToken=null,t&&this.managedRoomContextRevoker?.(t)}}export{Us as SessionManager,Bs as resolveSessionWorkDir};
@@ -1,7 +1,7 @@
1
1
  import type { AgentRecoveryResult } from '../../agents/adapter.js';
2
2
  import type { ActiveRunJournal } from './journal.js';
3
3
  import type { RunRecoveryBarrier } from './barrier.js';
4
- import type { RunJournalRecord } from './schema.js';
4
+ import { type RunJournalRecord } from './schema.js';
5
5
  export type RecoveryTerminal = Extract<AgentRecoveryResult, {
6
6
  result: 'interrupted_by_upgrade' | 'recovery_failed';
7
7
  }>;
@@ -1 +1 @@
1
- const d=new Set(["continue_dispatch_outcome_unknown","provider_session_missing","provider_identity_mismatch","authentication_required","permission_context_unavailable","provider_error"]);function l(p){return typeof p=="string"&&d.has(p)}class h{deps;pendingBySession=new Map;constructor(e){this.deps=e}async recoverAll(){const e=this.deps.journal.loadPendingReconciliation().records,t=new Map;for(const s of e){this.deps.barrier.begin(s.sessionId);const i=this.pendingBySession.get(s.sessionId)??new Set;i.add(s.runId),this.pendingBySession.set(s.sessionId,i);const a=t.get(s.sessionId)??[];a.push(s),t.set(s.sessionId,a)}const r=[...t.values()].filter(s=>s.some(i=>i.source!=="manager_worker")),o=[...t.values()].filter(s=>s.every(i=>i.source==="manager_worker")),n=s=>Promise.all(s.map(async i=>{for(const a of i)await this.recoverOne(a)}));await n(r),await n(o)}completeFromProvider(e,t){this.deps.journal.remove(e,t);const r=this.pendingBySession.get(e);r?.delete(t),(!r||r.size===0)&&(this.pendingBySession.delete(e),this.deps.barrier.end(e))}async recoverOne(e){if(this.deps.isCanonicalTerminal?.(e)){this.completeFromProvider(e.sessionId,e.runId);return}if(e.phase==="aborted"){this.completeFromProvider(e.sessionId,e.runId);return}if(e.phase==="interrupted_by_upgrade"||e.phase==="recovery_failed"){const s=e.phase==="interrupted_by_upgrade"?{result:"interrupted_by_upgrade",reason:e.recoveryReason==="missing_agent_session"?"missing_agent_session":"resume_unsupported"}:{result:"recovery_failed",reason:l(e.recoveryReason)?e.recoveryReason:"provider_error"};await this.deps.commitTerminal(e,s),this.completeFromProvider(e.sessionId,e.runId);return}const t=e.recoveryAttempts+1,r=Date.now();let o=this.deps.journal.update(e.sessionId,e.runId,{phase:"recovering",recoveryAttempts:t})??e,n;try{n=await this.deps.recover(o,()=>{o=this.deps.journal.update(e.sessionId,e.runId,{continueDispatchState:"intent"})??o})}catch(s){const i=this.deps.journal.get(e.sessionId,e.runId);if(i?.phase==="interrupted_by_upgrade"||i?.phase==="recovery_failed")throw s;const a=this.deps.maxAttempts??3;if(t<a){this.deps.journal.update(e.sessionId,e.runId,{phase:"running"});const u=this.deps.journal.get(e.sessionId,e.runId);if(u)return this.recoverOne(u)}this.deps.onOutcome?.({adapterType:e.agentType,source:e.source,result:"recovery_failed",attempts:t,durationMs:Date.now()-r,reason:"provider_error"}),await this.finishTerminal(e,{result:"recovery_failed",reason:"provider_error",message:s instanceof Error?s.message.slice(0,500):String(s).slice(0,500)});return}if(this.deps.onOutcome?.({adapterType:e.agentType,source:e.source,result:n.result,attempts:t,durationMs:Date.now()-r,..."reason"in n?{reason:n.reason}:{}}),n.result==="continued"){this.deps.journal.update(e.sessionId,e.runId,{continueDispatchState:"accepted",phase:"running"});return}if(n.result==="reattached"||n.result==="resumed"){this.deps.journal.update(e.sessionId,e.runId,{phase:"running"});return}if(n.result==="terminal_reconciled"){this.completeFromProvider(e.sessionId,e.runId);return}await this.finishTerminal(e,n)}async finishTerminal(e,t){this.deps.journal.update(e.sessionId,e.runId,{phase:t.result,recoveryReason:t.reason}),await this.deps.commitTerminal(e,t),this.completeFromProvider(e.sessionId,e.runId)}}export{h as RunRecoveryCoordinator};
1
+ import{isRecoverableJournalPhase as l}from"./schema.js";const h=new Set(["continue_dispatch_outcome_unknown","provider_session_missing","provider_identity_mismatch","authentication_required","permission_context_unavailable","provider_error"]);function c(d){return typeof d=="string"&&h.has(d)}class v{deps;pendingBySession=new Map;constructor(e){this.deps=e}async recoverAll(){const e=this.deps.journal.loadPendingReconciliation().records,t=new Map;for(const s of e){this.deps.barrier.begin(s.sessionId);const i=this.pendingBySession.get(s.sessionId)??new Set;i.add(s.runId),this.pendingBySession.set(s.sessionId,i);const a=t.get(s.sessionId)??[];a.push(s),t.set(s.sessionId,a)}const r=[...t.values()].filter(s=>s.some(i=>i.source!=="manager_worker")),p=[...t.values()].filter(s=>s.every(i=>i.source==="manager_worker")),n=s=>Promise.all(s.map(async i=>{const a=i.filter(o=>l(o.phase)),u=a.at(-1);for(const o of a.slice(0,-1))this.completeFromProvider(o.sessionId,o.runId);for(const o of i)l(o.phase)&&o!==u||await this.recoverOne(o)}));await n(r),await n(p)}completeFromProvider(e,t){this.deps.journal.remove(e,t);const r=this.pendingBySession.get(e);r?.delete(t),(!r||r.size===0)&&(this.pendingBySession.delete(e),this.deps.barrier.end(e))}async recoverOne(e){if(this.deps.isCanonicalTerminal?.(e)){this.completeFromProvider(e.sessionId,e.runId);return}if(e.phase==="aborted"){this.completeFromProvider(e.sessionId,e.runId);return}if(e.phase==="interrupted_by_upgrade"||e.phase==="recovery_failed"){const s=e.phase==="interrupted_by_upgrade"?{result:"interrupted_by_upgrade",reason:e.recoveryReason==="missing_agent_session"?"missing_agent_session":"resume_unsupported"}:{result:"recovery_failed",reason:c(e.recoveryReason)?e.recoveryReason:"provider_error"};await this.deps.commitTerminal(e,s),this.completeFromProvider(e.sessionId,e.runId);return}const t=e.recoveryAttempts+1,r=Date.now();let p=this.deps.journal.update(e.sessionId,e.runId,{phase:"recovering",recoveryAttempts:t})??e,n;try{n=await this.deps.recover(p,()=>{p=this.deps.journal.update(e.sessionId,e.runId,{continueDispatchState:"intent"})??p})}catch(s){const i=this.deps.journal.get(e.sessionId,e.runId);if(i?.phase==="interrupted_by_upgrade"||i?.phase==="recovery_failed")throw s;const a=this.deps.maxAttempts??3;if(t<a){this.deps.journal.update(e.sessionId,e.runId,{phase:"running"});const u=this.deps.journal.get(e.sessionId,e.runId);if(u)return this.recoverOne(u)}this.deps.onOutcome?.({adapterType:e.agentType,source:e.source,result:"recovery_failed",attempts:t,durationMs:Date.now()-r,reason:"provider_error"}),await this.finishTerminal(e,{result:"recovery_failed",reason:"provider_error",message:s instanceof Error?s.message.slice(0,500):String(s).slice(0,500)});return}if(this.deps.onOutcome?.({adapterType:e.agentType,source:e.source,result:n.result,attempts:t,durationMs:Date.now()-r,..."reason"in n?{reason:n.reason}:{}}),n.result==="continued"){this.deps.journal.update(e.sessionId,e.runId,{continueDispatchState:"accepted",phase:"running"});return}if(n.result==="reattached"||n.result==="resumed"){this.deps.journal.update(e.sessionId,e.runId,{phase:"running"});return}if(n.result==="terminal_reconciled"){this.completeFromProvider(e.sessionId,e.runId);return}await this.finishTerminal(e,n)}async finishTerminal(e,t){this.deps.journal.update(e.sessionId,e.runId,{phase:t.result,recoveryReason:t.reason}),await this.deps.commitTerminal(e,t),this.completeFromProvider(e.sessionId,e.runId)}}export{v as RunRecoveryCoordinator};
@@ -1,5 +1,5 @@
1
1
  import { type RunJournalRecord } from './schema.js';
2
- type JournalInput = Omit<RunJournalRecord, 'schemaVersion' | 'lastObservedEventSeq' | 'phase' | 'upgradeJobId' | 'interruptionReason' | 'continueDispatchState' | 'recoveryAttempts' | 'createdAt' | 'updatedAt' | 'recoveryReason'>;
2
+ type JournalInput = Omit<RunJournalRecord, 'schemaVersion' | 'lastObservedEventSeq' | 'phase' | 'upgradeJobId' | 'interruptionReason' | 'continueDispatchState' | 'recoveryAttempts' | 'createdAt' | 'updatedAt' | 'recoveryReason' | 'deliveryMode' | 'externalRunId'>;
3
3
  export type RunJournalLoadResult = {
4
4
  records: RunJournalRecord[];
5
5
  quarantined: string[];
@@ -1,2 +1,2 @@
1
- import n from"node:fs";import s from"node:path";import{createHash as p,randomUUID as u}from"node:crypto";import{resolveShennianPath as S}from"../../config/index.js";import{RUN_JOURNAL_SCHEMA_VERSION as w,isRecoverableJournalPhase as y,parseRunJournalRecord as h}from"./schema.js";function f(a,t){return p("sha256").update(`${a}\0${t}`).digest("hex")+".json"}function c(a){try{const t=n.lstatSync(a);if(t.isSymbolicLink()||!t.isFile())throw new Error("journal target is not a regular file")}catch(t){if(t.code!=="ENOENT")throw t}}function l(a){if(process.platform==="win32")return;const t=n.openSync(a,"r");try{n.fsyncSync(t)}finally{n.closeSync(t)}}function m(a){for(let t=0;t<3;t+=1){n.mkdirSync(a,{recursive:!0,mode:448});try{const r=n.lstatSync(a);if(r.isSymbolicLink()||!r.isDirectory())throw new Error("journal directory is unsafe");process.platform!=="win32"&&n.chmodSync(a,448);return}catch(r){if(r.code!=="ENOENT"||t===2)throw r}}}class N{daemonInstallationId;directory;quarantineDirectory;constructor(t,r={}){this.daemonInstallationId=t,this.directory=r.directory??S("runtime","run-recovery","active"),this.quarantineDirectory=s.join(s.dirname(this.directory),"quarantine"),m(this.directory),m(this.quarantineDirectory)}begin(t){const r=new Date().toISOString(),e={...t,schemaVersion:w,lastObservedEventSeq:-1,phase:"dispatching",upgradeJobId:null,interruptionReason:null,continueDispatchState:"none",recoveryAttempts:0,recoveryReason:null,createdAt:r,updatedAt:r};return this.write(e),e}update(t,r,e){const o=this.get(t,r);if(!o)return null;const i=h({...o,...e,updatedAt:new Date().toISOString()});return this.write(i),i}get(t,r){const e=this.recordPath(t,r);try{c(e);const o=h(JSON.parse(n.readFileSync(e,"utf8")));if(o.sessionId!==t||o.runId!==r)throw new Error("journal identity mismatch");return o}catch(o){return o.code==="ENOENT"||this.quarantine(e),null}}loadRecoverable(){return this.load(t=>y(t.phase))}loadPendingReconciliation(){return this.load(t=>y(t.phase)||t.phase==="interrupted_by_upgrade"||t.phase==="recovery_failed"||t.phase==="aborted")}load(t){const r=[],e=[];for(const o of n.readdirSync(this.directory)){if(!o.endsWith(".json"))continue;const i=s.join(this.directory,o);try{c(i);const d=h(JSON.parse(n.readFileSync(i,"utf8")));if(d.daemonInstallationId!==this.daemonInstallationId)throw new Error("daemon installation identity mismatch");if(f(d.sessionId,d.runId)!==o)throw new Error("journal filename identity mismatch");t(d)&&r.push(d)}catch{e.push(this.quarantine(i))}}return r.sort((o,i)=>o.createdAt.localeCompare(i.createdAt)),{records:r,quarantined:e}}markPlannedUpgrade(t){const{records:r}=this.loadRecoverable();for(const e of r)this.update(e.sessionId,e.runId,{upgradeJobId:t,interruptionReason:"planned_upgrade"});return l(this.directory),r.length}markAborted(t,r){this.update(t,r,{phase:"aborted"})}markSessionAborted(t){const r=this.loadRecoverable().records.filter(e=>e.sessionId===t);for(const e of r)this.markAborted(e.sessionId,e.runId);return this.sync(),r.length}remove(t,r){const e=this.recordPath(t,r);c(e);try{n.unlinkSync(e)}catch(o){if(o.code!=="ENOENT")throw o}l(this.directory)}sync(){for(const t of n.readdirSync(this.directory)){if(!t.endsWith(".json"))continue;const r=s.join(this.directory,t);c(r);const e=n.openSync(r,process.platform==="win32"?"r+":"r");try{n.fsyncSync(e)}finally{n.closeSync(e)}}l(this.directory)}recordPath(t,r){return s.join(this.directory,f(t,r))}write(t){if(t.daemonInstallationId!==this.daemonInstallationId)throw new Error("daemon installation identity mismatch");const r=h(t),e=this.recordPath(r.sessionId,r.runId);c(e);const o=`${e}.tmp-${process.pid}-${u()}`;try{n.writeFileSync(o,`${JSON.stringify(r,null,2)}
2
- `,{encoding:"utf8",mode:384,flag:"wx"}),process.platform!=="win32"&&n.chmodSync(o,384);const i=n.openSync(o,process.platform==="win32"?"r+":"r");try{n.fsyncSync(i)}finally{n.closeSync(i)}n.renameSync(o,e),l(this.directory)}finally{try{n.unlinkSync(o)}catch{}}}quarantine(t){const r=s.join(this.quarantineDirectory,`${s.basename(t)}.${Date.now()}.${u()}.invalid`);try{return c(t),n.renameSync(t,r),process.platform!=="win32"&&n.chmodSync(r,384),l(this.quarantineDirectory),r}catch{return t}}}export{N as ActiveRunJournal};
1
+ import n from"node:fs";import s from"node:path";import{createHash as p,randomUUID as u}from"node:crypto";import{resolveShennianPath as S}from"../../config/index.js";import{RUN_JOURNAL_SCHEMA_VERSION as w,isRecoverableJournalPhase as y,parseRunJournalRecord as h}from"./schema.js";function f(a,t){return p("sha256").update(`${a}\0${t}`).digest("hex")+".json"}function c(a){try{const t=n.lstatSync(a);if(t.isSymbolicLink()||!t.isFile())throw new Error("journal target is not a regular file")}catch(t){if(t.code!=="ENOENT")throw t}}function l(a){if(process.platform==="win32")return;const t=n.openSync(a,"r");try{n.fsyncSync(t)}finally{n.closeSync(t)}}function m(a){for(let t=0;t<3;t+=1){n.mkdirSync(a,{recursive:!0,mode:448});try{const r=n.lstatSync(a);if(r.isSymbolicLink()||!r.isDirectory())throw new Error("journal directory is unsafe");process.platform!=="win32"&&n.chmodSync(a,448);return}catch(r){if(r.code!=="ENOENT"||t===2)throw r}}}class R{daemonInstallationId;directory;quarantineDirectory;constructor(t,r={}){this.daemonInstallationId=t,this.directory=r.directory??S("runtime","run-recovery","active"),this.quarantineDirectory=s.join(s.dirname(this.directory),"quarantine"),m(this.directory),m(this.quarantineDirectory)}begin(t){const r=new Date().toISOString(),e={...t,schemaVersion:w,lastObservedEventSeq:-1,phase:"dispatching",upgradeJobId:null,interruptionReason:null,continueDispatchState:"none",deliveryMode:"direct",externalRunId:null,recoveryAttempts:0,recoveryReason:null,createdAt:r,updatedAt:r};return this.write(e),e}update(t,r,e){const o=this.get(t,r);if(!o)return null;const i=h({...o,...e,updatedAt:new Date().toISOString()});return this.write(i),i}get(t,r){const e=this.recordPath(t,r);try{c(e);const o=h(JSON.parse(n.readFileSync(e,"utf8")));if(o.sessionId!==t||o.runId!==r)throw new Error("journal identity mismatch");return o}catch(o){return o.code==="ENOENT"||this.quarantine(e),null}}loadRecoverable(){return this.load(t=>y(t.phase))}loadPendingReconciliation(){return this.load(t=>y(t.phase)||t.phase==="interrupted_by_upgrade"||t.phase==="recovery_failed"||t.phase==="aborted")}load(t){const r=[],e=[];for(const o of n.readdirSync(this.directory)){if(!o.endsWith(".json"))continue;const i=s.join(this.directory,o);try{c(i);const d=h(JSON.parse(n.readFileSync(i,"utf8")));if(d.daemonInstallationId!==this.daemonInstallationId)throw new Error("daemon installation identity mismatch");if(f(d.sessionId,d.runId)!==o)throw new Error("journal filename identity mismatch");t(d)&&r.push(d)}catch{e.push(this.quarantine(i))}}return r.sort((o,i)=>o.createdAt.localeCompare(i.createdAt)),{records:r,quarantined:e}}markPlannedUpgrade(t){const{records:r}=this.loadRecoverable();for(const e of r)this.update(e.sessionId,e.runId,{upgradeJobId:t,interruptionReason:"planned_upgrade"});return l(this.directory),r.length}markAborted(t,r){this.update(t,r,{phase:"aborted"})}markSessionAborted(t){const r=this.loadRecoverable().records.filter(e=>e.sessionId===t);for(const e of r)this.markAborted(e.sessionId,e.runId);return this.sync(),r.length}remove(t,r){const e=this.recordPath(t,r);c(e);try{n.unlinkSync(e)}catch(o){if(o.code!=="ENOENT")throw o}l(this.directory)}sync(){for(const t of n.readdirSync(this.directory)){if(!t.endsWith(".json"))continue;const r=s.join(this.directory,t);c(r);const e=n.openSync(r,process.platform==="win32"?"r+":"r");try{n.fsyncSync(e)}finally{n.closeSync(e)}}l(this.directory)}recordPath(t,r){return s.join(this.directory,f(t,r))}write(t){if(t.daemonInstallationId!==this.daemonInstallationId)throw new Error("daemon installation identity mismatch");const r=h(t),e=this.recordPath(r.sessionId,r.runId);c(e);const o=`${e}.tmp-${process.pid}-${u()}`;try{n.writeFileSync(o,`${JSON.stringify(r,null,2)}
2
+ `,{encoding:"utf8",mode:384,flag:"wx"}),process.platform!=="win32"&&n.chmodSync(o,384);const i=n.openSync(o,process.platform==="win32"?"r+":"r");try{n.fsyncSync(i)}finally{n.closeSync(i)}n.renameSync(o,e),l(this.directory)}finally{try{n.unlinkSync(o)}catch{}}}quarantine(t){const r=s.join(this.quarantineDirectory,`${s.basename(t)}.${Date.now()}.${u()}.invalid`);try{return c(t),n.renameSync(t,r),process.platform!=="win32"&&n.chmodSync(r,384),l(this.quarantineDirectory),r}catch{return t}}}export{R as ActiveRunJournal};
@@ -4,6 +4,7 @@ export declare const RECOVERY_CONTINUE_TEXT: "Continue.";
4
4
  export type RunJournalPhase = 'dispatching' | 'running' | 'waiting_approval' | 'recovering' | 'terminal' | 'interrupted_by_upgrade' | 'recovery_failed' | 'aborted';
5
5
  export type RunRecoverySource = 'personal' | 'managed_room' | 'managed_direct' | 'manager_worker';
6
6
  export type ContinueDispatchState = 'none' | 'intent' | 'accepted';
7
+ export type RunDeliveryMode = 'direct' | 'external_owner_queue';
7
8
  export type RunJournalRecord = {
8
9
  schemaVersion: typeof RUN_JOURNAL_SCHEMA_VERSION;
9
10
  daemonInstallationId: string;
@@ -32,6 +33,8 @@ export type RunJournalRecord = {
32
33
  upgradeJobId: string | null;
33
34
  interruptionReason: 'daemon_crash' | 'planned_upgrade' | 'os_restart' | null;
34
35
  continueDispatchState: ContinueDispatchState;
36
+ deliveryMode: RunDeliveryMode;
37
+ externalRunId: string | null;
35
38
  recoveryAttempts: number;
36
39
  /** Non-sensitive stable reason used to replay a pending terminal commit. */
37
40
  recoveryReason?: string | null;
@@ -1 +1 @@
1
- import{isAvailableAgentType as d}from"@shennian/wire";const a=1,m="Continue.",c=new Set(["dispatching","running","waiting_approval","recovering","terminal","interrupted_by_upgrade","recovery_failed","aborted"]),u=new Set(["personal","managed_room","managed_direct","manager_worker"]),p=new Set(["none","intent","accepted"]),g=new Set(["standard","manager"]);function t(n,e){if(typeof n!="string"||!n.trim())throw new Error(`invalid ${e}`);return n}function r(n,e){if(n==null)return null;if(typeof n!="string"||!n.trim())throw new Error(`invalid ${e}`);return n}function s(n,e){const o=t(n,e);if(!Number.isFinite(Date.parse(o)))throw new Error(`invalid ${e}`);return o}function v(n){if(!n||typeof n!="object"||Array.isArray(n))throw new Error("invalid run journal record");const e=n;if(e.schemaVersion!==a)throw new Error(`unsupported run journal schema: ${String(e.schemaVersion)}`);const o=t(e.agentType,"agentType");if(!d(o))throw new Error("invalid agentType");if(!c.has(e.phase))throw new Error("invalid phase");if(!u.has(e.source))throw new Error("invalid source");if(!p.has(e.continueDispatchState))throw new Error("invalid continueDispatchState");if(!g.has(e.sessionMode))throw new Error("invalid sessionMode");if(!Number.isSafeInteger(e.lastObservedEventSeq)||Number(e.lastObservedEventSeq)<-1)throw new Error("invalid lastObservedEventSeq");if(!Number.isSafeInteger(e.recoveryAttempts)||Number(e.recoveryAttempts)<0)throw new Error("invalid recoveryAttempts");if(e.activationHopCount!=null&&(!Number.isSafeInteger(e.activationHopCount)||Number(e.activationHopCount)<0))throw new Error("invalid activationHopCount");if(e.source==="managed_room"&&(!e.roomContextRegistryKey||!e.activationCauseMessageId||!e.rootCauseMessageId||!e.activationSequence||e.activationHopCount==null||!e.managedPolicyVersion||!e.managedRuntimePolicyHash))throw new Error("managed room recovery identity is incomplete");if(e.source==="manager_worker"&&(!e.parentSessionId||!e.managedRuntimePolicyHash))throw new Error("manager worker recovery identity is incomplete");const i=e.interruptionReason;if(i!=null&&i!=="daemon_crash"&&i!=="planned_upgrade"&&i!=="os_restart")throw new Error("invalid interruptionReason");return{schemaVersion:a,daemonInstallationId:t(e.daemonInstallationId,"daemonInstallationId"),sessionId:t(e.sessionId,"sessionId"),runId:t(e.runId,"runId"),clientMessageId:t(e.clientMessageId,"clientMessageId"),agentType:o,sessionMode:e.sessionMode,workDir:t(e.workDir,"workDir"),agentSessionId:r(e.agentSessionId,"agentSessionId"),modelId:r(e.modelId,"modelId"),reasoningEffort:r(e.reasoningEffort,"reasoningEffort"),nativeSessionVisibility:e.nativeSessionVisibility==="internal"?"internal":"user",providerRunId:r(e.providerRunId,"providerRunId"),lastObservedEventSeq:Number(e.lastObservedEventSeq),phase:e.phase,source:e.source,parentSessionId:r(e.parentSessionId,"parentSessionId"),roomContextRegistryKey:r(e.roomContextRegistryKey,"roomContextRegistryKey"),activationCauseMessageId:r(e.activationCauseMessageId,"activationCauseMessageId"),rootCauseMessageId:r(e.rootCauseMessageId,"rootCauseMessageId"),activationSequence:r(e.activationSequence,"activationSequence"),activationHopCount:e.activationHopCount==null?null:Number(e.activationHopCount),managedPolicyVersion:r(e.managedPolicyVersion,"managedPolicyVersion"),managedRuntimePolicyHash:r(e.managedRuntimePolicyHash,"managedRuntimePolicyHash"),upgradeJobId:r(e.upgradeJobId,"upgradeJobId"),interruptionReason:i,continueDispatchState:e.continueDispatchState,recoveryAttempts:Number(e.recoveryAttempts),recoveryReason:r(e.recoveryReason,"recoveryReason"),createdAt:s(e.createdAt,"createdAt"),updatedAt:s(e.updatedAt,"updatedAt")}}function I(n){return n==="dispatching"||n==="running"||n==="waiting_approval"||n==="recovering"}export{m as RECOVERY_CONTINUE_TEXT,a as RUN_JOURNAL_SCHEMA_VERSION,I as isRecoverableJournalPhase,v as parseRunJournalRecord};
1
+ import{isAvailableAgentType as d}from"@shennian/wire";const a=1,m="Continue.",u=new Set(["dispatching","running","waiting_approval","recovering","terminal","interrupted_by_upgrade","recovery_failed","aborted"]),c=new Set(["personal","managed_room","managed_direct","manager_worker"]),l=new Set(["none","intent","accepted"]),p=new Set(["standard","manager"]);function t(n,e){if(typeof n!="string"||!n.trim())throw new Error(`invalid ${e}`);return n}function r(n,e){if(n==null)return null;if(typeof n!="string"||!n.trim())throw new Error(`invalid ${e}`);return n}function s(n,e){const o=t(n,e);if(!Number.isFinite(Date.parse(o)))throw new Error(`invalid ${e}`);return o}function v(n){if(!n||typeof n!="object"||Array.isArray(n))throw new Error("invalid run journal record");const e=n;if(e.schemaVersion!==a)throw new Error(`unsupported run journal schema: ${String(e.schemaVersion)}`);const o=t(e.agentType,"agentType");if(!d(o))throw new Error("invalid agentType");if(!u.has(e.phase))throw new Error("invalid phase");if(!c.has(e.source))throw new Error("invalid source");if(!l.has(e.continueDispatchState))throw new Error("invalid continueDispatchState");if(e.deliveryMode!=null&&e.deliveryMode!=="direct"&&e.deliveryMode!=="external_owner_queue")throw new Error("invalid deliveryMode");if(!p.has(e.sessionMode))throw new Error("invalid sessionMode");if(!Number.isSafeInteger(e.lastObservedEventSeq)||Number(e.lastObservedEventSeq)<-1)throw new Error("invalid lastObservedEventSeq");if(!Number.isSafeInteger(e.recoveryAttempts)||Number(e.recoveryAttempts)<0)throw new Error("invalid recoveryAttempts");if(e.activationHopCount!=null&&(!Number.isSafeInteger(e.activationHopCount)||Number(e.activationHopCount)<0))throw new Error("invalid activationHopCount");if(e.source==="managed_room"&&(!e.roomContextRegistryKey||!e.activationCauseMessageId||!e.rootCauseMessageId||!e.activationSequence||e.activationHopCount==null||!e.managedPolicyVersion||!e.managedRuntimePolicyHash))throw new Error("managed room recovery identity is incomplete");if(e.source==="manager_worker"&&(!e.parentSessionId||!e.managedRuntimePolicyHash))throw new Error("manager worker recovery identity is incomplete");const i=e.interruptionReason;if(i!=null&&i!=="daemon_crash"&&i!=="planned_upgrade"&&i!=="os_restart")throw new Error("invalid interruptionReason");return{schemaVersion:a,daemonInstallationId:t(e.daemonInstallationId,"daemonInstallationId"),sessionId:t(e.sessionId,"sessionId"),runId:t(e.runId,"runId"),clientMessageId:t(e.clientMessageId,"clientMessageId"),agentType:o,sessionMode:e.sessionMode,workDir:t(e.workDir,"workDir"),agentSessionId:r(e.agentSessionId,"agentSessionId"),modelId:r(e.modelId,"modelId"),reasoningEffort:r(e.reasoningEffort,"reasoningEffort"),nativeSessionVisibility:e.nativeSessionVisibility==="internal"?"internal":"user",providerRunId:r(e.providerRunId,"providerRunId"),lastObservedEventSeq:Number(e.lastObservedEventSeq),phase:e.phase,source:e.source,parentSessionId:r(e.parentSessionId,"parentSessionId"),roomContextRegistryKey:r(e.roomContextRegistryKey,"roomContextRegistryKey"),activationCauseMessageId:r(e.activationCauseMessageId,"activationCauseMessageId"),rootCauseMessageId:r(e.rootCauseMessageId,"rootCauseMessageId"),activationSequence:r(e.activationSequence,"activationSequence"),activationHopCount:e.activationHopCount==null?null:Number(e.activationHopCount),managedPolicyVersion:r(e.managedPolicyVersion,"managedPolicyVersion"),managedRuntimePolicyHash:r(e.managedRuntimePolicyHash,"managedRuntimePolicyHash"),upgradeJobId:r(e.upgradeJobId,"upgradeJobId"),interruptionReason:i,continueDispatchState:e.continueDispatchState,deliveryMode:e.deliveryMode==="external_owner_queue"?"external_owner_queue":"direct",externalRunId:r(e.externalRunId,"externalRunId"),recoveryAttempts:Number(e.recoveryAttempts),recoveryReason:r(e.recoveryReason,"recoveryReason"),createdAt:s(e.createdAt,"createdAt"),updatedAt:s(e.updatedAt,"updatedAt")}}function y(n){return n==="dispatching"||n==="running"||n==="waiting_approval"||n==="recovering"}export{m as RECOVERY_CONTINUE_TEXT,a as RUN_JOURNAL_SCHEMA_VERSION,y as isRecoverableJournalPhase,v as parseRunJournalRecord};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shennian",
3
- "version": "0.4.27",
3
+ "version": "0.4.29",
4
4
  "description": "Shennian — AI Agent Control Plane CLI",
5
5
  "type": "module",
6
6
  "bin": {