shennian 0.3.47 → 0.3.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/publish-build-manifest.json +6 -6
- package/dist/src/native-fusion/local-repository.d.ts +12 -2
- package/dist/src/native-fusion/local-repository.js +2 -2
- package/dist/src/native-fusion/service.d.ts +3 -1
- package/dist/src/native-fusion/service.js +2 -2
- package/dist/src/native-fusion/types.d.ts +3 -2
- package/dist/src/session/handlers/chat.js +2 -2
- package/package.json +1 -1
|
@@ -321,8 +321,8 @@
|
|
|
321
321
|
},
|
|
322
322
|
{
|
|
323
323
|
"file": "src/native-fusion/local-repository.js",
|
|
324
|
-
"beforeBytes":
|
|
325
|
-
"afterBytes":
|
|
324
|
+
"beforeBytes": 20859,
|
|
325
|
+
"afterBytes": 10730
|
|
326
326
|
},
|
|
327
327
|
{
|
|
328
328
|
"file": "src/native-fusion/native-source-deletions.js",
|
|
@@ -346,8 +346,8 @@
|
|
|
346
346
|
},
|
|
347
347
|
{
|
|
348
348
|
"file": "src/native-fusion/service.js",
|
|
349
|
-
"beforeBytes":
|
|
350
|
-
"afterBytes":
|
|
349
|
+
"beforeBytes": 25207,
|
|
350
|
+
"afterBytes": 11823
|
|
351
351
|
},
|
|
352
352
|
{
|
|
353
353
|
"file": "src/native-fusion/session-header.js",
|
|
@@ -521,8 +521,8 @@
|
|
|
521
521
|
},
|
|
522
522
|
{
|
|
523
523
|
"file": "src/session/handlers/chat.js",
|
|
524
|
-
"beforeBytes":
|
|
525
|
-
"afterBytes":
|
|
524
|
+
"beforeBytes": 34167,
|
|
525
|
+
"afterBytes": 14496
|
|
526
526
|
},
|
|
527
527
|
{
|
|
528
528
|
"file": "src/session/handlers/control.js",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { CanonicalSessionStore } from '../session/canonical-store.js';
|
|
2
2
|
import { listSessionRecords, recordSession } from '../session/store.js';
|
|
3
|
-
import type { ParsedNativeEvent } from './types.js';
|
|
3
|
+
import type { ManagedSuppressionWindow, ParsedNativeEvent, PendingManagedClaim } from './types.js';
|
|
4
4
|
import { type LocalToolDetail } from '../session/tool-detail-store.js';
|
|
5
5
|
import { NativeSourceDeletionStore } from './native-source-deletions.js';
|
|
6
6
|
declare const SCHEMA_VERSION: 1;
|
|
@@ -26,10 +26,11 @@ type SourceBinding = {
|
|
|
26
26
|
};
|
|
27
27
|
export type NativeLocalCommitResult = {
|
|
28
28
|
sessionId: string;
|
|
29
|
-
messageId: string;
|
|
29
|
+
messageId: string | null;
|
|
30
30
|
bodyRevision: number;
|
|
31
31
|
duplicate: boolean;
|
|
32
32
|
claimed: boolean;
|
|
33
|
+
suppressed: boolean;
|
|
33
34
|
};
|
|
34
35
|
type RecordSessionInput = Parameters<typeof recordSession>[0];
|
|
35
36
|
export declare class NativeFusionLocalRepository {
|
|
@@ -46,6 +47,13 @@ export declare class NativeFusionLocalRepository {
|
|
|
46
47
|
});
|
|
47
48
|
commitEvents(events: readonly ParsedNativeEvent[]): NativeLocalCommitResult[];
|
|
48
49
|
commitEvent(event: ParsedNativeEvent): NativeLocalCommitResult;
|
|
50
|
+
listManagedClaims(): PendingManagedClaim[];
|
|
51
|
+
listManagedSuppressions(): ManagedSuppressionWindow[];
|
|
52
|
+
upsertManagedClaim(claim: PendingManagedClaim): void;
|
|
53
|
+
updateManagedClaimSource(sessionId: string, agentType: string, sourceSessionKey: string): void;
|
|
54
|
+
removeManagedClaim(canonicalMessageId: string): void;
|
|
55
|
+
completeManagedClaim(canonicalMessageId: string, suppression?: ManagedSuppressionWindow): void;
|
|
56
|
+
removeManagedSuppression(agentType: string, sourceSessionKey: string): void;
|
|
49
57
|
listSourceBindings(): SourceBinding[];
|
|
50
58
|
purgeSession(sessionId: string): void;
|
|
51
59
|
isSourceDeleted(agentType: ParsedNativeEvent['agentType'], sourceSessionKey: string): boolean;
|
|
@@ -56,6 +64,8 @@ export declare class NativeFusionLocalRepository {
|
|
|
56
64
|
private canonicalStore;
|
|
57
65
|
private rootDir;
|
|
58
66
|
private sourcesFile;
|
|
67
|
+
private managedEchoStateFile;
|
|
68
|
+
private readManagedEchoState;
|
|
59
69
|
private ledgerFile;
|
|
60
70
|
}
|
|
61
71
|
export {};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import u from"node:crypto";import a from"node:fs";import g from"node:path";import{resolvePersonalSyncV1Path as C}from"../personal-sync/paths.js";import{getCanonicalSessionStore as O,getDaemonInstallationId as k,listSessionRecords as j,recordSession as K}from"../session/store.js";import{readToolDetail as B,writeToolDetail as H}from"../session/tool-detail-store.js";import{NativeSourceDeletionStore as J}from"./native-source-deletions.js";const I=1;class L extends Error{code="missing_user_boundary";constructor(e){super(`native source ${e} does not start at a visible user boundary`),this.name="MissingUserBoundaryError"}}class R extends Error{code="native_source_deleted";constructor(e){super(`native source ${e} is blocked by a durable deletion tombstone`),this.name="NativeSourceDeletedError"}}class ee{options;constructor(e={}){this.options=e}commitEvents(e){return[...e].sort((o,t)=>o.ts-t.ts).map(o=>this.commitEvent(o))}commitEvent(e){if(A(e),this.isSourceDeleted(e.agentType,e.sourceSessionKey))throw new R(e.sourceSessionKey);const o=this.installationId(),t=this.readSources(),i=M(e.agentType,e.sourceSessionKey),r=t.sources[i],l=W(e),d=this.ledgerFile(e.agentType,e.sourceEventKey),n=a.existsSync(d)?E(d):null;if(n&&n.daemonInstallationId!==o)throw new Error("native event ledger belongs to another daemon installation");if(n&&n.eventFingerprint!==l)throw new Error(`native event identity conflict: ${e.sourceEventKey}`);if(!r&&!n&&e.role!=="user")throw new L(e.sourceSessionKey);const y=e.claimSessionId??null,c=r?.sessionId??n?.sessionId??y??V(o,e.agentType,e.sourceSessionKey);if(y&&r&&r.sessionId!==y)throw new Error(`managed claim session conflicts with source binding ${e.sourceSessionKey}`);const p=this.canonicalStore(),m=e.claimCanonicalMessageId??U(o,e.agentType,e.canonicalMessageKey??e.sourceEventKey);let f=p.getManifest(c)?.bodyRevision??0,b=!!n;const S=!!e.claimCanonicalMessageId;if(S){const h=p.getMessageState(c,m);if(!h||h.tombstoned)throw new Error(`managed echo claim target is not durable: ${m}`)}if(!n&&!S){const h=e.canonicalMutation==="upsert"?p.getMessageState(c,m):null,$={id:m,sessionId:c,role:e.role,ts:h?.message?.ts??e.ts,payload:q(h?.message?.payload,e.payload)},T=e.canonicalMutation==="upsert"?p.upsert(c,$):p.append(c,$);if(f=T.bodyRevision,b=T.duplicate,e.localToolDetail){const F=this.options.writeLocalToolDetail??H,_=this.options.readLocalToolDetail??B,N=P(_(c,e.localToolDetail.toolCallId),e.localToolDetail);F({sessionId:c,...N,ts:e.ts,detailRef:{toolIndex:0}})}}n||this.recordIndex(e,c);const w=new Date().toISOString();return n||D(d,{schemaVersion:I,daemonInstallationId:o,sessionId:c,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,sourceEventKey:e.sourceEventKey,eventFingerprint:l,canonicalMessageId:m,canonicalRevision:f,cursor:e.cursor??null,mappedBy:S?"managed_echo_claim":e.sourceMode,eventTs:e.ts,createdAt:w}),t.sources[i]={schemaVersion:I,daemonInstallationId:o,sessionId:c,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,sourceMode:n?r?.sourceMode??e.sourceMode:e.sourceMode,lastCursor:n?r?.lastCursor??n.cursor:e.cursor??r?.lastCursor??null,bodyRevision:f,createdAt:r?.createdAt??w,updatedAt:w},D(this.sourcesFile(),t),{sessionId:c,messageId:m,bodyRevision:f,duplicate:b,claimed:S}}listSourceBindings(){return Object.values(this.readSources().sources).filter(e=>!this.isSourceDeleted(e.agentType,e.sourceSessionKey))}purgeSession(e){if(!e)throw new Error("sessionId is required");const o=this.readSources();let t=!1;for(const[r,l]of Object.entries(o.sources))l.sessionId===e&&(delete o.sources[r],t=!0);t&&D(this.sourcesFile(),o);const i=g.join(this.rootDir(),"ledger");if(a.existsSync(i))for(const r of a.readdirSync(i,{withFileTypes:!0})){if(!r.isFile()||!r.name.endsWith(".json"))continue;const l=g.join(i,r.name),d=E(l);if(d.daemonInstallationId!==this.installationId())throw new Error("native event ledger belongs to another daemon installation");d.sessionId===e&&a.unlinkSync(l)}}isSourceDeleted(e,o){return(this.options.deletionStore??new J({rootDir:g.basename(this.rootDir())==="native-fusion"?g.dirname(this.rootDir()):this.rootDir(),daemonInstallationId:this.installationId()})).has(e,o)}observeNativeTitle(e,o,t){const i=t.trim().slice(0,120);if(!i)return!1;const r=this.readSources().sources[M(e,o)];if(!r||this.isSourceDeleted(e,o))return!1;const l=(this.options.listLocalSessions??j)().find(y=>y.sessionId===r.sessionId);if(l?.titleLockedByUser||l?.titleSource==="agent_native"&&l.title===i)return!1;const d=u.createHash("sha256").update(`${e}\0${o}\0${i}`).digest("hex");return(this.options.recordLocalSession??K)({sessionId:r.sessionId,agentType:r.agentType,workDir:l?.workDir??"",agentSessionId:o,title:i,titleSource:"agent_native",titleOriginEventId:`agent-native:${d}`}),!0}recordIndex(e,o){(this.options.recordLocalSession??K)({sessionId:o,agentType:e.agentType,workDir:e.workDir??"",agentSessionId:e.sourceSessionKey,modelId:e.modelId??null,status:e.terminal?"completed":"active",lastActivityAt:new Date(e.ts).toISOString(),lastMessagePreview:e.role==="user"?x(e.payload):void 0,title:e.title?.trim()||(e.role==="user"?x(e.payload)??"":void 0),titleSource:e.title?.trim()?e.titleSource??"first_user_fallback":"first_user_fallback",titleOriginEventId:e.sourceEventKey})}readSources(){const e=this.sourcesFile();if(!a.existsSync(e))return{schemaVersion:I,daemonInstallationId:this.installationId(),sources:{}};const o=E(e);if(o.schemaVersion!==I||o.daemonInstallationId!==this.installationId()||!o.sources)throw new Error("invalid native source binding store");for(const t of Object.values(o.sources))if(t.daemonInstallationId!==this.installationId())throw new Error("native source binding belongs to another daemon installation");return o}installationId(){return this.options.daemonInstallationId??k()}canonicalStore(){return this.options.canonicalStore??O()}rootDir(){return this.options.rootDir??C("native-fusion")}sourcesFile(){return g.join(this.rootDir(),"sources.json")}ledgerFile(e,o){const t=u.createHash("sha256").update(`${e}\0${o}`).digest("hex");return g.join(this.rootDir(),"ledger",`${t}.json`)}}function A(s){if(!s.agentType||!s.sourceSessionKey||!s.sourceEventKey||!s.payload)throw new Error("invalid native event");if(!Number.isFinite(s.ts))throw new Error("invalid native event timestamp")}function M(s,e){return u.createHash("sha256").update(`${s}\0${e}`).digest("hex")}function V(s,e,o){return`native-session-${u.createHash("sha256").update(`${s}\0${e}\0${o}`).digest("hex").slice(0,32)}`}function U(s,e,o){return`native-${u.createHash("sha256").update(`${s}\0${e}\0${o}`).digest("hex").slice(0,32)}`}function W(s){return u.createHash("sha256").update(JSON.stringify({agentType:s.agentType,sourceSessionKey:s.sourceSessionKey,sourceEventKey:s.sourceEventKey,role:s.role,ts:s.ts,payload:s.payload,claimCanonicalMessageId:s.claimCanonicalMessageId??null,claimSessionId:s.claimSessionId??null,canonicalMessageKey:s.canonicalMessageKey??null,canonicalMutation:s.canonicalMutation??"append"})).digest("hex")}function q(s,e){if(!s)return e;try{const o=JSON.parse(s),t=JSON.parse(e);if(o.type!=="tool"||t.type!=="tool"||o.toolCallId!==t.toolCallId)return e;const i=typeof t.name=="string"?t.name:"",r=/(?:_output|_result)$/.test(i);return JSON.stringify({...o,...t,...r&&typeof o.name=="string"?{name:o.name}:{}})}catch{return e}}function P(s,e){if(!s||s.toolCallId!==e.toolCallId)return e;const o=/(?:_output|_result)$/.test(e.name);return{...e,name:o?s.name:e.name,...e.args===void 0&&s.args!==void 0?{args:s.args}:{},...e.result===void 0&&s.result!==void 0?{result:s.result}:{}}}function x(s){const e=s.replace(/\s+/g," ").trim();return e?e.length>120?`${e.slice(0,120)}...`:e:null}function D(s,e){const o=g.dirname(s);a.mkdirSync(o,{recursive:!0,mode:448});const t=`${s}.tmp-${process.pid}-${u.randomBytes(6).toString("hex")}`;let i;try{i=a.openSync(t,"wx",384),a.writeFileSync(i,`${JSON.stringify(e,null,2)}
|
|
2
|
-
`,"utf8"),
|
|
1
|
+
import p from"node:crypto";import l from"node:fs";import m from"node:path";import{resolvePersonalSyncV1Path as k}from"../personal-sync/paths.js";import{getCanonicalSessionStore as A,getDaemonInstallationId as v,listSessionRecords as B,recordSession as $}from"../session/store.js";import{readToolDetail as H,writeToolDetail as J}from"../session/tool-detail-store.js";import{NativeSourceDeletionStore as L}from"./native-source-deletions.js";const S=1;class R extends Error{code="missing_user_boundary";constructor(e){super(`native source ${e} does not start at a visible user boundary`),this.name="MissingUserBoundaryError"}}class V extends Error{code="native_source_deleted";constructor(e){super(`native source ${e} is blocked by a durable deletion tombstone`),this.name="NativeSourceDeletedError"}}class oe{options;constructor(e={}){this.options=e}commitEvents(e){return[...e].sort((s,o)=>s.ts-o.ts).map(s=>this.commitEvent(s))}commitEvent(e){if(q(e),this.isSourceDeleted(e.agentType,e.sourceSessionKey))throw new V(e.sourceSessionKey);const s=this.installationId(),o=this.readSources(),i=b(e.agentType,e.sourceSessionKey),r=o.sources[i],a=W(e),c=this.ledgerFile(e.agentType,e.sourceEventKey),n=l.existsSync(c)?w(c):null;if(n&&n.daemonInstallationId!==s)throw new Error("native event ledger belongs to another daemon installation");if(n&&n.eventFingerprint!==a)throw new Error(`native event identity conflict: ${e.sourceEventKey}`);if(!r&&!n&&e.role!=="user")throw new R(e.sourceSessionKey);const g=e.claimSessionId??null,d=r?.sessionId??n?.sessionId??g??U(s,e.agentType,e.sourceSessionKey);if(g&&r&&r.sessionId!==g)throw new Error(`managed claim session conflicts with source binding ${e.sourceSessionKey}`);const y=this.canonicalStore(),f=e.localDisposition==="managed_echo_suppressed"||n?.mappedBy==="managed_echo_suppressed",h=n?.canonicalMessageId??(f?null:e.claimCanonicalMessageId??P(s,e.agentType,e.canonicalMessageKey??e.sourceEventKey));let E=y.getManifest(d)?.bodyRevision??0,T=!!n;const M=!f&&!!e.claimCanonicalMessageId;if(M){if(!h)throw new Error("managed echo claim requires a canonical message");const I=y.getMessageState(d,h);if(!I||I.tombstoned)throw new Error(`managed echo claim target is not durable: ${h}`)}if(!n&&!M&&!f){if(!h)throw new Error("native event requires a canonical message");const I=e.canonicalMutation==="upsert"?y.getMessageState(d,h):null,K={id:h,sessionId:d,role:e.role,ts:I?.message?.ts??e.ts,payload:z(I?.message?.payload,e.payload)},F=e.canonicalMutation==="upsert"?y.upsert(d,K):y.append(d,K);if(E=F.bodyRevision,T=F.duplicate,e.localToolDetail){const C=this.options.writeLocalToolDetail??J,j=this.options.readLocalToolDetail??H,N=G(j(d,e.localToolDetail.toolCallId),e.localToolDetail);C({sessionId:d,...N,ts:e.ts,detailRef:{toolIndex:0}})}}!n&&!f&&this.recordIndex(e,d);const D=new Date().toISOString();return n||u(c,{schemaVersion:S,daemonInstallationId:s,sessionId:d,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,sourceEventKey:e.sourceEventKey,eventFingerprint:a,canonicalMessageId:h,canonicalRevision:E,cursor:e.cursor??null,mappedBy:f?"managed_echo_suppressed":M?"managed_echo_claim":e.sourceMode,eventTs:e.ts,createdAt:D}),o.sources[i]={schemaVersion:S,daemonInstallationId:s,sessionId:d,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,sourceMode:n?r?.sourceMode??e.sourceMode:e.sourceMode,lastCursor:n?r?.lastCursor??n.cursor:e.cursor??r?.lastCursor??null,bodyRevision:E,createdAt:r?.createdAt??D,updatedAt:D},u(this.sourcesFile(),o),{sessionId:d,messageId:h,bodyRevision:E,duplicate:T,claimed:M,suppressed:f}}listManagedClaims(){return Object.values(this.readManagedEchoState().claims)}listManagedSuppressions(){return Object.values(this.readManagedEchoState().suppressions)}upsertManagedClaim(e){x(e);const s=this.readManagedEchoState();s.claims[e.canonicalMessageId]=e,u(this.managedEchoStateFile(),s)}updateManagedClaimSource(e,s,o){const i=this.readManagedEchoState();let r=!1;for(const a of Object.values(i.claims))a.sessionId!==e||a.sourceAgentType!==s||(a.sourceSessionKey=o,r=!0);r&&u(this.managedEchoStateFile(),i)}removeManagedClaim(e){const s=this.readManagedEchoState();s.claims[e]&&(delete s.claims[e],u(this.managedEchoStateFile(),s))}completeManagedClaim(e,s){const o=this.readManagedEchoState();delete o.claims[e],s&&(_(s),o.suppressions[b(s.agentType,s.sourceSessionKey)]=s),u(this.managedEchoStateFile(),o)}removeManagedSuppression(e,s){const o=this.readManagedEchoState(),i=b(e,s);o.suppressions[i]&&(delete o.suppressions[i],u(this.managedEchoStateFile(),o))}listSourceBindings(){return Object.values(this.readSources().sources).filter(e=>!this.isSourceDeleted(e.agentType,e.sourceSessionKey))}purgeSession(e){if(!e)throw new Error("sessionId is required");const s=this.readSources();let o=!1;for(const[c,n]of Object.entries(s.sources))n.sessionId===e&&(delete s.sources[c],o=!0);o&&u(this.sourcesFile(),s);const i=this.readManagedEchoState();let r=!1;for(const[c,n]of Object.entries(i.claims))n.sessionId===e&&(delete i.claims[c],r=!0);for(const[c,n]of Object.entries(i.suppressions))n.sessionId===e&&(delete i.suppressions[c],r=!0);r&&u(this.managedEchoStateFile(),i);const a=m.join(this.rootDir(),"ledger");if(l.existsSync(a))for(const c of l.readdirSync(a,{withFileTypes:!0})){if(!c.isFile()||!c.name.endsWith(".json"))continue;const n=m.join(a,c.name),g=w(n);if(g.daemonInstallationId!==this.installationId())throw new Error("native event ledger belongs to another daemon installation");g.sessionId===e&&l.unlinkSync(n)}}isSourceDeleted(e,s){return(this.options.deletionStore??new L({rootDir:m.basename(this.rootDir())==="native-fusion"?m.dirname(this.rootDir()):this.rootDir(),daemonInstallationId:this.installationId()})).has(e,s)}observeNativeTitle(e,s,o){const i=o.trim().slice(0,120);if(!i)return!1;const r=this.readSources().sources[b(e,s)];if(!r||this.isSourceDeleted(e,s))return!1;const a=(this.options.listLocalSessions??B)().find(g=>g.sessionId===r.sessionId);if(a?.titleLockedByUser||a?.titleSource==="agent_native"&&a.title===i)return!1;const c=p.createHash("sha256").update(`${e}\0${s}\0${i}`).digest("hex");return(this.options.recordLocalSession??$)({sessionId:r.sessionId,agentType:r.agentType,workDir:a?.workDir??"",agentSessionId:s,title:i,titleSource:"agent_native",titleOriginEventId:`agent-native:${c}`}),!0}recordIndex(e,s){(this.options.recordLocalSession??$)({sessionId:s,agentType:e.agentType,workDir:e.workDir??"",agentSessionId:e.sourceSessionKey,modelId:e.modelId??null,status:e.terminal?"completed":"active",lastActivityAt:new Date(e.ts).toISOString(),lastMessagePreview:e.role==="user"?O(e.payload):void 0,title:e.title?.trim()||(e.role==="user"?O(e.payload)??"":void 0),titleSource:e.title?.trim()?e.titleSource??"first_user_fallback":"first_user_fallback",titleOriginEventId:e.sourceEventKey})}readSources(){const e=this.sourcesFile();if(!l.existsSync(e))return{schemaVersion:S,daemonInstallationId:this.installationId(),sources:{}};const s=w(e);if(s.schemaVersion!==S||s.daemonInstallationId!==this.installationId()||!s.sources)throw new Error("invalid native source binding store");for(const o of Object.values(s.sources))if(o.daemonInstallationId!==this.installationId())throw new Error("native source binding belongs to another daemon installation");return s}installationId(){return this.options.daemonInstallationId??v()}canonicalStore(){return this.options.canonicalStore??A()}rootDir(){return this.options.rootDir??k("native-fusion")}sourcesFile(){return m.join(this.rootDir(),"sources.json")}managedEchoStateFile(){return m.join(this.rootDir(),"managed-echo-state.json")}readManagedEchoState(){const e=this.managedEchoStateFile();if(!l.existsSync(e))return{schemaVersion:S,daemonInstallationId:this.installationId(),claims:{},suppressions:{}};const s=w(e);if(s.schemaVersion!==S||s.daemonInstallationId!==this.installationId()||!s.claims||!s.suppressions)throw new Error("invalid managed echo state");for(const o of Object.values(s.claims))x(o);for(const o of Object.values(s.suppressions))_(o);return s}ledgerFile(e,s){const o=p.createHash("sha256").update(`${e}\0${s}`).digest("hex");return m.join(this.rootDir(),"ledger",`${o}.json`)}}function q(t){if(!t.agentType||!t.sourceSessionKey||!t.sourceEventKey||!t.payload)throw new Error("invalid native event");if(!Number.isFinite(t.ts))throw new Error("invalid native event timestamp")}function b(t,e){return p.createHash("sha256").update(`${t}\0${e}`).digest("hex")}function U(t,e,s){return`native-session-${p.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function P(t,e,s){return`native-${p.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function W(t){return p.createHash("sha256").update(JSON.stringify({agentType:t.agentType,sourceSessionKey:t.sourceSessionKey,sourceEventKey:t.sourceEventKey,role:t.role,ts:t.ts,payload:t.payload,canonicalMessageKey:t.canonicalMessageKey??null,canonicalMutation:t.canonicalMutation??"append"})).digest("hex")}function x(t){if(!t.sessionId||!t.canonicalMessageId||!t.textHash||!Number.isFinite(t.createdAt)||!["codex","claude","opencode"].includes(t.sourceAgentType)||!["suppress_following","import_following"].includes(t.managedEchoPolicy))throw new Error("invalid managed echo claim")}function _(t){if(!t.sessionId||!t.sourceSessionKey||!t.claimSourceEventKey||!["codex","claude","opencode"].includes(t.agentType)||!Number.isFinite(t.startedAt)||!Number.isFinite(t.endsAt)||t.endsAt<t.startedAt)throw new Error("invalid managed echo suppression")}function z(t,e){if(!t)return e;try{const s=JSON.parse(t),o=JSON.parse(e);if(s.type!=="tool"||o.type!=="tool"||s.toolCallId!==o.toolCallId)return e;const i=typeof o.name=="string"?o.name:"",r=/(?:_output|_result)$/.test(i);return JSON.stringify({...s,...o,...r&&typeof s.name=="string"?{name:s.name}:{}})}catch{return e}}function G(t,e){if(!t||t.toolCallId!==e.toolCallId)return e;const s=/(?:_output|_result)$/.test(e.name);return{...e,name:s?t.name:e.name,...e.args===void 0&&t.args!==void 0?{args:t.args}:{},...e.result===void 0&&t.result!==void 0?{result:t.result}:{}}}function O(t){const e=t.replace(/\s+/g," ").trim();return e?e.length>120?`${e.slice(0,120)}...`:e:null}function u(t,e){const s=m.dirname(t);l.mkdirSync(s,{recursive:!0,mode:448});const o=`${t}.tmp-${process.pid}-${p.randomBytes(6).toString("hex")}`;let i;try{i=l.openSync(o,"wx",384),l.writeFileSync(i,`${JSON.stringify(e,null,2)}
|
|
2
|
+
`,"utf8"),l.fsyncSync(i),l.closeSync(i),i=void 0,l.renameSync(o,t)}finally{i!==void 0&&l.closeSync(i);try{l.unlinkSync(o)}catch{}}}function w(t){return JSON.parse(l.readFileSync(t,"utf8"))}export{R as MissingUserBoundaryError,oe as NativeFusionLocalRepository,V as NativeSourceDeletedError};
|
|
@@ -28,6 +28,7 @@ export declare class NativeSessionFusionService {
|
|
|
28
28
|
managedEchoPolicy?: 'suppress_following' | 'import_following';
|
|
29
29
|
externalRunId?: string | null;
|
|
30
30
|
}): void;
|
|
31
|
+
cancelManagedSend(canonicalMessageId: string): void;
|
|
31
32
|
noteManagedSourceSession(sessionId: string, agentType: string, sourceSessionKey: string | null): void;
|
|
32
33
|
handleSessionDeleted(sessionId: string): void;
|
|
33
34
|
private ensureActiveScanning;
|
|
@@ -40,5 +41,6 @@ export declare class NativeSessionFusionService {
|
|
|
40
41
|
private refreshExternalObservations;
|
|
41
42
|
private tryClaimManagedEcho;
|
|
42
43
|
private observeExternalLifecycle;
|
|
43
|
-
private
|
|
44
|
+
private suppressionFor;
|
|
45
|
+
private clearSuppression;
|
|
44
46
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
`).trim()}function B(u){return u.split(/[\\/]+/).includes(".codex")}class J{client;repository;timer=null;activeTimer=null;scanPromise=null;queuedTargetFiles=new Set;fullScanQueued=!1;watchEnabled=!1;lifecycleRevision=0;watcher=new b(e=>{this.scanNow(e).catch(s=>{console.error("[native-fusion] watched file scan failed",s)})});pendingClaims=new Map;suppressionWindows=new Map;externalObservations=new Map;constructor(e,s=new C){this.client=e,this.repository=s}start(){this.timer||(this.timer=setInterval(()=>{this.scanNow().catch(e=>{console.error("[native-fusion] periodic scan failed",e)})},x))}stop(){this.lifecycleRevision+=1,this.timer&&clearInterval(this.timer),this.timer=null,this.activeTimer&&clearInterval(this.activeTimer),this.activeTimer=null,this.watchEnabled=!1,this.watcher.stop()}handleConnected(){const e=++this.lifecycleRevision;this.watchEnabled=!0,this.scanNow().catch(s=>{console.error("[native-fusion] initial scan failed",s)}).finally(()=>{this.lifecycleRevision===e&&this.start()})}registerManagedSend(e){if(!e.canonicalMessageId)return;const s=e.sourceAgentType??e.agentType;if(s!=="codex"&&s!=="claude"&&s!=="opencode")return;const t=e.canonicalMessageId;if(this.pendingClaims.set(t,{sessionId:e.sessionId,agentType:e.agentType,sourceAgentType:s,canonicalMessageId:e.canonicalMessageId,text:M(e.text),createdAt:Date.now(),expiresAt:Date.now()+(e.externalRunId?w:A),sourceSessionKey:e.sourceSessionKey??null,managedEchoPolicy:e.managedEchoPolicy??"suppress_following",externalRunId:e.externalRunId??null}),e.externalRunId&&e.sourceSessionKey){const i=`${s}:${e.sourceSessionKey}`,r=this.externalObservations.get(i)??[];r.push({sessionId:e.sessionId,sourceSessionKey:e.sourceSessionKey,runId:e.externalRunId,canonicalMessageId:e.canonicalMessageId,state:"queued",createdAt:Date.now(),lastPublishedAt:Date.now()}),this.externalObservations.set(i,r),this.emitExternalActivity(r[r.length-1],"queued"),this.ensureActiveScanning()}}noteManagedSourceSession(e,s,t){if(t)for(const i of this.pendingClaims.values())i.sessionId===e&&i.sourceAgentType===s&&(i.sourceSessionKey=t)}handleSessionDeleted(e){for(const[s,t]of this.pendingClaims.entries())t.sessionId===e&&this.pendingClaims.delete(s);for(const[s,t]of this.suppressionWindows.entries())t.sessionId===e&&this.suppressionWindows.delete(s);for(const[s,t]of this.externalObservations.entries()){const i=t.filter(r=>r.sessionId!==e);i.length===0?this.externalObservations.delete(s):this.externalObservations.set(s,i)}this.repository.purgeSession(e)}ensureActiveScanning(){this.activeTimer||(this.activeTimer=setInterval(()=>{if(this.externalObservations.size===0){this.activeTimer&&clearInterval(this.activeTimer),this.activeTimer=null;return}this.scanNow().catch(e=>{console.error("[native-fusion] active observation scan failed",e)})},$),this.scanNow().catch(()=>{}))}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){this.client.sendAgentEvent({type:"event",event:"agent",id:`external-owner-${e.runId}-final`,payload:{state:"final",sessionId:e.sessionId,runId:e.runId,seq:2,owner:"external",ownerLabel:"Codex Desktop"}})}pruneState(){const e=Date.now();for(const[s,t]of this.pendingClaims.entries())t.expiresAt<=e&&this.pendingClaims.delete(s);for(const[s,t]of this.suppressionWindows.entries())t.endsAt<=e&&this.suppressionWindows.delete(s)}async scanNow(e){if(this.client.getState()==="connected"){if(this.scanPromise){const s=this.scanPromise;if(e)for(const t of e)this.queuedTargetFiles.add(t);else this.fullScanQueued=!0;if(await s,this.fullScanQueued)return this.fullScanQueued=!1,this.queuedTargetFiles.clear(),this.scanNow();if(this.queuedTargetFiles.size>0){const t=[...this.queuedTargetFiles];return this.queuedTargetFiles.clear(),this.scanNow(t)}return}return this.scanPromise=this.runScanLoop(e).finally(()=>{this.scanPromise=null}),this.scanPromise}}async runScanLoop(e){let s=e?[...new Set(e)]:void 0;for(;;){if(await this.runScan(s),this.fullScanQueued){this.fullScanQueued=!1,this.queuedTargetFiles.clear(),s=void 0;continue}if(this.queuedTargetFiles.size>0){s=[...this.queuedTargetFiles],this.queuedTargetFiles.clear();continue}return}}async runScan(e){this.pruneState(),this.refreshExternalObservations();const s=W(),t={...s.files},i=[],r=new Map,h=e?[...e]:[...N(),...K(),..._()];!e&&this.watchEnabled&&this.watcher.refresh(h);for(const n of h){if(!T.existsSync(n))continue;const l=T.statSync(n),v=B(n),f=n.endsWith(".opencode-session.json"),d=O(n);if(d.status==="pending")continue;let a=s.files[n];const c=`${String(l.dev)}:${String(l.ino)}:${String(l.birthtimeMs)}`;let y=!a;if(a&&!f&&(l.size<a.offset||a.fileIdentity&&a.fileIdentity!==c)&&(a=void 0,y=!0),!f&&d.sources.length>0&&d.sources.every(o=>this.repository.isSourceDeleted?.(o.agentType,o.sourceSessionKey))){t[n]={...a??{daemonInstallationId:s.daemonInstallationId,classification:d.createdAtMs>=s.fusionEpochMs?"live":"historical",awaitingUserBoundary:!0,sessionCreatedAtMs:d.createdAtMs,sourceBoundaryEstablished:{}},offset:l.size,mtimeMs:l.mtimeMs,fileIdentity:c};continue}if(!a)a={daemonInstallationId:s.daemonInstallationId,offset:0,mtimeMs:l.mtimeMs,classification:d.createdAtMs>=s.fusionEpochMs?"live":"historical",awaitingUserBoundary:!0,sessionCreatedAtMs:d.createdAtMs,sourceBoundaryEstablished:{}};else if(f&&a.mtimeMs===l.mtimeMs)continue;const m=f?0:a.offset,p=f?q(n,m):v?k(n,m):R(n,m);for(const o of p.events)this.repository.isSourceDeleted?.(o.agentType,o.sourceSessionKey)||o.title?.trim()&&o.titleSource==="agent_native"&&r.set(`${o.agentType}:${o.sourceSessionKey}`,o);let S,g;if(f){const o=z(p.events,a,s.fusionEpochMs,y,l.mtimeMs,p.nextOffset);S=o.events,g=o.cursor}else if(y&&a.classification==="historical")S=[],g={...a,offset:p.nextOffset,mtimeMs:l.mtimeMs,awaitingUserBoundary:!0};else{const o=U(p.events,a,p.nextOffset,l.mtimeMs);S=o.events,g=o.cursor}g.fileIdentity=c,t[n]=g;for(const o of S){if(this.repository.isSourceDeleted?.(o.agentType,o.sourceSessionKey))continue;const I=this.tryClaimManagedEcho(o);if(I){i.push(I);continue}this.isSuppressed(o)||i.push(o)}}this.repository.commitEvents(i);for(const n of r.values())this.repository.observeNativeTitle(n.agentType,n.sourceSessionKey,n.title??"");for(const n of this.repository.listSourceBindings()){if(n.agentType!=="codex")continue;const l=P(n.sourceSessionKey);l&&this.repository.observeNativeTitle("codex",n.sourceSessionKey,l)}for(const n of i)this.observeExternalLifecycle(n);s.files=t,F(s),await E(this.client).catch(n=>{console.error("[native-fusion] index projection sync failed; retained for retry",n)})}refreshExternalObservations(){const e=Date.now();for(const[s,t]of this.externalObservations.entries()){const i=t.filter(h=>e-h.createdAt<w);if(i.length===0){this.externalObservations.delete(s);continue}this.externalObservations.set(s,i);const r=i[0];e-r.lastPublishedAt<D||(r.lastPublishedAt=e,this.emitExternalActivity(r,r.state))}}tryClaimManagedEcho(e){const s=Date.now();for(const[t,i]of this.pendingClaims.entries()){if(i.sourceAgentType!==e.agentType||i.expiresAt<=s||e.role!=="user"||!i.sourceSessionKey||i.sourceSessionKey!==e.sourceSessionKey)continue;const r=i.externalRunId?w:A;if(!(Math.abs(e.ts-i.createdAt)>r)&&M(e.payload)===i.text)return this.pendingClaims.delete(t),i.managedEchoPolicy==="suppress_following"&&this.suppressionWindows.set(`${e.agentType}:${e.sourceSessionKey}`,{sessionId:i.sessionId,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,startedAt:e.ts,endsAt:Math.max(s,e.ts)+L}),{...e,sourceMode:"managed_echo_claim",claimCanonicalMessageId:i.canonicalMessageId,claimSessionId:i.sessionId,managedEchoPolicy:i.managedEchoPolicy}}return null}observeExternalLifecycle(e){const s=`${e.agentType}:${e.sourceSessionKey}`,t=this.externalObservations.get(s);if(!t?.length)return;const i=t[0];e.claimCanonicalMessageId===i.canonicalMessageId&&i.state==="queued"&&(i.state="running",i.lastPublishedAt=Date.now(),this.emitExternalActivity(i,"running")),!(!e.terminal||i.state!=="running")&&(this.emitExternalTerminal(i),t.shift(),t.length===0?this.externalObservations.delete(s):(this.externalObservations.set(s,t),t[0].lastPublishedAt=Date.now(),this.emitExternalActivity(t[0],"queued")))}isSuppressed(e){const s=`${e.agentType}:${e.sourceSessionKey}`,t=this.suppressionWindows.get(s);return!t||e.ts<t.startedAt?!1:e.ts>t.endsAt?(this.suppressionWindows.delete(s),!1):e.role==="user"?(this.suppressionWindows.delete(s),!1):!0}}function U(u,e,s,t){if(!e.awaitingUserBoundary)return{events:u,cursor:{...e,offset:s,mtimeMs:t}};if(u.length===0)return{events:[],cursor:{...e,offset:s,mtimeMs:t,awaitingUserBoundary:!0}};const i=u.findIndex(r=>r.role==="user");return i<0?{events:[],cursor:{...e,mtimeMs:t,awaitingUserBoundary:!0}}:{events:u.slice(i),cursor:{...e,offset:s,mtimeMs:t,awaitingUserBoundary:!1}}}function z(u,e,s,t,i,r){const h={...e.sourceBoundaryEstablished??{}},n=e.eventWatermarkTs??-1,l=new Set(e.eventKeysAtWatermark??[]),v=t?u:u.filter(c=>c.ts>n||c.ts===n&&!l.has(c.sourceEventKey)),f=[];for(const c of v){const y=c.sourceSessionKey;if(h[y]){f.push(c);continue}!(!t||(c.sourceSessionCreatedAtMs??0)>=s)||c.role!=="user"||(h[y]=!0,f.push(c))}const d=u.reduce((c,y)=>Math.max(c,y.ts),n),a=new Set(d===n?e.eventKeysAtWatermark??[]:[]);for(const c of u)c.ts===d&&a.add(c.sourceEventKey);return{events:f,cursor:{...e,offset:r,mtimeMs:i,eventWatermarkTs:d>=0?d:void 0,eventKeysAtWatermark:d>=0?[...a]:void 0,sourceBoundaryEstablished:h}}}export{J as NativeSessionFusionService};
|
|
1
|
+
import A from"node:fs";import C from"node:crypto";import{publishPendingSessionIndexes as _}from"../session/index-publisher.js";import{NativeFusionFileWatcher as N}from"./file-watcher.js";import{NativeFusionLocalRepository as O}from"./local-repository.js";import{listClaudeTranscriptFiles as P,listCodexRolloutFiles as R,listOpenCodeSessionFiles as k,inspectNativeSessionFile as q,lookupCodexThreadName as F,parseClaudeTranscriptChunk as W,parseCodexRolloutChunk as L,parseOpenCodeSessionFile as D}from"./parsers.js";import{loadNativeScannerState as $,saveNativeScannerState as B}from"./state.js";const E=6e4,U=E*2,z=30*6e4,H=2e3,Q=3e4,K=240*6e4;function V(d){return d.replace(/\r\n/g,`
|
|
2
|
+
`).trim()}function j(d){return d.split(/[\\/]+/).includes(".codex")}class ie{client;repository;timer=null;activeTimer=null;scanPromise=null;queuedTargetFiles=new Set;fullScanQueued=!1;watchEnabled=!1;lifecycleRevision=0;watcher=new N(e=>{this.scanNow(e).catch(s=>{console.error("[native-fusion] watched file scan failed",s)})});pendingClaims;suppressionWindows;externalObservations=new Map;constructor(e,s=new O){this.client=e,this.repository=s,this.pendingClaims=new Map((this.repository.listManagedClaims?.()??[]).map(t=>[t.canonicalMessageId,t])),this.suppressionWindows=new Map((this.repository.listManagedSuppressions?.()??[]).map(t=>[T(t.agentType,t.sourceSessionKey),t]))}start(){this.timer||(this.timer=setInterval(()=>{this.scanNow().catch(e=>{console.error("[native-fusion] periodic scan failed",e)})},E))}stop(){this.lifecycleRevision+=1,this.timer&&clearInterval(this.timer),this.timer=null,this.activeTimer&&clearInterval(this.activeTimer),this.activeTimer=null,this.watchEnabled=!1,this.watcher.stop()}handleConnected(){const e=++this.lifecycleRevision;this.watchEnabled=!0,this.scanNow().catch(s=>{console.error("[native-fusion] initial scan failed",s)}).finally(()=>{this.lifecycleRevision===e&&this.start()})}registerManagedSend(e){if(!e.canonicalMessageId)return;const s=e.sourceAgentType??e.agentType;if(s!=="codex"&&s!=="claude"&&s!=="opencode")return;const t=e.canonicalMessageId,n=this.pendingClaims.get(t),o=b(e.text);if(n&&(n.sessionId!==e.sessionId||n.sourceAgentType!==s||n.textHash!==o))throw new Error(`managed echo claim identity conflict: ${t}`);const l={sessionId:e.sessionId,agentType:e.agentType,sourceAgentType:s,canonicalMessageId:e.canonicalMessageId,textHash:o,createdAt:n?.createdAt??Date.now(),sourceSessionKey:e.sourceSessionKey??n?.sourceSessionKey??null,managedEchoPolicy:e.managedEchoPolicy??"suppress_following",externalRunId:e.externalRunId??null};if(this.repository.upsertManagedClaim?.(l),this.pendingClaims.set(t,l),e.externalRunId&&e.sourceSessionKey){const f=`${s}:${e.sourceSessionKey}`,h=this.externalObservations.get(f)??[];h.push({sessionId:e.sessionId,sourceSessionKey:e.sourceSessionKey,runId:e.externalRunId,canonicalMessageId:e.canonicalMessageId,state:"queued",createdAt:Date.now(),lastPublishedAt:Date.now()}),this.externalObservations.set(f,h),this.emitExternalActivity(h[h.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(o=>o.sessionId!==e);n.length===0?this.externalObservations.delete(s):this.externalObservations.set(s,n)}this.repository.purgeSession(e)}ensureActiveScanning(){this.activeTimer||(this.activeTimer=setInterval(()=>{if(this.externalObservations.size===0){this.activeTimer&&clearInterval(this.activeTimer),this.activeTimer=null;return}this.scanNow().catch(e=>{console.error("[native-fusion] active observation scan failed",e)})},H),this.scanNow().catch(()=>{}))}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){this.client.sendAgentEvent({type:"event",event:"agent",id:`external-owner-${e.runId}-final`,payload:{state:"final",sessionId:e.sessionId,runId:e.runId,seq:2,owner:"external",ownerLabel:"Codex Desktop"}})}pruneState(){const e=Date.now();for(const[s,t]of this.suppressionWindows.entries())t.endsAt>e||(this.repository.removeManagedSuppression?.(t.agentType,t.sourceSessionKey),this.suppressionWindows.delete(s))}async scanNow(e){if(this.client.getState()==="connected"){if(this.scanPromise){const s=this.scanPromise;if(e)for(const t of e)this.queuedTargetFiles.add(t);else this.fullScanQueued=!0;if(await s,this.fullScanQueued)return this.fullScanQueued=!1,this.queuedTargetFiles.clear(),this.scanNow();if(this.queuedTargetFiles.size>0){const t=[...this.queuedTargetFiles];return this.queuedTargetFiles.clear(),this.scanNow(t)}return}return this.scanPromise=this.runScanLoop(e).finally(()=>{this.scanPromise=null}),this.scanPromise}}async runScanLoop(e){let s=e?[...new Set(e)]:void 0;for(;;){if(await this.runScan(s),this.fullScanQueued){this.fullScanQueued=!1,this.queuedTargetFiles.clear(),s=void 0;continue}if(this.queuedTargetFiles.size>0){s=[...this.queuedTargetFiles],this.queuedTargetFiles.clear();continue}return}}async runScan(e){this.pruneState(),this.refreshExternalObservations();const s=$(),t={...s.files},n=[],o=new Map,l=new Map,f=new Map,h=e?[...e]:[...R(),...P(),...k()];!e&&this.watchEnabled&&this.watcher.refresh(h);for(const i of h){if(!A.existsSync(i))continue;const r=A.statSync(i),m=j(i),p=i.endsWith(".opencode-session.json"),c=q(i);if(c.status==="pending")continue;let u=s.files[i];const v=`${String(r.dev)}:${String(r.ino)}:${String(r.birthtimeMs)}`;let I=!u;if(u&&!p&&(r.size<u.offset||u.fileIdentity&&u.fileIdentity!==v)&&(u=void 0,I=!0),!p&&c.sources.length>0&&c.sources.every(a=>this.repository.isSourceDeleted?.(a.agentType,a.sourceSessionKey))){t[i]={...u??{daemonInstallationId:s.daemonInstallationId,classification:c.createdAtMs>=s.fusionEpochMs?"live":"historical",awaitingUserBoundary:!0,sessionCreatedAtMs:c.createdAtMs,sourceBoundaryEstablished:{}},offset:r.size,mtimeMs:r.mtimeMs,fileIdentity:v};continue}if(!u)u={daemonInstallationId:s.daemonInstallationId,offset:0,mtimeMs:r.mtimeMs,classification:c.createdAtMs>=s.fusionEpochMs?"live":"historical",awaitingUserBoundary:!0,sessionCreatedAtMs:c.createdAtMs,sourceBoundaryEstablished:{}};else if(p&&u.mtimeMs===r.mtimeMs)continue;const x=p?0:u.offset,g=p?D(i,x):m?L(i,x):W(i,x);for(const a of g.events)this.repository.isSourceDeleted?.(a.agentType,a.sourceSessionKey)||a.title?.trim()&&a.titleSource==="agent_native"&&f.set(`${a.agentType}:${a.sourceSessionKey}`,a);let w,S;if(p){const a=G(g.events,u,s.fusionEpochMs,I,r.mtimeMs,g.nextOffset);w=a.events,S=a.cursor}else if(I&&u.classification==="historical")w=[],S={...u,offset:g.nextOffset,mtimeMs:r.mtimeMs,awaitingUserBoundary:!0};else{const a=X(g.events,u,g.nextOffset,r.mtimeMs);w=a.events,S=a.cursor}S.fileIdentity=v,t[i]=S;for(const a of w){if(this.repository.isSourceDeleted?.(a.agentType,a.sourceSessionKey))continue;const y=this.tryClaimManagedEcho(a);if(y){n.push(y.event),o.set(y.claimKey,y.suppression??null),y.suppression&&l.set(T(y.suppression.agentType,y.suppression.sourceSessionKey),{window:y.suppression,claimKey:y.claimKey});continue}const M=this.suppressionFor(a,l,o);if(M){n.push({...a,claimSessionId:M.sessionId,localDisposition:"managed_echo_suppressed"});continue}n.push(a)}}this.repository.commitEvents(n);for(const[i,r]of o)this.repository.completeManagedClaim?.(i,r??void 0),this.pendingClaims.delete(i),r&&this.suppressionWindows.set(T(r.agentType,r.sourceSessionKey),r);for(const i of f.values())this.repository.observeNativeTitle(i.agentType,i.sourceSessionKey,i.title??"");for(const i of this.repository.listSourceBindings()){if(i.agentType!=="codex")continue;const r=F(i.sourceSessionKey);r&&this.repository.observeNativeTitle("codex",i.sourceSessionKey,r)}for(const i of n)this.observeExternalLifecycle(i);s.files=t,B(s),await _(this.client).catch(i=>{console.error("[native-fusion] index projection sync failed; retained for retry",i)})}refreshExternalObservations(){const e=Date.now();for(const[s,t]of this.externalObservations.entries()){const n=t.filter(l=>e-l.createdAt<K);if(n.length===0){this.externalObservations.delete(s);continue}this.externalObservations.set(s,n);const o=n[0];e-o.lastPublishedAt<Q||(o.lastPublishedAt=e,this.emitExternalActivity(o,o.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 o=n.externalRunId?K:U;if(Math.abs(e.ts-n.createdAt)>o||b(e.payload)!==n.textHash)continue;const l=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)+z}:void 0;return{claimKey:t,suppression:l,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];e.claimCanonicalMessageId===n.canonicalMessageId&&n.state==="queued"&&(n.state="running",n.lastPublishedAt=Date.now(),this.emitExternalActivity(n,"running")),!(!e.terminal||n.state!=="running")&&(this.emitExternalTerminal(n),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=T(e.agentType,e.sourceSessionKey),o=s.get(n),l=o?.window??this.suppressionWindows.get(n);return!l||e.ts<l.startedAt?null:e.ts>l.endsAt?(this.clearSuppression(n,l,o,s,t),null):e.role==="user"?(e.sourceEventKey===l.claimSourceEventKey||this.clearSuppression(n,l,o,s,t),null):l}clearSuppression(e,s,t,n,o){if(t){n.delete(e),o.set(t.claimKey,null);return}this.repository.removeManagedSuppression?.(s.agentType,s.sourceSessionKey),this.suppressionWindows.delete(e)}}function b(d){return C.createHash("sha256").update(V(d)).digest("hex")}function T(d,e){return`${d}:${e}`}function X(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(o=>o.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 G(d,e,s,t,n,o){const l={...e.sourceBoundaryEstablished??{}},f=e.eventWatermarkTs??-1,h=new Set(e.eventKeysAtWatermark??[]),i=t?d:d.filter(c=>c.ts>f||c.ts===f&&!h.has(c.sourceEventKey)),r=[];for(const c of i){const u=c.sourceSessionKey;if(l[u]){r.push(c);continue}!(!t||(c.sourceSessionCreatedAtMs??0)>=s)||c.role!=="user"||(l[u]=!0,r.push(c))}const m=d.reduce((c,u)=>Math.max(c,u.ts),f),p=new Set(m===f?e.eventKeysAtWatermark??[]:[]);for(const c of d)c.ts===m&&p.add(c.sourceEventKey);return{events:r,cursor:{...e,offset:o,mtimeMs:n,eventWatermarkTs:m>=0?m:void 0,eventKeysAtWatermark:m>=0?[...p]:void 0,sourceBoundaryEstablished:l}}}export{ie as NativeSessionFusionService};
|
|
@@ -27,6 +27,7 @@ export type ParsedNativeEvent = NativeSessionEventPayload & {
|
|
|
27
27
|
claimSessionId?: string | null;
|
|
28
28
|
canonicalMessageKey?: string;
|
|
29
29
|
canonicalMutation?: 'append' | 'upsert';
|
|
30
|
+
localDisposition?: 'managed_echo_suppressed';
|
|
30
31
|
titleSource?: Extract<SessionTitleSource, 'agent_native' | 'first_user_fallback'>;
|
|
31
32
|
localToolDetail?: {
|
|
32
33
|
toolCallId: string;
|
|
@@ -43,9 +44,8 @@ export type PendingManagedClaim = {
|
|
|
43
44
|
agentType: string;
|
|
44
45
|
sourceAgentType: 'codex' | 'claude' | 'opencode';
|
|
45
46
|
canonicalMessageId: string;
|
|
46
|
-
|
|
47
|
+
textHash: string;
|
|
47
48
|
createdAt: number;
|
|
48
|
-
expiresAt: number;
|
|
49
49
|
sourceSessionKey?: string | null;
|
|
50
50
|
managedEchoPolicy: 'suppress_following' | 'import_following';
|
|
51
51
|
externalRunId?: string | null;
|
|
@@ -63,6 +63,7 @@ export type ManagedSuppressionWindow = {
|
|
|
63
63
|
sessionId: string;
|
|
64
64
|
agentType: 'codex' | 'claude' | 'opencode';
|
|
65
65
|
sourceSessionKey: string;
|
|
66
|
+
claimSourceEventKey: string;
|
|
66
67
|
startedAt: number;
|
|
67
68
|
endsAt: number;
|
|
68
69
|
};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
`),s=n>0?Math.min(n,80):Math.min(e.length,80);return e.slice(0,s)}function ye(e,n,s={}){if(e.state==="tool-call"||e.state==="tool-result"){const a={runId:e.runId,sourceSeq:e.seq};return{state:e.state,runId:e.runId,seq:e.seq,sessionId:n,detailRef:a,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}:{},...s}}return{...e,sessionId:n,...s}}const Se=3e4;function Ae(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 j(e,n){return`Agent send failed: ${n instanceof Error?n.message:String(n)}`}function K(e,n){return e!=="manager"?e:n==="claude"?"claude":"codex"}function Ee(e,n){z({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 G(e){return ge(e)}function X(e,n,s){return{}}function Y(e,n,s,a,i,c){e.configure?.({sessionId:n,externalChannel:a??null,env:{...G(s),...X(n,a,i),...c?{SHENNIAN_MANAGED_ROOM_CONTEXT:c}:{}}})}function xe(e,n,s){if(e!=="claude"||!s)return n;const a=n.trim();return!!a&&a.startsWith("-")&&!a.includes("/")?le(s)??n:n}function Re(e,n,s,a){let i=null;function c(t,r={}){e.client.sendAgentEvent({type:"event",event:"agent",payload:ye(t,n,r),seq:t.seq,id:`agent-evt-${t.runId}-${t.seq}`})}function m(t){t?.heartbeatTimer&&(clearInterval(t.heartbeatTimer),t.heartbeatTimer=null)}function S(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 w(t){!t||t.heartbeatTimer||(t.heartbeatTimer=setInterval(()=>{S(t)},Se),t.heartbeatTimer.unref?.())}function l(t){const r=t?.pendingTextEvent;!t||!r||!r.text||(q(n,{id:`agent-${r.runId}-${r.seq}`,sessionId:n,role:"agent",ts:Date.now(),payload:r.text}),R(e.client),c({state:"delta",runId:r.runId,seq:r.seq,text:r.text,thinking:r.thinking||void 0}),t.pendingTextEvent=null)}a.on("agentEvent",t=>{const r=e.sessions.get(n),b=Ae(t),T=t.state==="final"||t.state==="error"||t.state==="aborted";if(r&&(r.nextEventSeq=t.seq+1,T||(r.currentRunId=t.runId),!T&&b&&(r.currentRunPhase=b,w(r)),t.agentSessionId&&(r.agentSessionId=t.agentSessionId)),e.managerRuntime?.noteAgentEvent(n,t),t.state==="delta"&&t.thinking)return;t.state!=="delta"&&M({level:"info",sessionId:n,wsEvent:`agent.${t.state}`,wsDirection:"out",metadata:{runId:t.runId,seq:t.seq,agentType:s}});let A=!1;if(t.state==="tool-call"||t.state==="tool-result"){const p=`agent-${t.runId}-${t.seq}`;Ie({sessionId:n,toolCallId:p,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.state==="tool-call"?"running":"completed",...t.durationMs!==void 0?{durationMs:t.durationMs}:{},detailRef:{runId:t.runId,sourceSeq:t.seq}}),q(n,{id:p,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:p,status: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"})}),A=!0}else t.state==="approval-pending"?(q(n,{id:`agent-${t.runId||"run"}-${t.seq}`,sessionId:n,role:"agent",ts:Date.now(),payload:oe(t.approval)}),A=!0):(t.state==="error"||t.state==="aborted")&&t.message&&(q(n,{id:`agent-${t.runId||"run"}-${t.seq}`,sessionId:n,role:"agent",ts:Date.now(),payload:t.message}),A=!0);A&&R(e.client);const I=`${n}:${t.runId}`;if(t.state==="delta"&&!t.thinking&&t.text&&e.runTextAcc.set(I,(e.runTextAcc.get(I)??"")+t.text),t.agentSessionId&&t.agentSessionId!==i&&(i=t.agentSessionId,e.nativeFusion?.noteManagedSourceSession(n,K(s,t.source),t.agentSessionId),r&&z({sessionId:n,agentType:s,workDir:r.workDir,agentSessionId:t.agentSessionId}),e.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:n,agentType:s,agentSessionId:t.agentSessionId}}})),t.state==="delta"){const p=t.text??"";if(!p)return;r?.pendingTextEvent&&r.pendingTextEvent.runId!==t.runId&&l(r),r&&!r.pendingTextEvent&&(r.pendingTextEvent={runId:t.runId,seq:t.seq,text:"",thinking:!1}),r?.pendingTextEvent&&(r.pendingTextEvent.text+=p,r.pendingTextEvent.seq=t.seq);return}let $={};if(t.state==="final"){l(r);const p=e.runTextAcc.get(I)??"";p&&($={messageSummary:he(p)}),e.runTextAcc.delete(I)}else t.state==="error"||t.state==="aborted"?(l(r),e.runTextAcc.delete(I)):(t.state==="tool-call"||t.state==="tool-result"||t.state==="approval-pending")&&l(r);T&&r?.currentRunId===t.runId&&(r.currentRunId=null,r.currentRunPhase=null,r.nextEventSeq=0,m(r),e.chatQueue?.noteTerminal(n),z({sessionId:n,agentType:s,workDir:r.workDir,agentSessionId:r.agentSessionId,status:t.state==="final"?"completed":"failed",lastActivityAt:new Date().toISOString()}),R(e.client)),c(t,$)}),a.on("error",t=>{console.error(`[chat.send] adapter error sessionId=${n} agentType=${s}: ${t.message}`),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 we(e,n){if(e.processedReqIds.add(n),e.processedReqIds.size>1e3){const s=e.processedReqIds.values().next().value;e.processedReqIds.delete(s)}}async function v(e){e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),e.adapter.removeAllListeners(),await e.adapter.stop().catch(()=>{})}function Z(e,n,s,a,i,c,m){return m?e.send(n,s,a,i,c,m):c?.trim()?e.send(n,s,a,i,c):i?.length?e.send(n,s,a,i):a?e.send(n,s,a):e.send(n,s)}async function Te(e,n,s,a,i,c,m,S){e.evictIdleSessions();const w=re(s);if(!w)throw new Error(`Unsupported agent: ${s}`);const l=a==="manager"&&s!=="manager"?new ie(w,s==="claude"?"claude":"codex"):w;Y(l,n,s,m,S),await l.start(n,i,c);const t={adapter:l,workDir:i,agentType:s,sessionMode:a,agentSessionId:c??null,lastActiveAt:Date.now(),currentRunId:null,currentRunPhase:null,nextEventSeq:0,heartbeatSeq:0,heartbeatTimer:null,pendingTextEvent:null,externalChannel:m??null,externalReplyTarget:S??null,externalChannelEnv:{...G(s),...X(n,m,S)}};return e.sessions.set(n,t),Re(e,n,s,l),t}function ke(e,n){const s=e.sessions.get(n),a=s?.currentRunId;if(!s||!a)return;const i=s.nextEventSeq;e.runTextAcc.delete(`${n}:${a}`),s.pendingTextEvent=null,s.currentRunId=null,s.currentRunPhase=null,s.nextEventSeq=0,s.heartbeatTimer&&(clearInterval(s.heartbeatTimer),s.heartbeatTimer=null),e.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"aborted",sessionId:n,runId:a,seq:i},seq:i,id:`agent-evt-${a}-${i}`}),e.chatQueue?.noteTerminal(n)}async function Le(e,n,s){if(e.processedReqIds.has(n.id)){e.client.sendRes({type:"res",id:n.id,ok:!0});return}we(e,n.id);const{sessionId:a,text:i,agentType:c,sessionMode:m,managerConfig:S,workDir:w,agentSessionId:l,modelId:t,systemPrompt:r,managerDefaultWorkerAgentType:b,managerDefaultWorkerModelId:T,reasoningEffort:A,clientMessageId:I,sessionListProjection:$,waitForDispatch:p,responseId:ee,managedRoomContextToken:P,suppressExternalOwnerEcho:te}=n.params,h=ee||n.id;ue($);const C=null,L="";if(!a||!i){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:h,ok:!1,error:"sessionId and text are required"});return}const g=c,D=g==="manager"||m==="manager"?"manager":"standard";D==="manager"&&e.managerRuntime?.setManagerWorkerDefaults(a,b??null,T??null);const W=(g==="claude"||g==="codex"||g==="workbuddy")&&typeof A=="string"&&A.trim()?A.trim():void 0,k=e.resolvePath(xe(g,w||V.homedir(),l)||V.homedir()),ne=k,_=fe(n.params.attachments);_?.length&&pe(k,_);const y={text:i,attachments:_,localized:!1};let d=e.sessions.get(a);if(d){if(d.lastActiveAt=Date.now(),d.agentType!==g||(d.sessionMode??(d.agentType==="manager"?"manager":"standard"))!==D||d.workDir!==k||JSON.stringify(d.externalChannel??null)!==JSON.stringify(C??null)){e.sessions.delete(a);try{await v(d)}catch{e.processedReqIds.delete(n.id)}d=void 0}else if(l&&d.agentSessionId!==l)try{await d.adapter.resume(l),d.agentSessionId=l}catch{e.sessions.delete(a);try{await v(d)}catch{e.processedReqIds.delete(n.id)}d=void 0}}if(!d)try{d=await Te(e,a,g,D,k,l,C,L)}catch(o){const f=o instanceof Error&&o.message.startsWith("Unsupported agent:")?o.message:`Failed to start ${c}: ${o instanceof Error?o.message:String(o)}`;console.error(`[chat.send] start failed reqId=${n.id} sessionId=${a} agentType=${c} workDir=${k} agentSessionId=${l??""}: ${f}`),e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:a,message:f,runId:"",seq:0}}),e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:h,ok:!1,error:f});return}P&&/^mrc_[A-Za-z0-9_-]{43}$/.test(P)&&Y(d.adapter,a,g,C,L,P);const E={id:I??`user-${n.id}`,sessionId:a,role:"user",ts:Date.now(),payload:de(y.text,y.attachments)},x=ce(a,E.id),H=x.state==="queued";if(x.state!=="not_found"&&!H){e.client.sendRes({type:"res",id:h,ok:!0,payload:x});return}M({level:"info",sessionId:a,wsEvent:"chat.send.start",metadata:{reqId:n.id,agentType:g,sessionMode:D,managerConfig:S??null,modelId:t,managerDefaultWorkerAgentType:b??null,managerDefaultWorkerModelId:T??null,reasoningEffort:W}});const O=(o,f)=>({sessionId:a,clientMessageId:E.id,messageId:E.id,deliveryState:o,bodyRevision:f,committedAt:new Date(E.ts).toISOString()}),U=()=>{if(H)return{sessionId:a,clientMessageId:x.clientMessageId,messageId:x.messageId,deliveryState:x.deliveryState,bodyRevision:x.bodyRevision,committedAt:x.committedAt};Ee(e,{sessionId:a,agentType:g,sessionMode:D,managerConfig:S??null,workDir:ne,agentSessionId:d.agentSessionId??l??null,modelId:t,managerDefaultWorkerAgentType:b??null,managerDefaultWorkerModelId:T??null});const o=q(a,E,"committed");return me(k,_??[]),R(e.client),O("committed",o)},B=o=>{d.currentRunId||(d.nextEventSeq=0),e.nativeFusion?.registerManagedSend({sessionId:a,agentType:g,sourceAgentType:K(g,t),canonicalMessageId:I??null,sourceSessionKey:d.agentSessionId??l??null,text:y.text,managedEchoPolicy:o?.deliveryMode==="external_owner_queue"&&!te?"import_following":"suppress_following",externalRunId:o?.deliveryMode==="external_owner_queue"?`codex-external-${o.queuedSubmissionId??I??n.id}`:null});const f=o?.deliveryMode==="external_owner_queue"?"queued":"executing",u=F(a,E,f);return R(e.client),O(f,u)},Q=async(o,f)=>{const u=j(g,o);console.error(`[chat.send] send failed reqId=${n.id} sessionId=${a} agentType=${c} workDir=${k} agentSessionId=${d.agentSessionId??l??""}: ${u}`),e.sessions.delete(a);try{await v(d)}catch{}if(e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:a,message:u,runId:"",seq:0}}),!f){const N={id:`agent-error-${n.id}-${Date.now()}`,sessionId:a,role:"agent",ts:Date.now(),payload:u};q(a,N),R(e.client)}f&&(e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:h,ok:!1,error:u}))};if(p){try{U()}catch(u){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:h,ok:!1,error:`Durable message commit failed: ${u instanceof Error?u.message:String(u)}`});return}let o;try{const u=y.attachments;o=await Z(d.adapter,y.text,t,W,u,r,s),M({level:"info",sessionId:a,wsEvent:"chat.send.done",metadata:{reqId:n.id}})}catch(u){const N=F(a,E,"failed");R(e.client);const se=j(g,u);await Q(u,!1),e.client.sendRes({type:"res",id:h,ok:!0,payload:{...O("failed",N),failureMessage:se}});return}const f=B(o??void 0);e.client.sendRes({type:"res",id:h,ok:!0,payload:{...f,...y.localized?{localizedAttachments:!0}:{},...o?.deliveryMode==="external_owner_queue"?{queued:!0,deliveryMode:o.deliveryMode,queuedSubmissionId:o.queuedSubmissionId}:{}}}),M({level:"info",sessionId:a,wsEvent:"chat.send.res",metadata:{reqId:n.id,ok:!0}});return}let J;try{J=U()}catch(o){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:h,ok:!1,error:`Durable message commit failed: ${o instanceof Error?o.message:String(o)}`});return}B(),e.client.sendRes({type:"res",id:h,ok:!0,payload:{...J,...y.localized?{localizedAttachments:!0}:{}}}),M({level:"info",sessionId:a,wsEvent:"chat.send.res",metadata:{reqId:n.id,ok:!0}});const ae=y.attachments;Z(d.adapter,y.text,t,W,ae,r,s).then(()=>{M({level:"info",sessionId:a,wsEvent:"chat.send.done",metadata:{reqId:n.id}})}).catch(o=>{F(a,E,"failed"),R(e.client),Q(o,!1)})}async function He(e,n){const{sessionId:s}=n.params,a=e.sessions.get(s);if(a){try{await a.adapter.stop()}catch{}ke(e,s),e.activityPublisher?.publish(s,null)}e.client.sendRes({type:"res",id:n.id,ok:!0})}export{He as handleChatAbort,Le as handleChatSend,Ee as sendSessionUpdateEvent};
|
|
1
|
+
import j from"node:os";import{createAgent as oe}from"../../agents/adapter.js";import{buildApprovalPendingPayload as de,buildUserMessagePayload as ie}from"@shennian/wire";import{ManagerModeAdapter as le}from"../../agents/manager.js";import{reportLog as b}from"../../log-reporter.js";import{lookupClaudeTranscriptCwd as ce}from"../../native-fusion/parsers.js";import{appendMessage as q,getMessageDeliveryStatus as ue,recordSession as N,upsertMessage as v}from"../store.js";import{mergeProjectedSessions as ge}from"../projection.js";import{buildManagedAgentEnv as fe}from"../../agents/config-status.js";import{parsePersonalAttachmentReferences as pe}from"../attachment-reference.js";import{assertFinishedAttachmentReferences as me,markAttachmentReferences as he}from"../attachment-transfer-store.js";import{requestSessionIndexPublish as w}from"../index-publisher.js";import{writeToolDetail as Ie}from"../tool-detail-store.js";function ye(e){const n=e.indexOf(`
|
|
2
|
+
`),s=n>0?Math.min(n,80):Math.min(e.length,80);return e.slice(0,s)}function Se(e,n,s={}){if(e.state==="tool-call"||e.state==="tool-result"){const a={runId:e.runId,sourceSeq:e.seq};return{state:e.state,runId:e.runId,seq:e.seq,sessionId:n,detailRef:a,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}:{},...s}}return{...e,sessionId:n,...s}}const Ae=3e4;function Ee(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 K(e,n){return`Agent send failed: ${n instanceof Error?n.message:String(n)}`}function G(e,n){return e!=="manager"?e:n==="claude"?"claude":"codex"}function we(e,n){N({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 X(e){return fe(e)}function Y(e,n,s){return{}}function Z(e,n,s,a,i,c){e.configure?.({sessionId:n,externalChannel:a??null,env:{...X(s),...Y(n,a,i),...c?{SHENNIAN_MANAGED_ROOM_CONTEXT:c}:{}}})}function xe(e,n,s){if(e!=="claude"||!s)return n;const a=n.trim();return!!a&&a.startsWith("-")&&!a.includes("/")?ce(s)??n:n}function Re(e,n,s,a){let i=null;function c(t,r={}){e.client.sendAgentEvent({type:"event",event:"agent",payload:Se(t,n,r),seq:t.seq,id:`agent-evt-${t.runId}-${t.seq}`})}function h(t){t?.heartbeatTimer&&(clearInterval(t.heartbeatTimer),t.heartbeatTimer=null)}function S(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 x(t){!t||t.heartbeatTimer||(t.heartbeatTimer=setInterval(()=>{S(t)},Ae),t.heartbeatTimer.unref?.())}function l(t){const r=t?.pendingTextEvent;!t||!r||!r.text||(q(n,{id:`agent-${r.runId}-${r.seq}`,sessionId:n,role:"agent",ts:Date.now(),payload:r.text}),w(e.client),c({state:"delta",runId:r.runId,seq:r.seq,text:r.text,thinking:r.thinking||void 0}),t.pendingTextEvent=null)}a.on("agentEvent",t=>{const r=e.sessions.get(n),M=Ee(t),R=t.state==="final"||t.state==="error"||t.state==="aborted";if(r&&(r.nextEventSeq=t.seq+1,R||(r.currentRunId=t.runId),!R&&M&&(r.currentRunPhase=M,x(r)),t.agentSessionId&&(r.agentSessionId=t.agentSessionId)),e.managerRuntime?.noteAgentEvent(n,t),t.state==="delta"&&t.thinking)return;t.state!=="delta"&&b({level:"info",sessionId:n,wsEvent:`agent.${t.state}`,wsDirection:"out",metadata:{runId:t.runId,seq:t.seq,agentType:s}});let A=!1;if(t.state==="tool-call"||t.state==="tool-result"){const m=`agent-${t.runId}-${t.seq}`;Ie({sessionId:n,toolCallId:m,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.state==="tool-call"?"running":"completed",...t.durationMs!==void 0?{durationMs:t.durationMs}:{},detailRef:{runId:t.runId,sourceSeq:t.seq}}),q(n,{id:m,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:m,status: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"})}),A=!0}else t.state==="approval-pending"?(q(n,{id:`agent-${t.runId||"run"}-${t.seq}`,sessionId:n,role:"agent",ts:Date.now(),payload:de(t.approval)}),A=!0):(t.state==="error"||t.state==="aborted")&&t.message&&(q(n,{id:`agent-${t.runId||"run"}-${t.seq}`,sessionId:n,role:"agent",ts:Date.now(),payload:t.message}),A=!0);A&&w(e.client);const T=`${n}:${t.runId}`;if(t.state==="delta"&&!t.thinking&&t.text&&e.runTextAcc.set(T,(e.runTextAcc.get(T)??"")+t.text),t.agentSessionId&&t.agentSessionId!==i&&(i=t.agentSessionId,e.nativeFusion?.noteManagedSourceSession(n,G(s,t.source),t.agentSessionId),r&&N({sessionId:n,agentType:s,workDir:r.workDir,agentSessionId:t.agentSessionId}),e.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:n,agentType:s,agentSessionId:t.agentSessionId}}})),t.state==="delta"){const m=t.text??"";if(!m)return;r?.pendingTextEvent&&r.pendingTextEvent.runId!==t.runId&&l(r),r&&!r.pendingTextEvent&&(r.pendingTextEvent={runId:t.runId,seq:t.seq,text:"",thinking:!1}),r?.pendingTextEvent&&(r.pendingTextEvent.text+=m,r.pendingTextEvent.seq=t.seq);return}let $={};if(t.state==="final"){l(r);const m=e.runTextAcc.get(T)??"";m&&($={messageSummary:ye(m)}),e.runTextAcc.delete(T)}else t.state==="error"||t.state==="aborted"?(l(r),e.runTextAcc.delete(T)):(t.state==="tool-call"||t.state==="tool-result"||t.state==="approval-pending")&&l(r);R&&r?.currentRunId===t.runId&&(r.currentRunId=null,r.currentRunPhase=null,r.nextEventSeq=0,h(r),e.chatQueue?.noteTerminal(n),N({sessionId:n,agentType:s,workDir:r.workDir,agentSessionId:r.agentSessionId,status:t.state==="final"?"completed":"failed",lastActivityAt:new Date().toISOString()}),w(e.client)),c(t,$)}),a.on("error",t=>{console.error(`[chat.send] adapter error sessionId=${n} agentType=${s}: ${t.message}`),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 Te(e,n){if(e.processedReqIds.add(n),e.processedReqIds.size>1e3){const s=e.processedReqIds.values().next().value;e.processedReqIds.delete(s)}}async function z(e){e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),e.adapter.removeAllListeners(),await e.adapter.stop().catch(()=>{})}function ee(e,n,s,a,i,c,h){return h?e.send(n,s,a,i,c,h):c?.trim()?e.send(n,s,a,i,c):i?.length?e.send(n,s,a,i):a?e.send(n,s,a):e.send(n,s)}async function ke(e,n,s,a,i,c,h,S){e.evictIdleSessions();const x=oe(s);if(!x)throw new Error(`Unsupported agent: ${s}`);const l=a==="manager"&&s!=="manager"?new le(x,s==="claude"?"claude":"codex"):x;Z(l,n,s,h,S),await l.start(n,i,c);const t={adapter:l,workDir:i,agentType:s,sessionMode:a,agentSessionId:c??null,lastActiveAt:Date.now(),currentRunId:null,currentRunPhase:null,nextEventSeq:0,heartbeatSeq:0,heartbeatTimer:null,pendingTextEvent:null,externalChannel:h??null,externalReplyTarget:S??null,externalChannelEnv:{...X(s),...Y(n,h,S)}};return e.sessions.set(n,t),Re(e,n,s,l),t}function Me(e,n){const s=e.sessions.get(n),a=s?.currentRunId;if(!s||!a)return;const i=s.nextEventSeq;e.runTextAcc.delete(`${n}:${a}`),s.pendingTextEvent=null,s.currentRunId=null,s.currentRunPhase=null,s.nextEventSeq=0,s.heartbeatTimer&&(clearInterval(s.heartbeatTimer),s.heartbeatTimer=null),e.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"aborted",sessionId:n,runId:a,seq:i},seq:i,id:`agent-evt-${a}-${i}`}),e.chatQueue?.noteTerminal(n)}async function He(e,n,s){if(e.processedReqIds.has(n.id)){e.client.sendRes({type:"res",id:n.id,ok:!0});return}Te(e,n.id);const{sessionId:a,text:i,agentType:c,sessionMode:h,managerConfig:S,workDir:x,agentSessionId:l,modelId:t,systemPrompt:r,managerDefaultWorkerAgentType:M,managerDefaultWorkerModelId:R,reasoningEffort:A,clientMessageId:T,sessionListProjection:$,waitForDispatch:m,responseId:te,managedRoomContextToken:P,suppressExternalOwnerEcho:ne}=n.params,I=te||n.id;ge($);const C=null,L="";if(!a||!i){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:I,ok:!1,error:"sessionId and text are required"});return}const g=c,D=g==="manager"||h==="manager"?"manager":"standard";D==="manager"&&e.managerRuntime?.setManagerWorkerDefaults(a,M??null,R??null);const W=(g==="claude"||g==="codex"||g==="workbuddy")&&typeof A=="string"&&A.trim()?A.trim():void 0,k=e.resolvePath(xe(g,x||j.homedir(),l)||j.homedir()),ae=k,_=pe(n.params.attachments);_?.length&&me(k,_);const y={text:i,attachments:_,localized:!1};let d=e.sessions.get(a);if(d){if(d.lastActiveAt=Date.now(),d.agentType!==g||(d.sessionMode??(d.agentType==="manager"?"manager":"standard"))!==D||d.workDir!==k||JSON.stringify(d.externalChannel??null)!==JSON.stringify(C??null)){e.sessions.delete(a);try{await z(d)}catch{e.processedReqIds.delete(n.id)}d=void 0}else if(l&&d.agentSessionId!==l)try{await d.adapter.resume(l),d.agentSessionId=l}catch{e.sessions.delete(a);try{await z(d)}catch{e.processedReqIds.delete(n.id)}d=void 0}}if(!d)try{d=await ke(e,a,g,D,k,l,C,L)}catch(o){const f=o instanceof Error&&o.message.startsWith("Unsupported agent:")?o.message:`Failed to start ${c}: ${o instanceof Error?o.message:String(o)}`;console.error(`[chat.send] start failed reqId=${n.id} sessionId=${a} agentType=${c} workDir=${k} agentSessionId=${l??""}: ${f}`),e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:a,message:f,runId:"",seq:0}}),e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:I,ok:!1,error:f});return}P&&/^mrc_[A-Za-z0-9_-]{43}$/.test(P)&&Z(d.adapter,a,g,C,L,P);const p={id:T??`user-${n.id}`,sessionId:a,role:"user",ts:Date.now(),payload:ie(y.text,y.attachments)},E=ue(a,p.id),H=E.state==="queued";if(E.state!=="not_found"&&!H){e.client.sendRes({type:"res",id:I,ok:!0,payload:E});return}b({level:"info",sessionId:a,wsEvent:"chat.send.start",metadata:{reqId:n.id,agentType:g,sessionMode:D,managerConfig:S??null,modelId:t,managerDefaultWorkerAgentType:M??null,managerDefaultWorkerModelId:R??null,reasoningEffort:W}});const O=(o,f)=>({sessionId:a,clientMessageId:p.id,messageId:p.id,deliveryState:o,bodyRevision:f,committedAt:new Date(p.ts).toISOString()}),U=o=>{e.nativeFusion?.registerManagedSend({sessionId:a,agentType:g,sourceAgentType:G(g,t),canonicalMessageId:p.id,sourceSessionKey:d.agentSessionId??l??null,text:y.text,managedEchoPolicy:o?.deliveryMode==="external_owner_queue"&&!ne?"import_following":"suppress_following",externalRunId:o?.deliveryMode==="external_owner_queue"?`codex-external-${o.queuedSubmissionId??p.id}`:null})},B=()=>{if(U(),H)return{sessionId:a,clientMessageId:E.clientMessageId,messageId:E.messageId,deliveryState:E.deliveryState,bodyRevision:E.bodyRevision,committedAt:E.committedAt};try{we(e,{sessionId:a,agentType:g,sessionMode:D,managerConfig:S??null,workDir:ae,agentSessionId:d.agentSessionId??l??null,modelId:t,managerDefaultWorkerAgentType:M??null,managerDefaultWorkerModelId:R??null});const o=q(a,p,"committed");return he(k,_??[]),w(e.client),O("committed",o)}catch(o){throw e.nativeFusion?.cancelManagedSend?.(p.id),o}},Q=o=>{d.currentRunId||(d.nextEventSeq=0),U(o);const f=o?.deliveryMode==="external_owner_queue"?"queued":"executing",u=v(a,p,f);return w(e.client),O(f,u)},J=async(o,f)=>{e.nativeFusion?.cancelManagedSend?.(p.id);const u=K(g,o);console.error(`[chat.send] send failed reqId=${n.id} sessionId=${a} agentType=${c} workDir=${k} agentSessionId=${d.agentSessionId??l??""}: ${u}`),e.sessions.delete(a);try{await z(d)}catch{}if(e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:a,message:u,runId:"",seq:0}}),!f){const F={id:`agent-error-${n.id}-${Date.now()}`,sessionId:a,role:"agent",ts:Date.now(),payload:u};q(a,F),w(e.client)}f&&(e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:I,ok:!1,error:u}))};if(m){try{B()}catch(u){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:I,ok:!1,error:`Durable message commit failed: ${u instanceof Error?u.message:String(u)}`});return}let o;try{const u=y.attachments;o=await ee(d.adapter,y.text,t,W,u,r,s),b({level:"info",sessionId:a,wsEvent:"chat.send.done",metadata:{reqId:n.id}})}catch(u){const F=v(a,p,"failed");w(e.client);const re=K(g,u);await J(u,!1),e.client.sendRes({type:"res",id:I,ok:!0,payload:{...O("failed",F),failureMessage:re}});return}const f=Q(o??void 0);e.client.sendRes({type:"res",id:I,ok:!0,payload:{...f,...y.localized?{localizedAttachments:!0}:{},...o?.deliveryMode==="external_owner_queue"?{queued:!0,deliveryMode:o.deliveryMode,queuedSubmissionId:o.queuedSubmissionId}:{}}}),b({level:"info",sessionId:a,wsEvent:"chat.send.res",metadata:{reqId:n.id,ok:!0}});return}let V;try{V=B()}catch(o){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:I,ok:!1,error:`Durable message commit failed: ${o instanceof Error?o.message:String(o)}`});return}Q(),e.client.sendRes({type:"res",id:I,ok:!0,payload:{...V,...y.localized?{localizedAttachments:!0}:{}}}),b({level:"info",sessionId:a,wsEvent:"chat.send.res",metadata:{reqId:n.id,ok:!0}});const se=y.attachments;ee(d.adapter,y.text,t,W,se,r,s).then(()=>{b({level:"info",sessionId:a,wsEvent:"chat.send.done",metadata:{reqId:n.id}})}).catch(o=>{v(a,p,"failed"),w(e.client),J(o,!1)})}async function Ue(e,n){const{sessionId:s}=n.params,a=e.sessions.get(s);if(a){try{await a.adapter.stop()}catch{}Me(e,s),e.activityPublisher?.publish(s,null)}e.client.sendRes({type:"res",id:n.id,ok:!0})}export{Ue as handleChatAbort,He as handleChatSend,we as sendSessionUpdateEvent};
|