shennian 0.3.49 → 0.3.50
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/manager/registry.js +1 -2
- package/dist/src/native-fusion/local-repository.js +2 -2
- package/dist/src/session/store.js +2 -2
- package/node_modules/@shennian/wire/dist/message-payload.d.ts +9 -0
- package/node_modules/@shennian/wire/dist/message-payload.js +53 -0
- package/package.json +1 -1
|
@@ -291,8 +291,8 @@
|
|
|
291
291
|
},
|
|
292
292
|
{
|
|
293
293
|
"file": "src/manager/registry.js",
|
|
294
|
-
"beforeBytes":
|
|
295
|
-
"afterBytes":
|
|
294
|
+
"beforeBytes": 13737,
|
|
295
|
+
"afterBytes": 6649
|
|
296
296
|
},
|
|
297
297
|
{
|
|
298
298
|
"file": "src/manager/retired-external-channel-compat.js",
|
|
@@ -321,8 +321,8 @@
|
|
|
321
321
|
},
|
|
322
322
|
{
|
|
323
323
|
"file": "src/native-fusion/local-repository.js",
|
|
324
|
-
"beforeBytes":
|
|
325
|
-
"afterBytes":
|
|
324
|
+
"beforeBytes": 20835,
|
|
325
|
+
"afterBytes": 10716
|
|
326
326
|
},
|
|
327
327
|
{
|
|
328
328
|
"file": "src/native-fusion/native-source-deletions.js",
|
|
@@ -596,8 +596,8 @@
|
|
|
596
596
|
},
|
|
597
597
|
{
|
|
598
598
|
"file": "src/session/store.js",
|
|
599
|
-
"beforeBytes":
|
|
600
|
-
"afterBytes":
|
|
599
|
+
"beforeBytes": 13716,
|
|
600
|
+
"afterBytes": 7553
|
|
601
601
|
},
|
|
602
602
|
{
|
|
603
603
|
"file": "src/session/tool-detail-store.js",
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
import g from"node:fs";import S from"node:path";import{randomUUID as m}from"node:crypto";import{
|
|
2
|
-
`).trim()||null}function f(i,e){return i?i.length>e?`${i.slice(0,e)}...`:i:null}export{E as ManagerRegistry};
|
|
1
|
+
import g from"node:fs";import S from"node:path";import{randomUUID as m}from"node:crypto";import{isAgentHiddenPayload as w,sessionPreviewFromPayload as A}from"@shennian/wire";import{resolveShennianPath as T}from"../config/index.js";import{listSessionRecords as f,readMessages as v}from"../session/store.js";import{listProjectedSessions as M}from"../session/projection.js";const I=T("manager-registry.json");function u(){return new Date().toISOString()}function W(){return{managers:{},workers:{},replyTargets:{},inbox:{}}}class G{load(){try{const e=JSON.parse(g.readFileSync(I,"utf-8"));return{managers:e.managers??{},workers:e.workers??{},replyTargets:e.replyTargets??{},inbox:e.inbox??{}}}catch{return W()}}save(e){g.mkdirSync(S.dirname(I),{recursive:!0}),g.writeFileSync(I,JSON.stringify(e,null,2))}upsertManager(e){const t=this.load(),r=t.managers[e.sessionId],s=u(),a={sessionId:e.sessionId,agentSessionId:e.agentSessionId??r?.agentSessionId??null,workDir:e.workDir,machineId:e.machineId??r?.machineId??null,modelId:e.modelId,agentType:e.agentType??r?.agentType??"manager",providerModelId:e.providerModelId??r?.providerModelId??null,defaultWorkerAgentType:e.defaultWorkerAgentType??r?.defaultWorkerAgentType??null,defaultWorkerModelId:e.defaultWorkerModelId??r?.defaultWorkerModelId??null,status:e.status??r?.status??"idle",managedWorkerSessionIds:r?.managedWorkerSessionIds??[],attachedExternalChannels:r?.attachedExternalChannels??[],createdAt:r?.createdAt??s,updatedAt:s};return t.managers[e.sessionId]=a,this.save(t),a}getManager(e){return this.load().managers[e]}enqueueInboxEvent(e){const t=this.load(),r=t.managers[e.managerSessionId],s=t.workers[e.workerSessionId];if(!r||!s||s.managedBy!==r.sessionId||s.workDir!==r.workDir)return null;const a=u(),o=t.inbox[e.managerSessionId]??[],d=o.findIndex(c=>c.workerSessionId===e.workerSessionId&&(e.kind==="worker.health"?c.kind==="worker.health":c.kind===e.kind&&c.runId===e.runId)),l=d>=0?{...o[d],...e,updatedAt:a}:{...e,id:`mie_${m()}`,createdAt:a,updatedAt:a};return d>=0?o[d]=l:o.push(l),t.inbox[e.managerSessionId]=o,this.save(t),l}listInboxEvents(e){return[...this.load().inbox[e]??[]].sort((t,r)=>t.createdAt.localeCompare(r.createdAt))}removeInboxEvents(e,t){if(!t.length)return;const r=this.load(),s=new Set(t);r.inbox[e]=(r.inbox[e]??[]).filter(a=>!s.has(a.id)),this.save(r)}addWorker(e){const t=this.load(),r=t.managers[e.managerSessionId];if(!r)throw new Error("Manager runtime is not registered");const s=e.sessionId??`sess_worker_${m()}`,a=u(),o={sessionId:s,agentType:e.agentType,workDir:e.workDir,managedBy:e.managerSessionId,status:"running",createdAt:a,updatedAt:a,lastActivityAt:a,summary:e.summary??null,agentSessionId:null,runId:null};return t.workers[s]=o,r.managedWorkerSessionIds.includes(s)||r.managedWorkerSessionIds.push(s),r.updatedAt=a,this.save(t),o}updateWorker(e,t){const r=this.load(),s=r.workers[e];if(!s)return;const a={...s,...t,updatedAt:u()};return r.workers[e]=a,this.save(r),a}listWorkers(e,t={}){const r=this.load(),s=r.managers[e];if(!s)return[];const a=Object.values(r.workers).filter(n=>n.workDir===s.workDir).filter(n=>t.includeManagers||n.agentType!=="manager").map(n=>this.decorateManagedWorker(n,t.runningSessionIds)),o=new Set(a.map(n=>n.sessionId)),d=f().filter(n=>n.workDir===s.workDir).filter(n=>t.includeManagers||n.agentType!=="manager").filter(n=>!o.has(n.sessionId)).map(n=>this.sessionRecordToWorker(n,t.runningSessionIds)),l=new Set([...a,...d].map(n=>n.sessionId)),h=(t.projectedSessions??M()).filter(n=>n.workDir===s.workDir).filter(n=>t.includeManagers||n.agentType!=="manager").filter(n=>!l.has(n.id)).map(n=>this.projectedSessionToWorker(n,t.runningSessionIds));return[...a,...d,...h].sort((n,k)=>k.lastActivityAt.localeCompare(n.lastActivityAt))}getWorkerForManager(e,t){const r=this.load(),s=r.managers[e],a=r.workers[t];if(!s)return;if(a)return a.workDir!==s.workDir||a.agentType==="manager"?void 0:this.decorateManagedWorker(a);const o=f().find(d=>d.sessionId===t);if(!(!o||o.workDir!==s.workDir||o.agentType==="manager"))return this.sessionRecordToWorker(o)}decorateManagedWorker(e,t){const r=t?.has(e.sessionId)?"running":e.status,s=this.readTranscriptSummary(e.sessionId,e.summary??null),a=s.lastMessagePreview??e.lastMessagePreview??e.summary??null,o=e.userGoal??s.userGoal??null;return{...e,managedBy:e.managedBy??null,status:r,title:e.title??o??a??e.summary??e.sessionId,userGoal:o,lastMessagePreview:a,summary:e.summary??a,canResume:e.canResume??e.status!=="aborted",source:e.source??"manager_registry",role:"managed_worker"}}sessionRecordToWorker(e,t){const r=this.readTranscriptSummary(e.sessionId,e.lastMessagePreview??null),s=r.lastMessagePreview??e.lastMessagePreview??null,a=r.userGoal,o=t?.has(e.sessionId)?"running":e.status==="failed"?"error":e.status==="completed"?"final":"idle";return{sessionId:e.sessionId,agentType:e.agentType,workDir:e.workDir,managedBy:null,status:o,createdAt:e.createdAt,updatedAt:e.updatedAt,lastActivityAt:e.lastActivityAt,title:a??s??e.sessionId,userGoal:a,lastMessagePreview:s,summary:s??a??null,agentSessionId:e.agentSessionId??null,runId:null,canResume:!!e.agentSessionId,source:"local_index",role:"project_session"}}projectedSessionToWorker(e,t){const r=e.lastMessagePreview??null,s=e.title?.trim()||r,a=t?.has(e.id)?"running":e.status==="failed"?"error":e.status==="completed"?"final":"idle";return{sessionId:e.id,agentType:e.agentType,workDir:e.workDir,managedBy:null,status:a,createdAt:e.createdAt,updatedAt:e.updatedAt,lastActivityAt:e.lastActivityAt||e.updatedAt,title:s??e.id,userGoal:s??null,lastMessagePreview:r,summary:r??s??null,agentSessionId:e.agentSessionId??null,runId:e.activity?.runId??null,canResume:!0,source:"app_projection",role:"project_session"}}readTranscriptSummary(e,t){const r=v(e,{limit:200});if(!r.length)return{userGoal:null,lastMessagePreview:t};const s=[...r].sort((d,l)=>d.ts-l.ts),a=s.find(d=>d.role==="user"),o=[...s].reverse().find(d=>y(d));return{userGoal:a?p(y(a),160):null,lastMessagePreview:p(y(o),180)??t}}createReplyTarget(e){const t=this.load(),r={replyTarget:`rt_${m()}`,managerSessionId:e.managerSessionId,channelId:e.channelId,conversationId:e.conversationId,messageId:e.messageId??null,createdAt:u()};return t.replyTargets[r.replyTarget]=r,this.save(t),r}getReplyTarget(e){return this.load().replyTargets[e]}getLatestReplyTargetForManager(e){return Object.values(this.load().replyTargets).filter(t=>t.managerSessionId===e).sort((t,r)=>r.createdAt.localeCompare(t.createdAt))[0]}}function y(i){return!i||w(i.payload)?null:A(i.payload,180)}function p(i,e){return i?i.length>e?`${i.slice(0,e)}...`:i:null}export{G as ManagerRegistry};
|
|
@@ -1,2 +1,2 @@
|
|
|
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{
|
|
1
|
+
import p from"node:crypto";import l from"node:fs";import h from"node:path";import{sessionPreviewFromPayload as k}from"@shennian/wire";import{resolvePersonalSyncV1Path as A}from"../personal-sync/paths.js";import{getCanonicalSessionStore as v,getDaemonInstallationId as B,listSessionRecords as H,recordSession as $}from"../session/store.js";import{readToolDetail as J,writeToolDetail as L}from"../session/tool-detail-store.js";import{NativeSourceDeletionStore as R}from"./native-source-deletions.js";const y=1;class V extends Error{code="missing_user_boundary";constructor(e){super(`native source ${e} does not start at a visible user boundary`),this.name="MissingUserBoundaryError"}}class P extends Error{code="native_source_deleted";constructor(e){super(`native source ${e} is blocked by a durable deletion tombstone`),this.name="NativeSourceDeletedError"}}class ne{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 P(e.sourceSessionKey);const s=this.installationId(),o=this.readSources(),i=b(e.agentType,e.sourceSessionKey),r=o.sources[i],a=z(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 V(e.sourceSessionKey);const m=e.claimSessionId??null,d=r?.sessionId??n?.sessionId??m??U(s,e.agentType,e.sourceSessionKey);if(m&&r&&r.sessionId!==m)throw new Error(`managed claim session conflicts with source binding ${e.sourceSessionKey}`);const S=this.canonicalStore(),f=e.localDisposition==="managed_echo_suppressed"||n?.mappedBy==="managed_echo_suppressed",g=n?.canonicalMessageId??(f?null:e.claimCanonicalMessageId??W(s,e.agentType,e.canonicalMessageKey??e.sourceEventKey));let E=S.getManifest(d)?.bodyRevision??0,T=!!n;const M=!f&&!!e.claimCanonicalMessageId;if(M){if(!g)throw new Error("managed echo claim requires a canonical message");const I=S.getMessageState(d,g);if(!I||I.tombstoned)throw new Error(`managed echo claim target is not durable: ${g}`)}if(!n&&!M&&!f){if(!g)throw new Error("native event requires a canonical message");const I=e.canonicalMutation==="upsert"?S.getMessageState(d,g):null,F={id:g,sessionId:d,role:e.role,ts:I?.message?.ts??e.ts,payload:G(I?.message?.payload,e.payload)},K=e.canonicalMutation==="upsert"?S.upsert(d,F):S.append(d,F);if(E=K.bodyRevision,T=K.duplicate,e.localToolDetail){const C=this.options.writeLocalToolDetail??L,j=this.options.readLocalToolDetail??J,N=Q(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:y,daemonInstallationId:s,sessionId:d,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,sourceEventKey:e.sourceEventKey,eventFingerprint:a,canonicalMessageId:g,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:y,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:g,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=h.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=h.join(a,c.name),m=w(n);if(m.daemonInstallationId!==this.installationId())throw new Error("native event ledger belongs to another daemon installation");m.sessionId===e&&l.unlinkSync(n)}}isSourceDeleted(e,s){return(this.options.deletionStore??new R({rootDir:h.basename(this.rootDir())==="native-fusion"?h.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??H)().find(m=>m.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:y,daemonInstallationId:this.installationId(),sources:{}};const s=w(e);if(s.schemaVersion!==y||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??B()}canonicalStore(){return this.options.canonicalStore??v()}rootDir(){return this.options.rootDir??A("native-fusion")}sourcesFile(){return h.join(this.rootDir(),"sources.json")}managedEchoStateFile(){return h.join(this.rootDir(),"managed-echo-state.json")}readManagedEchoState(){const e=this.managedEchoStateFile();if(!l.existsSync(e))return{schemaVersion:y,daemonInstallationId:this.installationId(),claims:{},suppressions:{}};const s=w(e);if(s.schemaVersion!==y||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 h.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 W(t,e,s){return`native-${p.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function z(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 G(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 Q(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){return k(t,120)}function u(t,e){const s=h.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{V as MissingUserBoundaryError,ne as NativeFusionLocalRepository,P as NativeSourceDeletedError};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import x from"node:crypto";import
|
|
2
|
-
`,"utf8"),
|
|
1
|
+
import x from"node:crypto";import l from"node:fs";import S from"node:path";import{SESSION_SYNC_PROTOCOL_VERSION as w,SESSION_TITLE_SOURCE_PRIORITY as y,sessionPreviewFromPayload as M}from"@shennian/wire";import{PERSONAL_SYNC_V1_ROOT_NAME as p,resolvePersonalSyncV1Path as k}from"../personal-sync/paths.js";import{CanonicalSessionStore as C}from"./canonical-store.js";import{getDaemonInstallationId as d}from"../personal-sync/identity.js";import{NativeSourceDeletionStore as O}from"../native-fusion/native-source-deletions.js";let m=null,I=null;function v(){return k()}function a(){const t=S.join(v(),"canonical");return(!m||I!==t)&&(m=new C(t,d()),I=t),m}function R(){return S.join(v(),"session-index.json")}function u(){const t=R();if(!l.existsSync(t))return{};const e=JSON.parse(l.readFileSync(t,"utf8"));if(!e||typeof e!="object"||Array.isArray(e))throw new Error(`invalid personal session index: ${t}`);const i=d();for(const n of Object.values(e))if(n.daemonInstallationId!==i)throw new Error(`personal session index belongs to another daemon installation: ${t}`);return e}function f(t){B(R(),t)}function E(t){return M(t.payload,120)}function g(t){if(new O({rootDir:v(),daemonInstallationId:d()}).hasSession(t))throw new Error(`session_deleted:${t}`)}function q(t){g(t.sessionId),d();const e=u(),i=e[t.sessionId],n=new Date().toISOString(),s=a().getManifest(t.sessionId)?.bodyRevision??i?.bodyRevision??0,o=a().getManifest(t.sessionId),r=h(i,t),c={daemonInstallationId:d(),sessionId:t.sessionId,agentType:t.agentType,sessionMode:t.sessionMode??i?.sessionMode,managerConfig:t.managerConfig??i?.managerConfig??null,workDir:t.workDir,agentSessionId:t.agentSessionId??i?.agentSessionId??null,modelId:t.modelId??i?.modelId??null,managerDefaultWorkerAgentType:t.managerDefaultWorkerAgentType??i?.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:t.managerDefaultWorkerModelId??i?.managerDefaultWorkerModelId??null,status:t.status??i?.status??"active",createdAt:i?.createdAt??n,updatedAt:n,lastActivityAt:t.lastActivityAt??i?.lastActivityAt??n,lastMessagePreview:t.lastMessagePreview??i?.lastMessagePreview??null,bodyRevision:s,indexRevision:(i?.indexRevision??0)+1,...r,firstAvailableSeq:o?.firstAvailableSeq??i?.firstAvailableSeq??null,visibleMessageCount:o?.visibleMessageCount??i?.visibleMessageCount??0,lastVisibleMessageId:o?.lastVisibleMessageId??i?.lastVisibleMessageId??null,lastBodyCommittedAt:o?.lastBodyCommittedAt??i?.lastBodyCommittedAt??null,deletionRevision:i?.deletionRevision??0,deletedAt:t.deletedAt??i?.deletedAt??null,syncedIndexRevision:i?.syncedIndexRevision??0};return e[t.sessionId]=c,f(e),c}function D(){return Object.values(u())}function $(t){if(!t)throw new Error("sessionId is required");const e=u();e[t]&&(delete e[t],f(e))}function F(t){a().purgeSession(t)}function j(){const t=d();return D().filter(e=>e.indexRevision>e.syncedIndexRevision).sort((e,i)=>e.indexRevision-i.indexRevision).map(e=>({protocolVersion:w,daemonInstallationId:t,session:{id:e.sessionId,agentType:e.agentType,sessionMode:e.sessionMode,managerConfig:e.managerConfig,agentSessionId:e.agentSessionId??null,modelId:e.modelId??null,managerDefaultWorkerAgentType:e.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:e.managerDefaultWorkerModelId??null,title:e.title,titleSource:e.titleSource,titleRevision:e.titleRevision,titleLockedByUser:e.titleLockedByUser,titleOriginEventId:e.titleOriginEventId,status:e.status??"active",workDir:e.workDir,lastMessagePreview:e.lastMessagePreview??null,bodyStorageMode:"machine_local",bodyRevision:e.bodyRevision,indexRevision:e.indexRevision,firstAvailableSeq:e.firstAvailableSeq,visibleMessageCount:e.visibleMessageCount,lastVisibleMessageId:e.lastVisibleMessageId,lastBodyCommittedAt:e.lastBodyCommittedAt,deletionRevision:e.deletionRevision,lastActivityAt:e.lastActivityAt,createdAt:e.createdAt,updatedAt:e.updatedAt,deletedAt:e.deletedAt}}))}function J(t,e){const i=u(),n=i[t];if(!(!n||e<=n.syncedIndexRevision)){if(e>n.indexRevision)throw new Error(`cannot acknowledge future index revision ${e} for ${t}`);i[t]={...n,syncedIndexRevision:e},f(i)}}function Y(t,e,i){g(t),d();const n=a().append(t,e,i);return A(t,e,n.bodyRevision),n.bodyRevision}function z(t,e,i){g(t),d();const n=a().upsert(t,e,i);return A(t,e,n.bodyRevision),n.bodyRevision}function G(t,e){const i=a().getMessageState(t,e);return!i||i.tombstoned||!i.message||!i.deliveryState?{state:"not_found",sessionId:t,clientMessageId:e}:{state:i.deliveryState,sessionId:t,clientMessageId:e,messageId:i.message.id,deliveryState:i.deliveryState,bodyRevision:i.lastRevision,committedAt:new Date(i.message.ts).toISOString()}}function H(t,e){g(t),d();const i=a().tombstone(t,e),n=u(),s=n[t];return s&&(n[t]={...s,bodyRevision:i.bodyRevision,indexRevision:s.indexRevision+1,updatedAt:new Date().toISOString()},f(n)),i.bodyRevision}function A(t,e,i){const n=u(),s=n[t];if(!s)return;const o=E(e),r=a().getManifest(t),c=e.role==="user"&&o&&!s.title?{title:o,titleSource:"first_user_fallback",titleRevision:s.titleRevision+1,titleLockedByUser:!1,titleOriginEventId:e.id}:null;n[t]={...s,...c??{},bodyRevision:i,indexRevision:s.indexRevision+1,updatedAt:new Date().toISOString(),lastActivityAt:new Date(e.ts).toISOString(),lastMessagePreview:o??s.lastMessagePreview??null,firstAvailableSeq:r?.firstAvailableSeq??s.firstAvailableSeq,visibleMessageCount:r?.visibleMessageCount??s.visibleMessageCount,lastVisibleMessageId:r?.lastVisibleMessageId??s.lastVisibleMessageId,lastBodyCommittedAt:r?.lastBodyCommittedAt??s.lastBodyCommittedAt},f(n)}function h(t,e){const i={title:t?.title??"",titleSource:t?.titleSource??"first_user_fallback",titleRevision:t?.titleRevision??0,titleLockedByUser:t?.titleLockedByUser??!1,titleOriginEventId:t?.titleOriginEventId??null},n=e.title?.trim()??"";if(!n)return i;if(!e.titleSource)throw new Error("titleSource is required with title");const s=e.titleSource==="shennian_manual";if(e.titleLockedByUser!==void 0&&e.titleLockedByUser!==s)throw new Error("titleLockedByUser conflicts with titleSource");const o=n===i.title&&e.titleSource===i.titleSource,r=e.titleRevision??i.titleRevision+(o?0:1);if(!Number.isSafeInteger(r)||r<0)throw new Error("invalid titleRevision");const c=y[e.titleSource],b=y[i.titleSource];return c<b||r<i.titleRevision||r===i.titleRevision&&!o?i:{title:n,titleSource:e.titleSource,titleRevision:r,titleLockedByUser:s,titleOriginEventId:e.titleOriginEventId??i.titleOriginEventId}}function K(t,e){const i=e?.limit&&e.limit>0?Math.min(e.limit,500):500;let s=a().readRecent(t,i).messages;return e?.before!==void 0&&(s=s.filter(o=>o.ts<e.before)),[...s].sort((o,r)=>r.ts-o.ts)}function Q(){return a().listSessionIds()}function X(){return a()}function B(t,e){const i=S.dirname(t);l.mkdirSync(i,{recursive:!0,mode:448});const n=`${t}.tmp-${process.pid}-${x.randomBytes(6).toString("hex")}`;let s;try{if(s=l.openSync(n,"wx",384),l.writeFileSync(s,`${JSON.stringify(e,null,2)}
|
|
2
|
+
`,"utf8"),l.fsyncSync(s),l.closeSync(s),s=void 0,l.renameSync(n,t),process.platform!=="win32"){const o=l.openSync(i,"r");try{l.fsyncSync(o)}finally{l.closeSync(o)}}}finally{s!==void 0&&l.closeSync(s);try{l.unlinkSync(n)}catch{}}}export{p as PERSONAL_SYNC_V1_ROOT_NAME,J as acknowledgeSessionIndexUpdate,Y as appendMessage,X as getCanonicalSessionStore,d as getDaemonInstallationId,G as getMessageDeliveryStatus,j as listPendingSessionIndexUpdates,D as listSessionRecords,Q as listSessions,F as purgeCanonicalSession,$ as purgeSessionRecord,K as readMessages,q as recordSession,H as tombstoneMessage,z as upsertMessage};
|
|
@@ -58,4 +58,13 @@ export declare function isCodexProjectInstructionsPayload(payload: string): bool
|
|
|
58
58
|
export declare function isSystemControlPayload(payload: string): boolean;
|
|
59
59
|
export declare function stripGitDirectiveArtifacts(text: string): string;
|
|
60
60
|
export declare function extractPayloadText(payload: string): string;
|
|
61
|
+
/**
|
|
62
|
+
* Converts already-readable text into the only form allowed in the replicated
|
|
63
|
+
* personal-session index. A preview is content, not an opaque transport field:
|
|
64
|
+
* private control context and binary payloads are therefore rejected instead
|
|
65
|
+
* of being truncated into something that still leaks them.
|
|
66
|
+
*/
|
|
67
|
+
export declare function sanitizeSessionPreview(value: string | null | undefined, maxLength?: number): string | null;
|
|
68
|
+
/** Extracts a safe, bounded preview from an envelope payload. */
|
|
69
|
+
export declare function sessionPreviewFromPayload(payload: string, maxLength?: number): string | null;
|
|
61
70
|
export {};
|
|
@@ -7,6 +7,20 @@ const ENVIRONMENT_CONTEXT_REQUIRED_TAG_RE = /<(cwd|shell|current_date|timezone)>
|
|
|
7
7
|
const CODEX_TURN_ABORTED_RE = /^\s*<turn_aborted>[^]*?<\/turn_aborted>\s*$/i;
|
|
8
8
|
const CODEX_PROJECT_INSTRUCTIONS_RE = /^\s*# AGENTS\.md instructions for [^\n]+\n+\s*<INSTRUCTIONS>\s*[^]*?<\/INSTRUCTIONS>\s*(?:<environment_context>[^]*?<\/environment_context>)?\s*$/i;
|
|
9
9
|
const APPROVAL_PENDING_PAYLOAD_PREFIX_RE = /^\s*\{\s*"v"\s*:\s*1\s*,\s*"type"\s*:\s*"approval_pending"/;
|
|
10
|
+
const PREVIEW_PRIVATE_CONTEXT_RE = /(?:<\/?(?:environment_context|instructions|system|developer|codex_internal_context|recommended_plugins)\b|#\s*AGENTS\.md instructions\b)/i;
|
|
11
|
+
const PREVIEW_DATA_URL_RE = /\bdata:[a-z0-9.+-]+\/[a-z0-9.+-]+;base64,[a-z0-9+/=\s]+/i;
|
|
12
|
+
const PREVIEW_BASE64_BLOB_RE = /(?:^|\s)(?=[A-Za-z0-9+/]{80,}={0,2}(?=\s|$))(?=[A-Za-z0-9+/]*[A-Z])(?=[A-Za-z0-9+/]*[a-z])(?=[A-Za-z0-9+/]*[0-9])[A-Za-z0-9+/]{80,}={0,2}(?=\s|$)/;
|
|
13
|
+
const PREVIEW_HIDDEN_TYPES = new Set([
|
|
14
|
+
'tool',
|
|
15
|
+
'tool_use',
|
|
16
|
+
'tool_result',
|
|
17
|
+
'thinking',
|
|
18
|
+
'system',
|
|
19
|
+
'developer',
|
|
20
|
+
'context',
|
|
21
|
+
'environment_context',
|
|
22
|
+
'channel_delivery_status',
|
|
23
|
+
]);
|
|
10
24
|
function escapeMarkdownLabel(label) {
|
|
11
25
|
return label.replace(/[[\]\\]/g, '\\$&');
|
|
12
26
|
}
|
|
@@ -234,6 +248,45 @@ export function extractPayloadText(payload) {
|
|
|
234
248
|
}
|
|
235
249
|
return stripGitDirectiveArtifacts(payload);
|
|
236
250
|
}
|
|
251
|
+
/**
|
|
252
|
+
* Converts already-readable text into the only form allowed in the replicated
|
|
253
|
+
* personal-session index. A preview is content, not an opaque transport field:
|
|
254
|
+
* private control context and binary payloads are therefore rejected instead
|
|
255
|
+
* of being truncated into something that still leaks them.
|
|
256
|
+
*/
|
|
257
|
+
export function sanitizeSessionPreview(value, maxLength = 160) {
|
|
258
|
+
if (typeof value !== 'string' || !Number.isSafeInteger(maxLength) || maxLength < 1)
|
|
259
|
+
return null;
|
|
260
|
+
const normalized = value.replace(/\r\n/g, '\n').trim();
|
|
261
|
+
if (!normalized)
|
|
262
|
+
return null;
|
|
263
|
+
if (PREVIEW_PRIVATE_CONTEXT_RE.test(normalized) ||
|
|
264
|
+
PREVIEW_DATA_URL_RE.test(normalized) ||
|
|
265
|
+
PREVIEW_BASE64_BLOB_RE.test(normalized) ||
|
|
266
|
+
parseObjectPayload(normalized))
|
|
267
|
+
return null;
|
|
268
|
+
const oneLine = normalized.replace(/\s+/g, ' ');
|
|
269
|
+
return oneLine.length > maxLength ? `${oneLine.slice(0, maxLength)}…` : oneLine;
|
|
270
|
+
}
|
|
271
|
+
/** Extracts a safe, bounded preview from an envelope payload. */
|
|
272
|
+
export function sessionPreviewFromPayload(payload, maxLength = 160) {
|
|
273
|
+
if (!payload || isToolPayload(payload) || isSystemControlPayload(payload))
|
|
274
|
+
return null;
|
|
275
|
+
if (isApprovalPendingPayload(payload))
|
|
276
|
+
return sanitizeSessionPreview('需要用户确认', maxLength);
|
|
277
|
+
const structured = parseStructuredMessagePayload(payload);
|
|
278
|
+
if (structured) {
|
|
279
|
+
const type = typeof structured.type === 'string' ? structured.type.toLowerCase() : '';
|
|
280
|
+
if (!type || PREVIEW_HIDDEN_TYPES.has(type))
|
|
281
|
+
return null;
|
|
282
|
+
return sanitizeSessionPreview(extractPayloadText(payload), maxLength);
|
|
283
|
+
}
|
|
284
|
+
// JSON objects that are not a known structured message can contain raw tool
|
|
285
|
+
// arguments/results. Never guess which property is safe for an index preview.
|
|
286
|
+
if (parseObjectPayload(payload))
|
|
287
|
+
return null;
|
|
288
|
+
return sanitizeSessionPreview(extractPayloadText(payload), maxLength);
|
|
289
|
+
}
|
|
237
290
|
function stringOrUndefined(value) {
|
|
238
291
|
return typeof value === 'string' && value.trim() ? value : undefined;
|
|
239
292
|
}
|