shennian 0.4.69 → 0.4.72
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 +150 -90
- package/dist/src/agent-env.js +4 -4
- package/dist/src/agents/adapter.d.ts +7 -3
- package/dist/src/agents/auth-status.d.ts +4 -0
- package/dist/src/agents/auth-status.js +2 -0
- package/dist/src/agents/claude.js +4 -4
- package/dist/src/agents/codex-protocol.d.ts +103 -0
- package/dist/src/agents/codex-protocol.js +3 -0
- package/dist/src/agents/codex-runtime.d.ts +91 -0
- package/dist/src/agents/codex-runtime.js +14 -0
- package/dist/src/agents/codex.d.ts +15 -91
- package/dist/src/agents/codex.js +2 -17
- package/dist/src/agents/command-spec.js +4 -3
- package/dist/src/agents/detect.js +1 -1
- package/dist/src/agents/manager.d.ts +4 -3
- package/dist/src/agents/manager.js +4 -4
- package/dist/src/agents/model-registry/discovery.js +3 -3
- package/dist/src/agents/model-registry/parsers.js +4 -4
- package/dist/src/agents/model-registry/service.d.ts +6 -2
- package/dist/src/agents/model-registry/service.js +1 -1
- package/dist/src/agents/model-registry/types.d.ts +9 -1
- package/dist/src/agents/model-registry/types.js +1 -1
- package/dist/src/agents/opencode.d.ts +4 -0
- package/dist/src/agents/opencode.js +1 -1
- package/dist/src/commands/agent-readiness.d.ts +17 -0
- package/dist/src/commands/agent-readiness.js +2 -0
- package/dist/src/commands/agent.js +2 -2
- package/dist/src/commands/daemon-instance-lock.js +1 -1
- package/dist/src/commands/daemon-process.d.ts +1 -0
- package/dist/src/commands/daemon-process.js +1 -1
- package/dist/src/commands/daemon-service-spec.d.ts +28 -0
- package/dist/src/commands/daemon-service-spec.js +1 -0
- package/dist/src/commands/daemon-start-wait.d.ts +1 -1
- package/dist/src/commands/daemon-start-wait.js +1 -1
- package/dist/src/commands/daemon-state.d.ts +57 -0
- package/dist/src/commands/daemon-state.js +1 -0
- package/dist/src/commands/daemon-windows.d.ts +8 -0
- package/dist/src/commands/daemon-windows.js +10 -10
- package/dist/src/commands/daemon.d.ts +4 -64
- package/dist/src/commands/daemon.js +13 -13
- package/dist/src/commands/manager.js +1 -1
- package/dist/src/commands/upgrade.js +1 -1
- package/dist/src/commands/worker.d.ts +11 -0
- package/dist/src/commands/worker.js +2 -0
- package/dist/src/daemon-ipc/agent-readiness.d.ts +14 -0
- package/dist/src/daemon-ipc/agent-readiness.js +1 -0
- package/dist/src/daemon-ipc/control.d.ts +1 -1
- package/dist/src/daemon-ipc/control.js +1 -1
- package/dist/src/index.js +3 -3
- package/dist/src/manager/prompt.d.ts +1 -1
- package/dist/src/manager/prompt.js +6 -6
- package/dist/src/manager/registry.d.ts +48 -2
- package/dist/src/manager/registry.js +1 -1
- package/dist/src/manager/runtime.d.ts +2 -2
- package/dist/src/manager/runtime.js +6 -6
- package/dist/src/manager/worker-ipc.d.ts +7 -0
- package/dist/src/manager/worker-ipc.js +1 -1
- package/dist/src/native-fusion/local-repository.d.ts +6 -1
- package/dist/src/native-fusion/local-repository.js +2 -2
- package/dist/src/native-fusion/service.d.ts +2 -0
- package/dist/src/native-fusion/service.js +2 -2
- package/dist/src/native-fusion/source-visibility-audit.d.ts +16 -0
- package/dist/src/native-fusion/source-visibility-audit.js +1 -0
- package/dist/src/native-fusion/source-visibility.d.ts +25 -0
- package/dist/src/native-fusion/source-visibility.js +1 -0
- package/dist/src/native-fusion/state.d.ts +1 -0
- package/dist/src/native-fusion/state.js +2 -2
- package/dist/src/native-fusion/types.d.ts +5 -0
- package/dist/src/room/managed-room-binding.d.ts +2 -0
- package/dist/src/room/managed-room-binding.js +1 -1
- package/dist/src/room/room-binding-client.d.ts +1 -0
- package/dist/src/room/room-binding-client.js +1 -1
- package/dist/src/runtime-distribution.d.ts +13 -0
- package/dist/src/runtime-distribution.js +1 -0
- package/dist/src/security/managed-agent-policy.d.ts +7 -0
- package/dist/src/security/managed-agent-policy.js +1 -1
- package/dist/src/session/handlers/agent-workspace-validation.d.ts +1 -0
- package/dist/src/session/handlers/agent-workspace-validation.js +1 -1
- package/dist/src/session/handlers/agents.d.ts +7 -1
- package/dist/src/session/handlers/agents.js +1 -1
- package/dist/src/session/handlers/chat-context.d.ts +31 -0
- package/dist/src/session/handlers/chat-context.js +1 -0
- package/dist/src/session/handlers/chat-events.d.ts +1 -1
- package/dist/src/session/handlers/chat-events.js +2 -2
- package/dist/src/session/handlers/chat-session-lifecycle.d.ts +2 -2
- package/dist/src/session/handlers/chat-session-lifecycle.js +1 -1
- package/dist/src/session/handlers/chat.d.ts +3 -29
- package/dist/src/session/handlers/chat.js +1 -1
- package/dist/src/session/manager.js +2 -2
- package/dist/src/session/queue-helpers.js +2 -2
- package/dist/src/session/queue-message.d.ts +2 -0
- package/dist/src/session/queue.d.ts +7 -1
- package/dist/src/session/queue.js +1 -1
- package/dist/src/session/queued-chat-dispatch.d.ts +4 -1
- package/dist/src/session/queued-chat-dispatch.js +1 -1
- package/dist/src/session/store.d.ts +4 -2
- package/dist/src/session/store.js +2 -2
- package/dist/src/session/types.d.ts +1 -0
- package/dist/src/upgrade/engine.js +2 -2
- package/dist/src/upgrade/scheduler.js +1 -1
- package/dist/src/upgrade/updater-launcher.js +3 -3
- package/node_modules/@shennian/wire/dist/room.d.ts +2 -0
- package/node_modules/@shennian/wire/dist/session-sync.d.ts +2 -1
- package/node_modules/@shennian/wire/dist/session.d.ts +25 -1
- package/node_modules/@shennian/wire/dist/session.js +8 -0
- package/package.json +1 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import y from"node:crypto";import d from"node:fs";import S from"node:path";import{isToolPayload as B,sessionPreviewFromPayload as L}from"@shennian/wire";import{resolvePersonalSyncV1Path as P}from"../personal-sync/paths.js";import{getCanonicalSessionStore as H,getDaemonInstallationId as U,listSessionRecords as x,recordSession as w}from"../session/store.js";import{readToolDetail as V,writeToolDetail as q}from"../session/tool-detail-store.js";import{NativeSourceDeletionStore as J}from"./native-source-deletions.js";import{recordPersonalSyncLocalMetric as v}from"../personal-sync/metrics.js";const E=1;class W extends Error{code="missing_user_boundary";constructor(e){super(`native source ${e} does not start at a visible user boundary`),this.name="MissingUserBoundaryError"}}class z extends Error{code="native_source_deleted";constructor(e){super(`native source ${e} is blocked by a durable deletion tombstone`),this.name="NativeSourceDeletedError"}}class G extends Error{sourceEventKey;code="native_event_identity_conflict";constructor(e){super(`native event identity conflict: ${e}`),this.sourceEventKey=e,this.name="NativeEventIdentityConflictError"}}class ue{options;constructor(e={}){this.options=e}commitEvents(e){return[...e].sort((s,o)=>s.ts-o.ts).map(s=>this.commitEvent(s))}updateMessageDeliveryState(e,s,o){if(!this.canonicalStore().updateMessageDeliveryState(e,s,o))return;const i=(this.options.listLocalSessions??x)().find(a=>a.sessionId===e);if(!i)return;(this.options.recordLocalSession??w)({...i})}commitEvent(e){if(Q(e),this.isSourceDeleted(e.agentType,e.sourceSessionKey))throw new z(e.sourceSessionKey);const s=this.installationId(),o=this.readSources(),n=b(e.agentType,e.sourceSessionKey),i=o.sources[n],c=Z(e),a=this.ledgerFile(e.agentType,e.sourceEventKey),r=d.existsSync(a)?D(a):null;if(r&&r.daemonInstallationId!==s)throw new Error("native event ledger belongs to another daemon installation");if(r&&r.eventFingerprint!==c)throw new G(e.sourceEventKey);if(!i&&!r&&e.role!=="user")throw new W(e.sourceSessionKey);const u=e.claimSessionId??null,l=i?.sessionId??r?.sessionId??u??X(s,e.agentType,e.sourceSessionKey);if(u&&i&&i.sessionId!==u)throw new Error(`managed claim session conflicts with source binding ${e.sourceSessionKey}`);const p=this.canonicalStore(),I=e.localDisposition==="managed_echo_suppressed"||r?.mappedBy==="managed_echo_suppressed",g=r?.canonicalMessageId??(I?null:e.claimCanonicalMessageId??Y(s,e.agentType,e.canonicalMessageKey??e.sourceEventKey));let f=p.getManifest(l)?.bodyRevision??0,T=!!r;const M=!I&&!!e.claimCanonicalMessageId;if(M){if(!g)throw new Error("managed echo claim requires a canonical message");const h=p.getMessageState(l,g);if(!h||h.tombstoned)throw new Error(`managed echo claim target is not durable: ${g}`);h.deliveryState==="queued"&&h.message&&(f=p.upsert(l,{...h.message,ts:e.ts},"executing").bodyRevision)}if(!r&&!M&&!I){if(!g)throw new Error("native event requires a canonical message");const h=e.canonicalMessageKey?p.getMessageState(l,g):null,F=e.canonicalMutation==="upsert"||!!h,$=F?h??p.getMessageState(l,g):null,_={id:g,sessionId:l,role:e.role,ts:$?.message?.ts??e.ts,payload:ee($?.message?.payload,e.payload)},C=F?p.upsert(l,_):p.append(l,_);if(f=C.bodyRevision,T=C.duplicate,e.localToolDetail){const N=this.options.writeLocalToolDetail??q,j=this.options.readLocalToolDetail??V,R=se(j(l,e.localToolDetail.toolCallId),e.localToolDetail);N({sessionId:l,...R,ts:e.ts,detailRef:{toolIndex:0}})}}e.terminal&&(f=p.completeStartedUserMessages(l,e.ts).at(-1)?.bodyRevision??f),(!r||e.terminal)&&!I&&this.recordIndex(e,l,g,f);const K=new Date().toISOString();return r||m(a,{schemaVersion:E,daemonInstallationId:s,sessionId:l,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,sourceEventKey:e.sourceEventKey,eventFingerprint:c,canonicalMessageId:g,canonicalRevision:f,cursor:e.cursor??null,mappedBy:I?"managed_echo_suppressed":M?"managed_echo_claim":e.sourceMode,eventTs:e.ts,createdAt:K}),o.sources[n]={schemaVersion:E,daemonInstallationId:s,sessionId:l,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,sourceMode:r?i?.sourceMode??e.sourceMode:e.sourceMode,lastCursor:r?i?.lastCursor??r.cursor:e.cursor??i?.lastCursor??null,bodyRevision:f,createdAt:i?.createdAt??K,updatedAt:K},m(this.sourcesFile(),o),!r&&e.cursor&&v("sourceCursorAdvanced"),!r&&!M&&!I&&!T&&v("canonicalCommits"),{sessionId:l,messageId:g,bodyRevision:f,duplicate:T,claimed:M,suppressed:I}}repairMissingUserBoundaries(){const e=this.options.listLocalSessions??x,s=this.options.recordLocalSession??w,o=this.canonicalStore();let n=0;for(const i of e()){if(i.localReady)continue;const a=o.getManifest(i.sessionId)?.firstAvailableSeq;if(a==null)continue;const r=o.readPage(i.sessionId,{limit:1,beforeSequence:a+1}).entries[0];!r||r.sequence!==a||r.message.role!=="user"||(s({sessionId:i.sessionId,agentType:i.agentType,sessionMode:i.sessionMode,managerConfig:i.managerConfig,workDir:i.workDir,agentSessionId:i.agentSessionId,modelId:i.modelId,managerDefaultWorkerAgentType:i.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:i.managerDefaultWorkerModelId,status:i.status,lastActivityAt:i.lastActivityAt,lastMessagePreview:i.lastMessagePreview,localReady:!0,firstUserMessageId:r.message.id,firstUserBodyRevision:r.revision}),n+=1)}return n}listManagedClaims(){return Object.values(this.readManagedEchoState().claims)}listManagedSuppressions(){return Object.values(this.readManagedEchoState().suppressions)}upsertManagedClaim(e){A(e);const s=this.readManagedEchoState();s.claims[e.canonicalMessageId]=e,m(this.managedEchoStateFile(),s)}updateManagedClaimSource(e,s,o){const n=this.readManagedEchoState();let i=!1;for(const c of Object.values(n.claims))c.sessionId!==e||c.sourceAgentType!==s||(c.sourceSessionKey=o,i=!0);i&&m(this.managedEchoStateFile(),n)}removeManagedClaim(e){const s=this.readManagedEchoState();s.claims[e]&&(delete s.claims[e],m(this.managedEchoStateFile(),s))}completeManagedClaim(e,s){const o=this.readManagedEchoState();delete o.claims[e],s&&(O(s),o.suppressions[b(s.agentType,s.sourceSessionKey)]=s),m(this.managedEchoStateFile(),o)}removeManagedSuppression(e,s){const o=this.readManagedEchoState(),n=b(e,s);o.suppressions[n]&&(delete o.suppressions[n],m(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[a,r]of Object.entries(s.sources))r.sessionId===e&&(delete s.sources[a],o=!0);o&&m(this.sourcesFile(),s);const n=this.readManagedEchoState();let i=!1;for(const[a,r]of Object.entries(n.claims))r.sessionId===e&&(delete n.claims[a],i=!0);for(const[a,r]of Object.entries(n.suppressions))r.sessionId===e&&(delete n.suppressions[a],i=!0);i&&m(this.managedEchoStateFile(),n);const c=S.join(this.rootDir(),"ledger");if(d.existsSync(c))for(const a of d.readdirSync(c,{withFileTypes:!0})){if(!a.isFile()||!a.name.endsWith(".json"))continue;const r=S.join(c,a.name),u=D(r);if(u.daemonInstallationId!==this.installationId())throw new Error("native event ledger belongs to another daemon installation");u.sessionId===e&&d.unlinkSync(r)}}isSourceDeleted(e,s){return(this.options.deletionStore??new J({rootDir:S.basename(this.rootDir())==="native-fusion"?S.dirname(this.rootDir()):this.rootDir(),daemonInstallationId:this.installationId()})).has(e,s)}observeNativeTitle(e,s,o){const n=o.trim().slice(0,120);if(!n)return!1;const i=this.readSources().sources[b(e,s)];if(!i||this.isSourceDeleted(e,s))return!1;const c=(this.options.listLocalSessions??x)().find(u=>u.sessionId===i.sessionId);if(c?.titleLockedByUser||c?.titleSource==="agent_native"&&c.title===n)return!1;const a=y.createHash("sha256").update(`${e}\0${s}\0${n}`).digest("hex");return(this.options.recordLocalSession??w)({sessionId:i.sessionId,agentType:i.agentType,workDir:c?.workDir??"",agentSessionId:s,title:n,titleSource:"agent_native",titleOriginEventId:`agent-native:${a}`}),!0}recordIndex(e,s,o,n){const i=this.options.recordLocalSession??w,c=e.terminal?`terminal:${y.createHash("sha256").update(e.sourceEventKey).digest("hex")}`:void 0,a=e.role==="user"||e.role==="agent"?k(e.payload):null,r=a&&o?{lastMessagePreview:a,lastMessagePreviewMessageId:o,lastMessagePreviewAt:new Date(e.ts).toISOString()}:{},u=o&&(a||B(e.payload))?new Date(e.ts).toISOString():void 0;i({sessionId:s,agentType:e.agentType,workDir:e.workDir??"",agentSessionId:e.sourceSessionKey,modelId:e.modelId??null,status:e.terminal?e.terminalState==="failed"||e.terminalState==="aborted"?"failed":"completed":"active",attentionId:c,lastActivityAt:new Date(e.ts).toISOString(),lastVisibleEventAt:u,...r,...e.role==="user"&&o?{localReady:!0,firstUserMessageId:o,firstUserBodyRevision:n}:{},title:e.title?.trim()||(e.role==="user"?k(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(!d.existsSync(e))return{schemaVersion:E,daemonInstallationId:this.installationId(),sources:{}};const s=D(e);if(s.schemaVersion!==E||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??U()}canonicalStore(){return this.options.canonicalStore??H()}rootDir(){return this.options.rootDir??P("native-fusion")}sourcesFile(){return S.join(this.rootDir(),"sources.json")}managedEchoStateFile(){return S.join(this.rootDir(),"managed-echo-state.json")}readManagedEchoState(){const e=this.managedEchoStateFile();if(!d.existsSync(e))return{schemaVersion:E,daemonInstallationId:this.installationId(),claims:{},suppressions:{}};const s=D(e);if(s.schemaVersion!==E||s.daemonInstallationId!==this.installationId()||!s.claims||!s.suppressions)throw new Error("invalid managed echo state");for(const o of Object.values(s.claims))A(o);for(const o of Object.values(s.suppressions))O(o);return s}ledgerFile(e,s){const o=y.createHash("sha256").update(`${e}\0${s}`).digest("hex");return S.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 y.createHash("sha256").update(`${t}\0${e}`).digest("hex")}function X(t,e,s){return`native-session-${y.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function Y(t,e,s){return`native-${y.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function Z(t){return y.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 A(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 O(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 ee(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 n=typeof o.name=="string"?o.name:"",i=/(?:_output|_result)$/.test(n);return JSON.stringify({...s,...o,...i&&typeof s.name=="string"?{name:s.name}:{}})}catch{return e}}function se(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 k(t){return L(t,120)}function m(t,e){const s=S.dirname(t);d.mkdirSync(s,{recursive:!0,mode:448});const o=`${t}.tmp-${process.pid}-${y.randomBytes(6).toString("hex")}`;let n;try{n=d.openSync(o,"wx",384),d.writeFileSync(n,`${JSON.stringify(e,null,2)}
|
|
2
|
-
`,"utf8"),d.fsyncSync(n),d.closeSync(n),n=void 0,d.renameSync(o,t)}finally{n!==void 0&&d.closeSync(n);try{d.unlinkSync(o)}catch{}}}function D(t){return JSON.parse(d.readFileSync(t,"utf8"))}export{
|
|
1
|
+
import{isToolPayload as B,sessionPreviewFromPayload as L}from"@shennian/wire";import y from"node:crypto";import d from"node:fs";import u from"node:path";import{recordPersonalSyncLocalMetric as C}from"../personal-sync/metrics.js";import{resolvePersonalSyncV1Path as P}from"../personal-sync/paths.js";import{getCanonicalSessionStore as H,getDaemonInstallationId as U,listSessionRecords as K,recordSession as w}from"../session/store.js";import{readToolDetail as V,writeToolDetail as q}from"../session/tool-detail-store.js";import{NativeSourceDeletionStore as J}from"./native-source-deletions.js";import{isInternalNativeSource as W}from"./source-visibility.js";const E=1;class z extends Error{code="missing_user_boundary";constructor(e){super(`native source ${e} does not start at a visible user boundary`),this.name="MissingUserBoundaryError"}}class G extends Error{code="internal_native_source";constructor(){super("internal native source cannot be imported as a personal session"),this.name="InternalNativeSourceError"}}class Q extends Error{code="native_source_deleted";constructor(e){super(`native source ${e} is blocked by a durable deletion tombstone`),this.name="NativeSourceDeletedError"}}class X extends Error{sourceEventKey;code="native_event_identity_conflict";constructor(e){super(`native event identity conflict: ${e}`),this.sourceEventKey=e,this.name="NativeEventIdentityConflictError"}}class pe{options;constructor(e={}){this.options=e}commitEvents(e){return[...e].filter(s=>!this.isSourceInternal(s.agentType,s.sourceSessionKey)).sort((s,o)=>s.ts-o.ts).map(s=>this.commitEvent(s))}updateMessageDeliveryState(e,s,o){if(!this.canonicalStore().updateMessageDeliveryState(e,s,o))return;const i=(this.options.listLocalSessions??K)().find(a=>a.sessionId===e);if(!i)return;(this.options.recordLocalSession??w)({...i})}commitEvent(e){if(Y(e),this.isSourceInternal(e.agentType,e.sourceSessionKey))throw new G;if(this.isSourceDeleted(e.agentType,e.sourceSessionKey))throw new Q(e.sourceSessionKey);const s=this.installationId(),o=this.readSources(),n=b(e.agentType,e.sourceSessionKey),i=o.sources[n],c=se(e),a=this.ledgerFile(e.agentType,e.sourceEventKey),r=d.existsSync(a)?D(a):null;if(r&&r.daemonInstallationId!==s)throw new Error("native event ledger belongs to another daemon installation");if(r&&r.eventFingerprint!==c)throw new X(e.sourceEventKey);if(!i&&!r&&e.role!=="user")throw new z(e.sourceSessionKey);const g=e.claimSessionId??null,l=i?.sessionId??r?.sessionId??g??Z(s,e.agentType,e.sourceSessionKey);if(g&&i&&i.sessionId!==g)throw new Error(`managed claim session conflicts with source binding ${e.sourceSessionKey}`);const f=this.canonicalStore(),I=e.localDisposition==="managed_echo_suppressed"||r?.mappedBy==="managed_echo_suppressed",m=r?.canonicalMessageId??(I?null:e.claimCanonicalMessageId??ee(s,e.agentType,e.canonicalMessageKey??e.sourceEventKey));let h=f.getManifest(l)?.bodyRevision??0,T=!!r;const M=!I&&!!e.claimCanonicalMessageId;if(M){if(!m)throw new Error("managed echo claim requires a canonical message");const S=f.getMessageState(l,m);if(!S||S.tombstoned)throw new Error(`managed echo claim target is not durable: ${m}`);S.deliveryState==="queued"&&S.message&&(h=f.upsert(l,{...S.message,ts:e.ts},"executing").bodyRevision)}if(!r&&!M&&!I){if(!m)throw new Error("native event requires a canonical message");const S=e.canonicalMessageKey?f.getMessageState(l,m):null,F=e.canonicalMutation==="upsert"||!!S,_=F?S??f.getMessageState(l,m):null,v={id:m,sessionId:l,role:e.role,ts:_?.message?.ts??e.ts,payload:te(_?.message?.payload,e.payload)},$=F?f.upsert(l,v):f.append(l,v);if(h=$.bodyRevision,T=$.duplicate,e.localToolDetail){const k=this.options.writeLocalToolDetail??q,j=this.options.readLocalToolDetail??V,R=oe(j(l,e.localToolDetail.toolCallId),e.localToolDetail);k({sessionId:l,...R,ts:e.ts,detailRef:{toolIndex:0}})}}e.terminal&&(h=f.completeStartedUserMessages(l,e.ts).at(-1)?.bodyRevision??h),(!r||e.terminal)&&!I&&this.recordIndex(e,l,m,h);const x=new Date().toISOString();return r||p(a,{schemaVersion:E,daemonInstallationId:s,sessionId:l,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,sourceEventKey:e.sourceEventKey,eventFingerprint:c,canonicalMessageId:m,canonicalRevision:h,cursor:e.cursor??null,mappedBy:I?"managed_echo_suppressed":M?"managed_echo_claim":e.sourceMode,eventTs:e.ts,createdAt:x}),o.sources[n]={schemaVersion:E,daemonInstallationId:s,sessionId:l,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,sourceMode:r?i?.sourceMode??e.sourceMode:e.sourceMode,lastCursor:r?i?.lastCursor??r.cursor:e.cursor??i?.lastCursor??null,bodyRevision:h,createdAt:i?.createdAt??x,updatedAt:x},p(this.sourcesFile(),o),!r&&e.cursor&&C("sourceCursorAdvanced"),!r&&!M&&!I&&!T&&C("canonicalCommits"),{sessionId:l,messageId:m,bodyRevision:h,duplicate:T,claimed:M,suppressed:I}}repairMissingUserBoundaries(){const e=this.options.listLocalSessions??K,s=this.options.recordLocalSession??w,o=this.canonicalStore();let n=0;for(const i of e()){if(i.localReady)continue;const a=o.getManifest(i.sessionId)?.firstAvailableSeq;if(a==null)continue;const r=o.readPage(i.sessionId,{limit:1,beforeSequence:a+1}).entries[0];!r||r.sequence!==a||r.message.role!=="user"||(s({sessionId:i.sessionId,agentType:i.agentType,sessionMode:i.sessionMode,managerConfig:i.managerConfig,workDir:i.workDir,agentSessionId:i.agentSessionId,modelId:i.modelId,managerDefaultWorkerAgentType:i.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:i.managerDefaultWorkerModelId,status:i.status,lastActivityAt:i.lastActivityAt,lastMessagePreview:i.lastMessagePreview,localReady:!0,firstUserMessageId:r.message.id,firstUserBodyRevision:r.revision}),n+=1)}return n}listManagedClaims(){return Object.values(this.readManagedEchoState().claims)}listManagedSuppressions(){return Object.values(this.readManagedEchoState().suppressions)}upsertManagedClaim(e){A(e);const s=this.readManagedEchoState();s.claims[e.canonicalMessageId]=e,p(this.managedEchoStateFile(),s)}updateManagedClaimSource(e,s,o){const n=this.readManagedEchoState();let i=!1;for(const c of Object.values(n.claims))c.sessionId!==e||c.sourceAgentType!==s||(c.sourceSessionKey=o,i=!0);i&&p(this.managedEchoStateFile(),n)}removeManagedClaim(e){const s=this.readManagedEchoState();s.claims[e]&&(delete s.claims[e],p(this.managedEchoStateFile(),s))}completeManagedClaim(e,s){const o=this.readManagedEchoState();delete o.claims[e],s&&(N(s),o.suppressions[b(s.agentType,s.sourceSessionKey)]=s),p(this.managedEchoStateFile(),o)}removeManagedSuppression(e,s){const o=this.readManagedEchoState(),n=b(e,s);o.suppressions[n]&&(delete o.suppressions[n],p(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[a,r]of Object.entries(s.sources))r.sessionId===e&&(delete s.sources[a],o=!0);o&&p(this.sourcesFile(),s);const n=this.readManagedEchoState();let i=!1;for(const[a,r]of Object.entries(n.claims))r.sessionId===e&&(delete n.claims[a],i=!0);for(const[a,r]of Object.entries(n.suppressions))r.sessionId===e&&(delete n.suppressions[a],i=!0);i&&p(this.managedEchoStateFile(),n);const c=u.join(this.rootDir(),"ledger");if(d.existsSync(c))for(const a of d.readdirSync(c,{withFileTypes:!0})){if(!a.isFile()||!a.name.endsWith(".json"))continue;const r=u.join(c,a.name),g=D(r);if(g.daemonInstallationId!==this.installationId())throw new Error("native event ledger belongs to another daemon installation");g.sessionId===e&&d.unlinkSync(r)}}isSourceInternal(e,s){const o=this.rootDir();return W(e,s,{filePath:u.join(u.basename(o)==="native-fusion"?u.dirname(o):o,"native-fusion-state.json"),daemonInstallationId:this.installationId()})}isSourceDeleted(e,s){return(this.options.deletionStore??new J({rootDir:u.basename(this.rootDir())==="native-fusion"?u.dirname(this.rootDir()):this.rootDir(),daemonInstallationId:this.installationId()})).has(e,s)}observeNativeTitle(e,s,o){if(this.isSourceInternal(e,s))return!1;const n=o.trim().slice(0,120);if(!n)return!1;const i=this.readSources().sources[b(e,s)];if(!i||this.isSourceDeleted(e,s))return!1;const c=(this.options.listLocalSessions??K)().find(g=>g.sessionId===i.sessionId);if(c?.titleLockedByUser||c?.titleSource==="agent_native"&&c.title===n)return!1;const a=y.createHash("sha256").update(`${e}\0${s}\0${n}`).digest("hex");return(this.options.recordLocalSession??w)({sessionId:i.sessionId,agentType:i.agentType,workDir:c?.workDir??"",agentSessionId:s,title:n,titleSource:"agent_native",titleOriginEventId:`agent-native:${a}`}),!0}recordIndex(e,s,o,n){const i=this.options.recordLocalSession??w,c=e.terminal?`terminal:${y.createHash("sha256").update(e.sourceEventKey).digest("hex")}`:void 0,a=e.role==="user"||e.role==="agent"?O(e.payload):null,r=a&&o?{lastMessagePreview:a,lastMessagePreviewMessageId:o,lastMessagePreviewAt:new Date(e.ts).toISOString()}:{},g=o&&(a||B(e.payload))?new Date(e.ts).toISOString():void 0;i({sessionId:s,agentType:e.agentType,workDir:e.workDir??"",agentSessionId:e.sourceSessionKey,modelId:e.modelId??null,status:e.terminal?e.terminalState==="failed"||e.terminalState==="aborted"?"failed":"completed":"active",attentionId:c,lastActivityAt:new Date(e.ts).toISOString(),lastVisibleEventAt:g,...r,...e.role==="user"&&o?{localReady:!0,firstUserMessageId:o,firstUserBodyRevision:n}:{},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(!d.existsSync(e))return{schemaVersion:E,daemonInstallationId:this.installationId(),sources:{}};const s=D(e);if(s.schemaVersion!==E||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??U()}canonicalStore(){return this.options.canonicalStore??H()}rootDir(){return this.options.rootDir??P("native-fusion")}sourcesFile(){return u.join(this.rootDir(),"sources.json")}managedEchoStateFile(){return u.join(this.rootDir(),"managed-echo-state.json")}readManagedEchoState(){const e=this.managedEchoStateFile();if(!d.existsSync(e))return{schemaVersion:E,daemonInstallationId:this.installationId(),claims:{},suppressions:{}};const s=D(e);if(s.schemaVersion!==E||s.daemonInstallationId!==this.installationId()||!s.claims||!s.suppressions)throw new Error("invalid managed echo state");for(const o of Object.values(s.claims))A(o);for(const o of Object.values(s.suppressions))N(o);return s}ledgerFile(e,s){const o=y.createHash("sha256").update(`${e}\0${s}`).digest("hex");return u.join(this.rootDir(),"ledger",`${o}.json`)}}function Y(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 y.createHash("sha256").update(`${t}\0${e}`).digest("hex")}function Z(t,e,s){return`native-session-${y.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function ee(t,e,s){return`native-${y.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function se(t){return y.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 A(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 N(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 te(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 n=typeof o.name=="string"?o.name:"",i=/(?:_output|_result)$/.test(n);return JSON.stringify({...s,...o,...i&&typeof s.name=="string"?{name:s.name}:{}})}catch{return e}}function oe(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 L(t,120)}function p(t,e){const s=u.dirname(t);d.mkdirSync(s,{recursive:!0,mode:448});const o=`${t}.tmp-${process.pid}-${y.randomBytes(6).toString("hex")}`;let n;try{n=d.openSync(o,"wx",384),d.writeFileSync(n,`${JSON.stringify(e,null,2)}
|
|
2
|
+
`,"utf8"),d.fsyncSync(n),d.closeSync(n),n=void 0,d.renameSync(o,t)}finally{n!==void 0&&d.closeSync(n);try{d.unlinkSync(o)}catch{}}}function D(t){return JSON.parse(d.readFileSync(t,"utf8"))}export{G as InternalNativeSourceError,z as MissingUserBoundaryError,X as NativeEventIdentityConflictError,pe as NativeFusionLocalRepository,Q as NativeSourceDeletedError};
|
|
@@ -17,6 +17,7 @@ export declare class NativeSessionFusionService {
|
|
|
17
17
|
private suppressionWindows;
|
|
18
18
|
private externalObservations;
|
|
19
19
|
private externalTerminalHandler;
|
|
20
|
+
private externalClaimHandler;
|
|
20
21
|
private ignoredSources;
|
|
21
22
|
constructor(client: CliRelayClient, repository?: NativeFusionLocalRepository);
|
|
22
23
|
/** Durably excludes short-lived validation/orchestration sessions from personal chat discovery. */
|
|
@@ -24,6 +25,7 @@ export declare class NativeSessionFusionService {
|
|
|
24
25
|
start(): void;
|
|
25
26
|
stop(): void;
|
|
26
27
|
setExternalTerminalHandler(handler: ((sessionId: string) => void) | null): void;
|
|
28
|
+
setExternalClaimHandler(handler: ((sessionId: string, canonicalMessageId: string) => void) | null): void;
|
|
27
29
|
restoreExternalObservation(params: {
|
|
28
30
|
sessionId: string;
|
|
29
31
|
sourceSessionKey: string;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
`).trim()}function C(){return new Promise(f=>setImmediate(f))}function ce(f){return f.split(/[\\/]+/).includes(".codex")}function F(f){try{return k.statSync(f).mtimeMs}catch{return 0}}class Se{client;repository;timer=null;activeTimer=null;scanPromise=null;queuedTargetFiles=new Set;pendingBoundaryFiles=new Set;sourceFiles=new Map;fullScanQueued=!1;watchEnabled=!1;lifecycleRevision=0;watcher=new z(e=>{this.scanNow(e).catch(s=>{console.error("[native-fusion] watched file scan failed",s)})});pendingClaims;suppressionWindows;externalObservations=new Map;externalTerminalHandler=null;ignoredSources=new Set;constructor(e,s=new H){this.client=e,this.repository=s,this.ignoredSources=new Set(Object.keys(B().ignoredSources??{})),this.pendingClaims=new Map((this.repository.listManagedClaims?.()??[]).map(t=>[t.canonicalMessageId,t])),this.suppressionWindows=new Map((this.repository.listManagedSuppressions?.()??[]).map(t=>[g(t.agentType,t.sourceSessionKey),t]))}ignoreNativeSource(e,s){if(!["codex","claude","opencode"].includes(e)||!s)return;const t=g(e,s);if(this.ignoredSources.has(t))return;this.ignoredSources.add(t);const n=B();for(const a of Object.keys(n.ignoredSources??{}))this.ignoredSources.add(a);n.ignoredSources=Object.fromEntries([...this.ignoredSources].sort().map(a=>[a,!0])),N(n)}start(){this.timer||(this.timer=setInterval(()=>{this.scanNow().catch(e=>{console.error("[native-fusion] periodic scan failed",e)})},se))}stop(){this.lifecycleRevision+=1,this.timer&&clearInterval(this.timer),this.timer=null,this.activeTimer&&clearInterval(this.activeTimer),this.activeTimer=null,this.pendingBoundaryFiles.clear(),this.watchEnabled=!1,this.watcher.stop()}setExternalTerminalHandler(e){this.externalTerminalHandler=e}restoreExternalObservation(e){if(!e.sessionId||!e.sourceSessionKey||!e.runId||!e.canonicalMessageId||!Number.isFinite(e.createdAt))return!1;const s=`codex:${e.sourceSessionKey}`,t=this.externalObservations.get(s)??[];if(t.some(u=>u.runId===e.runId))return!0;const n=Date.now(),a={...e,state:this.pendingClaims.has(e.canonicalMessageId)?"queued":"running",createdAt:Math.min(e.createdAt,n),lastPublishedAt:n};return t.push(a),t.sort((u,h)=>u.createdAt-h.createdAt),this.externalObservations.set(s,t),this.emitExternalActivity(a,a.state),this.ensureActiveScanning(!1),!0}handleConnected(){const e=++this.lifecycleRevision;this.watchEnabled=!0,this.scanNow().catch(s=>{console.error("[native-fusion] initial scan failed",s)}).finally(()=>{this.lifecycleRevision===e&&this.start()})}registerManagedSend(e){if(!e.canonicalMessageId)return;const s=e.sourceAgentType??e.agentType;if(s!=="codex"&&s!=="claude"&&s!=="opencode")return;const t=e.canonicalMessageId,n=this.pendingClaims.get(t),a=L(e.text);if(n&&(n.sessionId!==e.sessionId||n.sourceAgentType!==s||n.textHash!==a))throw new Error(`managed echo claim identity conflict: ${t}`);const u={sessionId:e.sessionId,agentType:e.agentType,sourceAgentType:s,canonicalMessageId:e.canonicalMessageId,textHash:a,createdAt:n?.createdAt??Date.now(),sourceSessionKey:e.sourceSessionKey??n?.sourceSessionKey??null,managedEchoPolicy:e.managedEchoPolicy??"suppress_following",externalRunId:e.externalRunId??null};if(this.repository.upsertManagedClaim?.(u),this.pendingClaims.set(t,u),e.externalRunId&&e.sourceSessionKey){const h=`${s}:${e.sourceSessionKey}`,y=this.externalObservations.get(h)??[];y.push({sessionId:e.sessionId,sourceSessionKey:e.sourceSessionKey,runId:e.externalRunId,canonicalMessageId:e.canonicalMessageId,state:"queued",createdAt:Date.now(),lastPublishedAt:Date.now()}),this.externalObservations.set(h,y),this.emitExternalActivity(y[y.length-1],"queued"),this.ensureActiveScanning()}}cancelManagedSend(e){this.repository.removeManagedClaim?.(e),this.pendingClaims.delete(e)}noteManagedSourceSession(e,s,t){if(t){this.repository.updateManagedClaimSource?.(e,s,t);for(const n of this.pendingClaims.values())n.sessionId===e&&n.sourceAgentType===s&&(n.sourceSessionKey=t)}}handleSessionDeleted(e){for(const[s,t]of this.pendingClaims.entries())t.sessionId===e&&this.pendingClaims.delete(s);for(const[s,t]of this.suppressionWindows.entries())t.sessionId===e&&this.suppressionWindows.delete(s);for(const[s,t]of this.externalObservations.entries()){const n=t.filter(a=>a.sessionId!==e);n.length===0?this.externalObservations.delete(s):this.externalObservations.set(s,n)}this.repository.purgeSession(e)}ensureActiveScanning(e=!0){this.activeTimer||(this.activeTimer=setInterval(()=>{if(this.externalObservations.size===0&&this.pendingBoundaryFiles.size===0){this.activeTimer&&clearInterval(this.activeTimer),this.activeTimer=null;return}this.scanActiveTargets()},ne),e&&this.scanActiveTargets())}scanActiveTargets(){const e=this.activeScanTargets();e===void 0&&this.scanPromise||this.scanNow(e).catch(s=>{console.error("[native-fusion] active observation scan failed",s)})}activeScanTargets(){const e=[...this.externalObservations.keys()];if(!e.some(s=>!this.sourceFiles.has(s)))return[...new Set([...this.pendingBoundaryFiles,...e.flatMap(s=>{const t=this.sourceFiles.get(s);return t?[t]:[]})])]}emitExternalActivity(e,s){this.client.sendAgentEvent({type:"event",event:"agent",id:`external-owner-${e.runId}-${s}-${e.lastPublishedAt}`,payload:{state:s==="queued"?"start":"heartbeat",sessionId:e.sessionId,runId:e.runId,seq:s==="queued"?0:1,runPhase:"thinking",canStop:!1,owner:"external",ownerLabel:"Codex Desktop",lifecycle:s}})}emitExternalTerminal(e,s="completed"){const t=s==="completed"?"final":s==="aborted"?"aborted":"error";this.client.sendAgentEvent({type:"event",event:"agent",id:`external-owner-${e.runId}-${t}`,payload:{state:t,sessionId:e.sessionId,runId:e.runId,seq:2,...t==="error"?{message:"Codex Desktop turn failed"}:{},owner:"external",ownerLabel:"Codex Desktop"}}),this.externalTerminalHandler?.(e.sessionId)}pruneState(){const e=Date.now();for(const[s,t]of this.suppressionWindows.entries())t.endsAt>e||(this.repository.removeManagedSuppression?.(t.agentType,t.sourceSessionKey),this.suppressionWindows.delete(s))}async scanNow(e){if(this.client.getState()==="connected"){if(this.scanPromise){const s=this.scanPromise;if(e)for(const t of e)this.queuedTargetFiles.add(t);else this.fullScanQueued=!0;if(await s,this.fullScanQueued)return this.fullScanQueued=!1,this.queuedTargetFiles.clear(),this.scanNow();if(this.queuedTargetFiles.size>0){const t=[...this.queuedTargetFiles];return this.queuedTargetFiles.clear(),this.scanNow(t)}return}return this.scanPromise=this.runScanLoop(e).finally(()=>{this.scanPromise=null}),this.scanPromise}}async runScanLoop(e){let s=e?[...new Set(e)]:void 0;for(;;){if(await this.runScan(s),this.fullScanQueued){await C(),this.fullScanQueued=!1,this.queuedTargetFiles.clear(),s=void 0;continue}if(this.queuedTargetFiles.size>0){await C(),s=[...this.queuedTargetFiles],this.queuedTargetFiles.clear();continue}return}}async runScan(e){this.pruneState(),this.refreshExternalObservations();const s=B();for(const o of Object.keys(s.ignoredSources??{}))this.ignoredSources.add(o);s.ignoredSources=Object.fromEntries([...this.ignoredSources].sort().map(o=>[o,!0]));const t={...s.files},n=new Map;let a=!1;const u=o=>{const c=new Set,w=l=>{try{this.repository.commitEvents(l)}catch(r){if(!(r instanceof U))throw r;const p=l.find(E=>E.sourceEventKey===r.sourceEventKey);if(!p)throw r;c.add(p.sourceEventKey),console.warn(`[native-fusion] quarantined conflicting native event ${p.sourceEventKey}`);const I=l.filter(E=>E!==p);I.length>0&&w(I)}};return w(o),c},h=e?[...e]:[...V(),...j(),...Q()].sort((o,c)=>F(c)-F(o));!e&&this.watchEnabled&&this.watcher.refresh(h,Y());let y=0,T=!1;for(;h.length>0||T&&this.queuedTargetFiles.size>0;){let o;if(T&&this.queuedTargetFiles.size>0){if(o=[...this.queuedTargetFiles].sort((i,d)=>F(d)-F(i))[0],o){this.queuedTargetFiles.delete(o);const i=h.indexOf(o);i>=0&&h.splice(i,1)}T=!1}else o=h.shift(),T=!0;if(!o)break;if(y>0&&y%oe===0&&await C(),y+=1,!k.existsSync(o)){this.watchEnabled&&this.watcher.untrackFile(o),this.pendingBoundaryFiles.delete(o);for(const[i,d]of this.sourceFiles.entries())d===o&&this.sourceFiles.delete(i);continue}this.watchEnabled&&this.watcher.trackFile(o);const c=k.statSync(o),w=ce(o),l=o.endsWith(".opencode-session.json");let r=s.files[o];const p=`${String(c.dev)}:${String(c.ino)}:${String(c.birthtimeMs)}`;let I=!r;if(r&&!l&&(c.size<r.offset||r.fileIdentity&&r.fileIdentity!==p)&&(r=void 0,I=!0),!!(r&&r.fileIdentity===p&&r.mtimeMs===c.mtimeMs&&r.offset>=c.size&&r.sources?.length)&&r){for(const i of r.sources??[])this.sourceFiles.set(g(i.agentType,i.sourceSessionKey),o);r.classification==="live"&&r.awaitingUserBoundary?this.pendingBoundaryFiles.add(o):this.pendingBoundaryFiles.delete(o);continue}const m=r?.sources?.length&&!l?{status:"ready",createdAtMs:r.sessionCreatedAtMs??c.birthtimeMs,sources:r.sources}:X(o);if(m.status==="pending"){c.birthtimeMs>=s.fusionEpochMs&&this.pendingBoundaryFiles.add(o);continue}for(const i of m.sources)this.sourceFiles.set(g(i.agentType,i.sourceSessionKey),o);if(!l&&m.sources.length>0&&m.sources.every(i=>this.repository.isSourceDeleted?.(i.agentType,i.sourceSessionKey)||this.ignoredSources.has(g(i.agentType,i.sourceSessionKey)))){t[o]={...r??{daemonInstallationId:s.daemonInstallationId,classification:m.createdAtMs>=s.fusionEpochMs?"live":"historical",awaitingUserBoundary:!0,sessionCreatedAtMs:m.createdAtMs,sourceBoundaryEstablished:{}},offset:c.size,mtimeMs:c.mtimeMs,fileIdentity:p};continue}if(!r)r={daemonInstallationId:s.daemonInstallationId,offset:0,mtimeMs:c.mtimeMs,classification:m.createdAtMs>=s.fusionEpochMs?"live":"historical",awaitingUserBoundary:!0,sessionCreatedAtMs:m.createdAtMs,sourceBoundaryEstablished:{}};else if(l&&r.mtimeMs===c.mtimeMs)continue;const x=l?0:r.offset;let S;try{if(S=l?ee(o,x):w?await Z(o,x):await J(o,x),!l&&S.nextOffset<x)throw new Error("native transcript cursor moved backwards")}catch(i){console.error("[native-fusion] target file scan failed; isolated for retry",i);continue}let A,v;if(l){const i=ue(S.events,r,s.fusionEpochMs,I,c.mtimeMs,S.nextOffset);A=i.events,v=i.cursor}else if(I&&r.classification==="historical")A=[],v={...r,offset:S.nextOffset,mtimeMs:c.mtimeMs,awaitingUserBoundary:!0};else{const i=le(S.events,r,S.nextOffset,c.mtimeMs);A=i.events,v=i.cursor}v.fileIdentity=p,v.sources=m.sources;const b=[],O=new Map,P=new Map;for(const i of A){if(this.ignoredSources.has(g(i.agentType,i.sourceSessionKey))||this.repository.isSourceDeleted?.(i.agentType,i.sourceSessionKey))continue;const d=this.tryClaimManagedEcho(i);if(d){b.push(d.event),O.set(d.claimKey,d.suppression??null),d.suppression&&P.set(g(d.suppression.agentType,d.suppression.sourceSessionKey),{window:d.suppression,claimKey:d.claimKey});continue}const K=this.suppressionFor(i,P,O);if(K){b.push({...i,claimSessionId:K.sessionId,localDisposition:"managed_echo_suppressed"});continue}b.push(i)}const M=b.sort((i,d)=>i.ts-d.ts),_=new Set;try{for(let i=0;i<M.length;i+=q){const d=u(M.slice(i,i+q));for(const K of d)_.add(K);a=!0,i+q<M.length&&await C()}}catch(i){console.error("[native-fusion] target file commit failed; isolated for retry",i);continue}const $=new Set(M.filter(i=>_.has(i.sourceEventKey)).flatMap(i=>i.claimCanonicalMessageId?[i.claimCanonicalMessageId]:[]));for(const[i,d]of O)$.has(i)||(this.repository.completeManagedClaim?.(i,d??void 0),this.pendingClaims.delete(i),d&&this.suppressionWindows.set(g(d.agentType,d.sourceSessionKey),d));for(const i of S.events)this.ignoredSources.has(g(i.agentType,i.sourceSessionKey))||this.repository.isSourceDeleted?.(i.agentType,i.sourceSessionKey)||i.title?.trim()&&i.titleSource==="agent_native"&&n.set(`${i.agentType}:${i.sourceSessionKey}`,i);for(const i of M)_.has(i.sourceEventKey)||this.observeExternalLifecycle(i);t[o]=v,s.files=t,N(s),v.classification==="live"&&v.awaitingUserBoundary?this.pendingBoundaryFiles.add(o):this.pendingBoundaryFiles.delete(o),M.length>0&&await R(this.client).catch(i=>{console.error("[native-fusion] index projection sync failed; retained for retry",i)})}a||this.repository.commitEvents([]);for(const o of n.values())this.repository.observeNativeTitle(o.agentType,o.sourceSessionKey,o.title??"");for(const o of this.repository.listSourceBindings()){if(o.agentType!=="codex")continue;const c=G(o.sourceSessionKey);c&&this.repository.observeNativeTitle("codex",o.sourceSessionKey,c)}s.files=t,N(s),this.pendingBoundaryFiles.size>0&&this.ensureActiveScanning(!1),this.repository.repairMissingUserBoundaries?.(),await R(this.client).catch(o=>{console.error("[native-fusion] index projection sync failed; retained for retry",o)})}refreshExternalObservations(){const e=Date.now();for(const[s,t]of this.externalObservations.entries()){const n=t.filter(u=>e-u.createdAt<D);if(n.length===0){this.externalObservations.delete(s);continue}this.externalObservations.set(s,n);const a=n[0];e-a.lastPublishedAt<re||(a.lastPublishedAt=e,this.emitExternalActivity(a,a.state))}}tryClaimManagedEcho(e){const s=Date.now();for(const[t,n]of this.pendingClaims.entries()){if(n.sourceAgentType!==e.agentType||e.role!=="user"||!n.sourceSessionKey||n.sourceSessionKey!==e.sourceSessionKey)continue;const a=n.externalRunId?D:te;if(Math.abs(e.ts-n.createdAt)>a||L(e.payload)!==n.textHash)continue;const u=n.managedEchoPolicy==="suppress_following"?{sessionId:n.sessionId,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,claimSourceEventKey:e.sourceEventKey,startedAt:e.ts,endsAt:Math.max(s,e.ts)+ie}:void 0;return{claimKey:t,suppression:u,event:{...e,sourceMode:"managed_echo_claim",claimCanonicalMessageId:n.canonicalMessageId,claimSessionId:n.sessionId,managedEchoPolicy:n.managedEchoPolicy}}}return null}observeExternalLifecycle(e){const s=`${e.agentType}:${e.sourceSessionKey}`,t=this.externalObservations.get(s);if(!t?.length)return;const n=t[0];if(e.claimCanonicalMessageId===n.canonicalMessageId&&n.state==="queued"&&(this.repository.updateMessageDeliveryState(n.sessionId,n.canonicalMessageId,"executing"),n.state="running",n.lastPublishedAt=Date.now(),this.emitExternalActivity(n,"running")),!e.terminal||n.state!=="running")return;const a=e.terminalState??"completed";this.repository.updateMessageDeliveryState(n.sessionId,n.canonicalMessageId,a==="aborted"?"cancelled":a==="failed"?"failed":"completed"),this.emitExternalTerminal(n,a),t.shift(),t.length===0?this.externalObservations.delete(s):(this.externalObservations.set(s,t),t[0].lastPublishedAt=Date.now(),this.emitExternalActivity(t[0],"queued"))}suppressionFor(e,s,t){const n=g(e.agentType,e.sourceSessionKey),a=s.get(n),u=a?.window??this.suppressionWindows.get(n);return!u||e.ts<u.startedAt?null:e.ts>u.endsAt?(this.clearSuppression(n,u,a,s,t),null):e.role==="user"?(e.sourceEventKey===u.claimSourceEventKey||this.clearSuppression(n,u,a,s,t),null):u}clearSuppression(e,s,t,n,a){if(t){n.delete(e),a.set(t.claimKey,null);return}this.repository.removeManagedSuppression?.(s.agentType,s.sourceSessionKey),this.suppressionWindows.delete(e)}}function L(f){return W.createHash("sha256").update(ae(f)).digest("hex")}function g(f,e){return`${f}:${e}`}function le(f,e,s,t){if(!e.awaitingUserBoundary)return{events:f,cursor:{...e,offset:s,mtimeMs:t}};if(f.length===0)return{events:[],cursor:{...e,offset:s,mtimeMs:t,awaitingUserBoundary:!0}};const n=f.findIndex(a=>a.role==="user");return n<0?{events:[],cursor:{...e,mtimeMs:t,awaitingUserBoundary:!0}}:{events:f.slice(n),cursor:{...e,offset:s,mtimeMs:t,awaitingUserBoundary:!1}}}function ue(f,e,s,t,n,a){const u={...e.sourceBoundaryEstablished??{}},h=e.eventWatermarkTs??-1,y=new Set(e.eventKeysAtWatermark??[]),T=t?f:f.filter(l=>l.ts>h||l.ts===h&&!y.has(l.sourceEventKey)),o=[];for(const l of T){const r=l.sourceSessionKey;if(u[r]){o.push(l);continue}!(!t||(l.sourceSessionCreatedAtMs??0)>=s)||l.role!=="user"||(u[r]=!0,o.push(l))}const c=f.reduce((l,r)=>Math.max(l,r.ts),h),w=new Set(c===h?e.eventKeysAtWatermark??[]:[]);for(const l of f)l.ts===c&&w.add(l.sourceEventKey);return{events:o,cursor:{...e,offset:a,mtimeMs:n,eventWatermarkTs:c>=0?c:void 0,eventKeysAtWatermark:c>=0?[...w]:void 0,sourceBoundaryEstablished:u}}}export{Se as NativeSessionFusionService};
|
|
1
|
+
import W from"node:crypto";import N from"node:fs";import{listSessionRecords as H}from"../session/store.js";import{knownInternalNativeSources as z,knownJournalInternalNativeSources as U,registerInternalNativeSource as j}from"./source-visibility.js";import{publishPendingSessionIndexes as R}from"../session/index-publisher.js";import{NativeFusionFileWatcher as V}from"./file-watcher.js";import{NativeEventIdentityConflictError as Q,NativeFusionLocalRepository as Y}from"./local-repository.js";import{inspectNativeSessionFile as J,listClaudeTranscriptFiles as X,listCodexRolloutFiles as G,listNativeTranscriptRoots as Z,listOpenCodeSessionFiles as ee,lookupCodexThreadName as se,parseClaudeTranscriptChunkCooperatively as te,parseCodexRolloutChunkCooperatively as ie,parseOpenCodeSessionFile as ne}from"./parsers.js";import{loadNativeScannerState as C,saveNativeScannerState as B}from"./state.js";const oe=5*6e4,re=2*6e4,ae=30*6e4,ce=2e3,le=1,q=16,ue=3e4,D=240*6e4;function de(f){return f.replace(/\r\n/g,`
|
|
2
|
+
`).trim()}function F(){return new Promise(f=>setImmediate(f))}function fe(f){return f.split(/[\\/]+/).includes(".codex")}function k(f){try{return N.statSync(f).mtimeMs}catch{return 0}}class Me{client;repository;timer=null;activeTimer=null;scanPromise=null;queuedTargetFiles=new Set;pendingBoundaryFiles=new Set;sourceFiles=new Map;fullScanQueued=!1;watchEnabled=!1;lifecycleRevision=0;watcher=new V(e=>{this.scanNow(e).catch(s=>{console.error("[native-fusion] watched file scan failed",s)})});pendingClaims;suppressionWindows;externalObservations=new Map;externalTerminalHandler=null;externalClaimHandler=null;ignoredSources=new Set;constructor(e,s=new Y){this.client=e,this.repository=s;const n=C();for(const t of[...z(H()),...U(n.daemonInstallationId)])j(t.sessionId,t.agentType,t.sourceSessionKey);this.ignoredSources=new Set(Object.keys(C().ignoredSources??{})),this.pendingClaims=new Map((this.repository.listManagedClaims?.()??[]).map(t=>[t.canonicalMessageId,t])),this.suppressionWindows=new Map((this.repository.listManagedSuppressions?.()??[]).map(t=>[g(t.agentType,t.sourceSessionKey),t]))}ignoreNativeSource(e,s){if(!["codex","claude","opencode"].includes(e)||!s)return;const n=g(e,s);if(this.ignoredSources.has(n))return;this.ignoredSources.add(n);const t=C();for(const r of Object.keys(t.ignoredSources??{}))this.ignoredSources.add(r);t.ignoredSources=Object.fromEntries([...this.ignoredSources].sort().map(r=>[r,!0])),B(t)}start(){this.timer||(this.timer=setInterval(()=>{this.scanNow().catch(e=>{console.error("[native-fusion] periodic scan failed",e)})},oe))}stop(){this.lifecycleRevision+=1,this.timer&&clearInterval(this.timer),this.timer=null,this.activeTimer&&clearInterval(this.activeTimer),this.activeTimer=null,this.pendingBoundaryFiles.clear(),this.watchEnabled=!1,this.watcher.stop()}setExternalTerminalHandler(e){this.externalTerminalHandler=e}setExternalClaimHandler(e){if(this.externalClaimHandler=e,!!e)for(const s of this.externalObservations.values())for(const n of s)n.state==="running"&&e(n.sessionId,n.canonicalMessageId)}restoreExternalObservation(e){if(!e.sessionId||!e.sourceSessionKey||!e.runId||!e.canonicalMessageId||!Number.isFinite(e.createdAt))return!1;const s=`codex:${e.sourceSessionKey}`,n=this.externalObservations.get(s)??[];if(n.some(u=>u.runId===e.runId))return!0;const t=Date.now(),r={...e,state:this.pendingClaims.has(e.canonicalMessageId)?"queued":"running",createdAt:Math.min(e.createdAt,t),lastPublishedAt:t};return n.push(r),n.sort((u,h)=>u.createdAt-h.createdAt),this.externalObservations.set(s,n),this.emitExternalActivity(r,r.state),r.state==="running"&&this.externalClaimHandler?.(r.sessionId,r.canonicalMessageId),this.ensureActiveScanning(!1),!0}handleConnected(){const e=++this.lifecycleRevision;this.watchEnabled=!0,this.scanNow().catch(s=>{console.error("[native-fusion] initial scan failed",s)}).finally(()=>{this.lifecycleRevision===e&&this.start()})}registerManagedSend(e){if(!e.canonicalMessageId)return;const s=e.sourceAgentType??e.agentType;if(s!=="codex"&&s!=="claude"&&s!=="opencode")return;const n=e.canonicalMessageId,t=this.pendingClaims.get(n),r=L(e.text);if(t&&(t.sessionId!==e.sessionId||t.sourceAgentType!==s||t.textHash!==r))throw new Error(`managed echo claim identity conflict: ${n}`);const u={sessionId:e.sessionId,agentType:e.agentType,sourceAgentType:s,canonicalMessageId:e.canonicalMessageId,textHash:r,createdAt:t?.createdAt??Date.now(),sourceSessionKey:e.sourceSessionKey??t?.sourceSessionKey??null,managedEchoPolicy:e.managedEchoPolicy??"suppress_following",externalRunId:e.externalRunId??null};if(this.repository.upsertManagedClaim?.(u),this.pendingClaims.set(n,u),e.externalRunId&&e.sourceSessionKey){const h=`${s}:${e.sourceSessionKey}`,y=this.externalObservations.get(h)??[];y.push({sessionId:e.sessionId,sourceSessionKey:e.sourceSessionKey,runId:e.externalRunId,canonicalMessageId:e.canonicalMessageId,state:"queued",createdAt:Date.now(),lastPublishedAt:Date.now()}),this.externalObservations.set(h,y),this.emitExternalActivity(y[y.length-1],"queued"),this.ensureActiveScanning()}}cancelManagedSend(e){this.repository.removeManagedClaim?.(e),this.pendingClaims.delete(e)}noteManagedSourceSession(e,s,n){if(n){this.repository.updateManagedClaimSource?.(e,s,n);for(const t of this.pendingClaims.values())t.sessionId===e&&t.sourceAgentType===s&&(t.sourceSessionKey=n)}}handleSessionDeleted(e){for(const[s,n]of this.pendingClaims.entries())n.sessionId===e&&this.pendingClaims.delete(s);for(const[s,n]of this.suppressionWindows.entries())n.sessionId===e&&this.suppressionWindows.delete(s);for(const[s,n]of this.externalObservations.entries()){const t=n.filter(r=>r.sessionId!==e);t.length===0?this.externalObservations.delete(s):this.externalObservations.set(s,t)}this.repository.purgeSession(e)}ensureActiveScanning(e=!0){this.activeTimer||(this.activeTimer=setInterval(()=>{if(this.externalObservations.size===0&&this.pendingBoundaryFiles.size===0){this.activeTimer&&clearInterval(this.activeTimer),this.activeTimer=null;return}this.scanActiveTargets()},ce),e&&this.scanActiveTargets())}scanActiveTargets(){const e=this.activeScanTargets();e===void 0&&this.scanPromise||this.scanNow(e).catch(s=>{console.error("[native-fusion] active observation scan failed",s)})}activeScanTargets(){const e=[...this.externalObservations.keys()];if(!e.some(s=>!this.sourceFiles.has(s)))return[...new Set([...this.pendingBoundaryFiles,...e.flatMap(s=>{const n=this.sourceFiles.get(s);return n?[n]:[]})])]}emitExternalActivity(e,s){this.client.sendAgentEvent({type:"event",event:"agent",id:`external-owner-${e.runId}-${s}-${e.lastPublishedAt}`,payload:{state:s==="queued"?"start":"heartbeat",sessionId:e.sessionId,runId:e.runId,seq:s==="queued"?0:1,runPhase:"thinking",canStop:!1,owner:"external",ownerLabel:"Codex Desktop",lifecycle:s}})}emitExternalTerminal(e,s="completed"){const n=s==="completed"?"final":s==="aborted"?"aborted":"error";this.client.sendAgentEvent({type:"event",event:"agent",id:`external-owner-${e.runId}-${n}`,payload:{state:n,sessionId:e.sessionId,runId:e.runId,seq:2,...n==="error"?{message:"Codex Desktop turn failed"}:{},owner:"external",ownerLabel:"Codex Desktop"}}),this.externalTerminalHandler?.(e.sessionId)}pruneState(){const e=Date.now();for(const[s,n]of this.suppressionWindows.entries())n.endsAt>e||(this.repository.removeManagedSuppression?.(n.agentType,n.sourceSessionKey),this.suppressionWindows.delete(s))}async scanNow(e){if(this.client.getState()==="connected"){if(this.scanPromise){const s=this.scanPromise;if(e)for(const n of e)this.queuedTargetFiles.add(n);else this.fullScanQueued=!0;if(await s,this.fullScanQueued)return this.fullScanQueued=!1,this.queuedTargetFiles.clear(),this.scanNow();if(this.queuedTargetFiles.size>0){const n=[...this.queuedTargetFiles];return this.queuedTargetFiles.clear(),this.scanNow(n)}return}return this.scanPromise=this.runScanLoop(e).finally(()=>{this.scanPromise=null}),this.scanPromise}}async runScanLoop(e){let s=e?[...new Set(e)]:void 0;for(;;){if(await this.runScan(s),this.fullScanQueued){await F(),this.fullScanQueued=!1,this.queuedTargetFiles.clear(),s=void 0;continue}if(this.queuedTargetFiles.size>0){await F(),s=[...this.queuedTargetFiles],this.queuedTargetFiles.clear();continue}return}}async runScan(e){this.pruneState(),this.refreshExternalObservations();const s=C();for(const o of Object.keys(s.ignoredSources??{}))this.ignoredSources.add(o);s.ignoredSources=Object.fromEntries([...this.ignoredSources].sort().map(o=>[o,!0]));const n={...s.files},t=new Map;let r=!1;const u=o=>{const c=new Set,w=l=>{try{this.repository.commitEvents(l)}catch(a){if(!(a instanceof Q))throw a;const p=l.find(M=>M.sourceEventKey===a.sourceEventKey);if(!p)throw a;c.add(p.sourceEventKey),console.warn(`[native-fusion] quarantined conflicting native event ${p.sourceEventKey}`);const T=l.filter(M=>M!==p);T.length>0&&w(T)}};return w(o),c},h=e?[...e]:[...G(),...X(),...ee()].sort((o,c)=>k(c)-k(o));!e&&this.watchEnabled&&this.watcher.refresh(h,Z());let y=0,I=!1;for(;h.length>0||I&&this.queuedTargetFiles.size>0;){let o;if(I&&this.queuedTargetFiles.size>0){if(o=[...this.queuedTargetFiles].sort((i,d)=>k(d)-k(i))[0],o){this.queuedTargetFiles.delete(o);const i=h.indexOf(o);i>=0&&h.splice(i,1)}I=!1}else o=h.shift(),I=!0;if(!o)break;if(y>0&&y%le===0&&await F(),y+=1,!N.existsSync(o)){this.watchEnabled&&this.watcher.untrackFile(o),this.pendingBoundaryFiles.delete(o);for(const[i,d]of this.sourceFiles.entries())d===o&&this.sourceFiles.delete(i);continue}this.watchEnabled&&this.watcher.trackFile(o);const c=N.statSync(o),w=fe(o),l=o.endsWith(".opencode-session.json");let a=s.files[o];const p=`${String(c.dev)}:${String(c.ino)}:${String(c.birthtimeMs)}`;let T=!a;if(a&&!l&&(c.size<a.offset||a.fileIdentity&&a.fileIdentity!==p)&&(a=void 0,T=!0),!!(a&&a.fileIdentity===p&&a.mtimeMs===c.mtimeMs&&a.offset>=c.size&&a.sources?.length)&&a){for(const i of a.sources??[])this.sourceFiles.set(g(i.agentType,i.sourceSessionKey),o);a.classification==="live"&&a.awaitingUserBoundary?this.pendingBoundaryFiles.add(o):this.pendingBoundaryFiles.delete(o);continue}const m=a?.sources?.length&&!l?{status:"ready",createdAtMs:a.sessionCreatedAtMs??c.birthtimeMs,sources:a.sources}:J(o);if(m.status==="pending"){c.birthtimeMs>=s.fusionEpochMs&&this.pendingBoundaryFiles.add(o);continue}for(const i of m.sources)this.sourceFiles.set(g(i.agentType,i.sourceSessionKey),o);if(!l&&m.sources.length>0&&m.sources.every(i=>this.repository.isSourceDeleted?.(i.agentType,i.sourceSessionKey)||this.repository.isSourceInternal?.(i.agentType,i.sourceSessionKey)||this.ignoredSources.has(g(i.agentType,i.sourceSessionKey)))){n[o]={...a??{daemonInstallationId:s.daemonInstallationId,classification:m.createdAtMs>=s.fusionEpochMs?"live":"historical",awaitingUserBoundary:!0,sessionCreatedAtMs:m.createdAtMs,sourceBoundaryEstablished:{}},offset:c.size,mtimeMs:c.mtimeMs,fileIdentity:p};continue}if(!a)a={daemonInstallationId:s.daemonInstallationId,offset:0,mtimeMs:c.mtimeMs,classification:m.createdAtMs>=s.fusionEpochMs?"live":"historical",awaitingUserBoundary:!0,sessionCreatedAtMs:m.createdAtMs,sourceBoundaryEstablished:{}};else if(l&&a.mtimeMs===c.mtimeMs)continue;const E=l?0:a.offset;let S;try{if(S=l?ne(o,E):w?await ie(o,E):await te(o,E),!l&&S.nextOffset<E)throw new Error("native transcript cursor moved backwards")}catch(i){console.error("[native-fusion] target file scan failed; isolated for retry",i);continue}let A,v;if(l){const i=ye(S.events,a,s.fusionEpochMs,T,c.mtimeMs,S.nextOffset);A=i.events,v=i.cursor}else if(T&&a.classification==="historical")A=[],v={...a,offset:S.nextOffset,mtimeMs:c.mtimeMs,awaitingUserBoundary:!0};else{const i=he(S.events,a,S.nextOffset,c.mtimeMs);A=i.events,v=i.cursor}v.fileIdentity=p,v.sources=m.sources;const b=[],O=new Map,P=new Map;for(const i of A){if(this.ignoredSources.has(g(i.agentType,i.sourceSessionKey))||this.repository.isSourceDeleted?.(i.agentType,i.sourceSessionKey)||this.repository.isSourceInternal?.(i.agentType,i.sourceSessionKey))continue;const d=this.tryClaimManagedEcho(i);if(d){b.push(d.event),O.set(d.claimKey,d.suppression??null),d.suppression&&P.set(g(d.suppression.agentType,d.suppression.sourceSessionKey),{window:d.suppression,claimKey:d.claimKey});continue}const K=this.suppressionFor(i,P,O);if(K){b.push({...i,claimSessionId:K.sessionId,localDisposition:"managed_echo_suppressed"});continue}b.push(i)}const x=b.sort((i,d)=>i.ts-d.ts),_=new Set;try{for(let i=0;i<x.length;i+=q){const d=u(x.slice(i,i+q));for(const K of d)_.add(K);r=!0,i+q<x.length&&await F()}}catch(i){console.error("[native-fusion] target file commit failed; isolated for retry",i);continue}const $=new Set(x.filter(i=>_.has(i.sourceEventKey)).flatMap(i=>i.claimCanonicalMessageId?[i.claimCanonicalMessageId]:[]));for(const[i,d]of O)$.has(i)||(this.repository.completeManagedClaim?.(i,d??void 0),this.pendingClaims.delete(i),d&&this.suppressionWindows.set(g(d.agentType,d.sourceSessionKey),d));for(const i of S.events)this.ignoredSources.has(g(i.agentType,i.sourceSessionKey))||this.repository.isSourceDeleted?.(i.agentType,i.sourceSessionKey)||this.repository.isSourceInternal?.(i.agentType,i.sourceSessionKey)||i.title?.trim()&&i.titleSource==="agent_native"&&t.set(`${i.agentType}:${i.sourceSessionKey}`,i);for(const i of x)_.has(i.sourceEventKey)||this.observeExternalLifecycle(i);n[o]=v,s.files=n,B(s),v.classification==="live"&&v.awaitingUserBoundary?this.pendingBoundaryFiles.add(o):this.pendingBoundaryFiles.delete(o),x.length>0&&await R(this.client).catch(i=>{console.error("[native-fusion] index projection sync failed; retained for retry",i)})}r||this.repository.commitEvents([]);for(const o of t.values())this.repository.observeNativeTitle(o.agentType,o.sourceSessionKey,o.title??"");for(const o of this.repository.listSourceBindings()){if(o.agentType!=="codex")continue;const c=se(o.sourceSessionKey);c&&this.repository.observeNativeTitle("codex",o.sourceSessionKey,c)}s.files=n,B(s),this.pendingBoundaryFiles.size>0&&this.ensureActiveScanning(!1),this.repository.repairMissingUserBoundaries?.(),await R(this.client).catch(o=>{console.error("[native-fusion] index projection sync failed; retained for retry",o)})}refreshExternalObservations(){const e=Date.now();for(const[s,n]of this.externalObservations.entries()){const t=n.filter(u=>e-u.createdAt<D);if(t.length===0){this.externalObservations.delete(s);continue}this.externalObservations.set(s,t);const r=t[0];e-r.lastPublishedAt<ue||(r.lastPublishedAt=e,this.emitExternalActivity(r,r.state))}}tryClaimManagedEcho(e){const s=Date.now();for(const[n,t]of this.pendingClaims.entries()){if(t.sourceAgentType!==e.agentType||e.role!=="user"||!t.sourceSessionKey||t.sourceSessionKey!==e.sourceSessionKey)continue;const r=t.externalRunId?D:re;if(Math.abs(e.ts-t.createdAt)>r||L(e.payload)!==t.textHash)continue;const u=t.managedEchoPolicy==="suppress_following"?{sessionId:t.sessionId,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,claimSourceEventKey:e.sourceEventKey,startedAt:e.ts,endsAt:Math.max(s,e.ts)+ae}:void 0;return{claimKey:n,suppression:u,event:{...e,sourceMode:"managed_echo_claim",claimCanonicalMessageId:t.canonicalMessageId,claimSessionId:t.sessionId,managedEchoPolicy:t.managedEchoPolicy}}}return null}observeExternalLifecycle(e){const s=`${e.agentType}:${e.sourceSessionKey}`,n=this.externalObservations.get(s);if(!n?.length)return;const t=n[0];if(e.claimCanonicalMessageId===t.canonicalMessageId&&t.state==="queued"&&(this.repository.updateMessageDeliveryState(t.sessionId,t.canonicalMessageId,"executing"),this.externalClaimHandler?.(t.sessionId,t.canonicalMessageId),t.state="running",t.lastPublishedAt=Date.now(),this.emitExternalActivity(t,"running")),!e.terminal||t.state!=="running")return;const r=e.terminalState??"completed";this.repository.updateMessageDeliveryState(t.sessionId,t.canonicalMessageId,r==="aborted"?"cancelled":r==="failed"?"failed":"completed"),this.emitExternalTerminal(t,r),n.shift(),n.length===0?this.externalObservations.delete(s):(this.externalObservations.set(s,n),n[0].lastPublishedAt=Date.now(),this.emitExternalActivity(n[0],"queued"))}suppressionFor(e,s,n){const t=g(e.agentType,e.sourceSessionKey),r=s.get(t),u=r?.window??this.suppressionWindows.get(t);return!u||e.ts<u.startedAt?null:e.ts>u.endsAt?(this.clearSuppression(t,u,r,s,n),null):e.role==="user"?(e.sourceEventKey===u.claimSourceEventKey||this.clearSuppression(t,u,r,s,n),null):u}clearSuppression(e,s,n,t,r){if(n){t.delete(e),r.set(n.claimKey,null);return}this.repository.removeManagedSuppression?.(s.agentType,s.sourceSessionKey),this.suppressionWindows.delete(e)}}function L(f){return W.createHash("sha256").update(de(f)).digest("hex")}function g(f,e){return`${f}:${e}`}function he(f,e,s,n){if(!e.awaitingUserBoundary)return{events:f,cursor:{...e,offset:s,mtimeMs:n}};if(f.length===0)return{events:[],cursor:{...e,offset:s,mtimeMs:n,awaitingUserBoundary:!0}};const t=f.findIndex(r=>r.role==="user");return t<0?{events:[],cursor:{...e,mtimeMs:n,awaitingUserBoundary:!0}}:{events:f.slice(t),cursor:{...e,offset:s,mtimeMs:n,awaitingUserBoundary:!1}}}function ye(f,e,s,n,t,r){const u={...e.sourceBoundaryEstablished??{}},h=e.eventWatermarkTs??-1,y=new Set(e.eventKeysAtWatermark??[]),I=n?f:f.filter(l=>l.ts>h||l.ts===h&&!y.has(l.sourceEventKey)),o=[];for(const l of I){const a=l.sourceSessionKey;if(u[a]){o.push(l);continue}!(!n||(l.sourceSessionCreatedAtMs??0)>=s)||l.role!=="user"||(u[a]=!0,o.push(l))}const c=f.reduce((l,a)=>Math.max(l,a.ts),h),w=new Set(c===h?e.eventKeysAtWatermark??[]:[]);for(const l of f)l.ts===c&&w.add(l.sourceEventKey);return{events:o,cursor:{...e,offset:r,mtimeMs:t,eventWatermarkTs:c>=0?c:void 0,eventKeysAtWatermark:c>=0?[...w]:void 0,sourceBoundaryEstablished:u}}}export{Me as NativeSessionFusionService};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** No titles, message contents, Codex databases or deletion APIs are read. */
|
|
2
|
+
export declare function auditInternalNativeVisibility(root?: string): {
|
|
3
|
+
mode: "dry-run";
|
|
4
|
+
counts: {
|
|
5
|
+
knownInternalSources: number;
|
|
6
|
+
importedSources: number;
|
|
7
|
+
confirmedDuplicateProjections: number;
|
|
8
|
+
};
|
|
9
|
+
conflicts: string[];
|
|
10
|
+
confirmed: {
|
|
11
|
+
sourceKey: string;
|
|
12
|
+
canonicalSessionId: string;
|
|
13
|
+
importedSessionId: string;
|
|
14
|
+
}[];
|
|
15
|
+
actionsApplied: number;
|
|
16
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import l from"node:fs";import d from"node:path";import{fileURLToPath as p}from"node:url";import{resolvePersonalSyncV1Path as S}from"../personal-sync/paths.js";import{knownInternalNativeSources as v,knownJournalInternalNativeSources as y}from"./source-visibility.js";function w(u=S()){function r(e,n){const o=d.join(u,e);return l.existsSync(o)?JSON.parse(l.readFileSync(o,"utf8")):n}const f=Object.values(r("session-index.json",{})),s=r("native-fusion-state.json",{}),a=Object.values(r("native-fusion/sources.json",{sources:{}}).sources),m=f.filter(e=>!!s.daemonInstallationId&&e.daemonInstallationId===s.daemonInstallationId),t=new Map,i=(e,n)=>{const o=t.get(e)??new Set;o.add(n),t.set(e,o)};for(const e of[...v(m),...y(s.daemonInstallationId??"")])i(`${e.agentType}:${e.sourceSessionKey}`,e.sessionId);for(const[e,n]of Object.entries(s.internalSourceOwners??{}))i(e,n.sessionId);const I=[...t].filter(([,e])=>e.size!==1).map(([e])=>e),c=a.flatMap(e=>{if(!s.daemonInstallationId||e.daemonInstallationId!==s.daemonInstallationId)return[];const n=`${e.agentType}:${e.sourceSessionKey}`,o=t.get(n);return!o||o.size!==1||o.has(e.sessionId)?[]:[{sourceKey:n,canonicalSessionId:[...o][0],importedSessionId:e.sessionId}]});return{mode:"dry-run",counts:{knownInternalSources:t.size,importedSources:a.length,confirmedDuplicateProjections:c.length},conflicts:I,confirmed:c,actionsApplied:0}}process.argv[1]&&d.resolve(process.argv[1])===p(import.meta.url)&&console.log(JSON.stringify(w(),null,2));export{w as auditInternalNativeVisibility};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type AgentType, type SessionMode } from '@shennian/wire';
|
|
2
|
+
type OwnerRecord = {
|
|
3
|
+
sessionId: string;
|
|
4
|
+
agentType: AgentType;
|
|
5
|
+
agentSessionId?: string | null;
|
|
6
|
+
modelId?: string | null;
|
|
7
|
+
sessionMode?: SessionMode;
|
|
8
|
+
nativeSessionVisibility?: 'user' | 'internal';
|
|
9
|
+
};
|
|
10
|
+
/** Only exact, persisted ownership is eligible. Titles/prompts/provider analytics are never evidence. */
|
|
11
|
+
export declare function knownInternalNativeSources(records: readonly OwnerRecord[]): Array<{
|
|
12
|
+
sessionId: string;
|
|
13
|
+
agentType: string;
|
|
14
|
+
sourceSessionKey: string;
|
|
15
|
+
}>;
|
|
16
|
+
/** Run history preserves rotated provider IDs that the current session index no longer carries. */
|
|
17
|
+
export declare function knownJournalInternalNativeSources(daemonInstallationId: string, directory?: string): ReturnType<typeof knownInternalNativeSources>;
|
|
18
|
+
/** The provider ID, not the transcript location or a message's Worker link, owns visibility. */
|
|
19
|
+
export declare function registerInternalNativeSource(sessionId: string, agentType: string, sourceSessionKey: string): void;
|
|
20
|
+
/** Read-only commit fence, including registrations made while a cooperative scan yielded. */
|
|
21
|
+
export declare function isInternalNativeSource(agentType: string, sourceSessionKey: string, options?: {
|
|
22
|
+
filePath?: string;
|
|
23
|
+
daemonInstallationId?: string;
|
|
24
|
+
}): boolean;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import a from"node:fs";import f from"node:path";import{createHash as S}from"node:crypto";import{resolveShennianPath as m}from"../config/index.js";import{parseRunJournalRecord as p}from"../session/run-recovery/schema.js";import{isInternalSession as I}from"@shennian/wire";import{getDaemonInstallationId as g}from"../personal-sync/identity.js";import{resolvePersonalSyncV1Path as v}from"../personal-sync/paths.js";import{loadNativeScannerState as h,saveNativeScannerState as w,validateNativeScannerState as y}from"./state.js";function x(i){return i.flatMap(n=>{if(!n.agentSessionId||!I({...n,id:n.sessionId}))return[];const o=n.agentType==="manager"?n.modelId:n.agentType;return!o||!["codex","claude","opencode"].includes(o)?[]:[{sessionId:n.sessionId,agentType:o,sourceSessionKey:n.agentSessionId}]})}function F(i,n=m("runtime","run-recovery","active")){if(!a.existsSync(n))return[];const o=[];for(const e of a.readdirSync(n))if(e.endsWith(".json"))try{const t=f.join(n,e);if(!a.lstatSync(t).isFile())continue;const r=p(JSON.parse(a.readFileSync(t,"utf8")));if(r.daemonInstallationId!==i)continue;const s=S("sha256").update(`${r.sessionId}\0${r.runId}`).digest("hex")+".json";if(e!==s)continue;o.push(...x([r]))}catch{}return o}const u=new Map;function z(i,n,o){if(!i||!o)throw new Error("internal_native_source_identity_required");if(!["codex","claude","opencode"].includes(n))return;const e=h(),t=`${n}:${o}`,r=e.internalSourceOwners?.[t];if(r&&r.sessionId!==i)throw new Error("internal_native_source_owner_conflict");r&&e.ignoredSources?.[t]||(e.ignoredSources={...e.ignoredSources,[t]:!0},e.internalSourceOwners={...e.internalSourceOwners,[t]:r??{sessionId:i,registeredAt:new Date().toISOString()}},w(e))}function D(i,n,o={}){const e=o.filePath??v("native-fusion-state.json");if(!a.existsSync(e))return u.delete(e),!1;const t=a.statSync(e),r=`${t.dev}:${t.ino}:${t.size}:${t.mtimeMs}:${t.ctimeMs}`,s=u.get(e),c=s?.signature===r?s.state:JSON.parse(a.readFileSync(e,"utf8")),l=o.daemonInstallationId??g();(s?.signature!==r||c.daemonInstallationId!==l)&&(y(c,l),u.size>32&&u.clear(),u.set(e,{signature:r,state:c}));const d=`${i}:${n}`;return c.ignoredSources?.[d]===!0||!!c.internalSourceOwners?.[d]}export{D as isInternalNativeSource,x as knownInternalNativeSources,F as knownJournalInternalNativeSources,z as registerInternalNativeSource};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { type NativeScannerState } from './types.js';
|
|
2
2
|
export declare function loadNativeScannerState(): NativeScannerState;
|
|
3
3
|
export declare function saveNativeScannerState(state: NativeScannerState): void;
|
|
4
|
+
export declare function validateNativeScannerState(state: NativeScannerState, daemonInstallationId: string): void;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
`,"utf8"),
|
|
1
|
+
import S from"node:crypto";import o from"node:fs";import p from"node:path";import{readPersonalDataEpochMarker as y}from"../personal-sync/epoch.js";import{getDaemonInstallationId as u}from"../personal-sync/identity.js";import{resolvePersonalSyncV1Path as m}from"../personal-sync/paths.js";import{NATIVE_SCANNER_STATE_SCHEMA_VERSION as f}from"./types.js";function d(){return m("native-fusion-state.json")}function I(){const e=d(),s=u();if(!o.existsSync(e)){const r=y()?.epochStartedAt,i={schemaVersion:f,fusionEpochMs:r?Date.parse(r):Date.now(),daemonInstallationId:s,files:{}};return l(e,i),i}let n;try{n=JSON.parse(o.readFileSync(e,"utf8"))}catch(r){throw new Error(`native fusion v1 state is unreadable: ${e}`,{cause:r})}return c(n,s),n}function b(e){const s=u();c(e,s);const n=d();if(o.existsSync(n)){const r=JSON.parse(o.readFileSync(n,"utf8"));if(c(r,s),r.fusionEpochMs!==e.fusionEpochMs)throw new Error("native fusion epoch is immutable");e.ignoredSources={...e.ignoredSources,...r.ignoredSources};for(const[i,t]of Object.entries(r.internalSourceOwners??{})){const a=e.internalSourceOwners?.[i];if(a&&a.sessionId!==t.sessionId)throw new Error("internal_native_source_owner_conflict")}e.internalSourceOwners={...e.internalSourceOwners,...r.internalSourceOwners}}l(n,e)}function c(e,s){if(!e||e.schemaVersion!==f||!Number.isSafeInteger(e.fusionEpochMs)||e.fusionEpochMs<=0||e.daemonInstallationId!==s||!e.files||typeof e.files!="object"||Array.isArray(e.files)||e.ignoredSources!==void 0&&(typeof e.ignoredSources!="object"||e.ignoredSources===null||Array.isArray(e.ignoredSources)||Object.entries(e.ignoredSources).some(([n,r])=>!n||r!==!0)))throw new Error("invalid native fusion v1 state");if(e.internalSourceOwners!==void 0&&(!e.internalSourceOwners||typeof e.internalSourceOwners!="object"||Array.isArray(e.internalSourceOwners)||Object.entries(e.internalSourceOwners).some(([n,r])=>!/^(codex|claude|opencode):.+$/.test(n)||!r||typeof r.sessionId!="string"||!r.sessionId||typeof r.registeredAt!="string"||!Number.isFinite(Date.parse(r.registeredAt)))))throw new Error("invalid internal native source ownership");for(const n of Object.values(e.files)){if(n.daemonInstallationId!==s)throw new Error("native fusion cursor belongs to another daemon installation");if(n.sources!==void 0&&(!Array.isArray(n.sources)||n.sources.some(r=>!r||!["codex","claude","opencode"].includes(r.agentType)||typeof r.sourceSessionKey!="string"||!r.sourceSessionKey)))throw new Error("native fusion cursor has invalid source metadata")}}function l(e,s){const n=p.dirname(e);o.mkdirSync(n,{recursive:!0,mode:448});const r=`${e}.tmp-${process.pid}-${S.randomBytes(6).toString("hex")}`;let i;try{if(i=o.openSync(r,"wx",384),o.writeFileSync(i,`${JSON.stringify(s,null,2)}
|
|
2
|
+
`,"utf8"),o.fsyncSync(i),o.closeSync(i),i=void 0,o.renameSync(r,e),process.platform!=="win32"){const t=o.openSync(n,"r");try{o.fsyncSync(t)}finally{o.closeSync(t)}}}finally{i!==void 0&&o.closeSync(i);try{o.unlinkSync(r)}catch{}}}export{I as loadNativeScannerState,b as saveNativeScannerState,c as validateNativeScannerState};
|
|
@@ -24,6 +24,11 @@ export type NativeScannerState = {
|
|
|
24
24
|
files: Record<string, NativeFileCursor>;
|
|
25
25
|
/** Provider-native sessions that are internal implementation details, never personal chats. */
|
|
26
26
|
ignoredSources?: Record<string, true>;
|
|
27
|
+
/** Durable provenance for internal sources; retained after runs and sessions retire. */
|
|
28
|
+
internalSourceOwners?: Record<string, {
|
|
29
|
+
sessionId: string;
|
|
30
|
+
registeredAt: string;
|
|
31
|
+
}>;
|
|
27
32
|
codexWindowsPathParserFixed?: boolean;
|
|
28
33
|
codexAppContextWrapperFixed?: boolean;
|
|
29
34
|
};
|
|
@@ -26,6 +26,7 @@ export type ManagedRoomBinder = {
|
|
|
26
26
|
status: 'pending_approval' | 'active' | 'paused' | 'detached';
|
|
27
27
|
activationMode: 'auto_follow' | 'mention_only' | 'paused';
|
|
28
28
|
};
|
|
29
|
+
resumeSequence: string;
|
|
29
30
|
idempotent: boolean;
|
|
30
31
|
}>;
|
|
31
32
|
restart(input: ManagedRoomRestartInput): Promise<{
|
|
@@ -56,6 +57,7 @@ export declare class TrustedManagedRoomBinder implements ManagedRoomBinder {
|
|
|
56
57
|
});
|
|
57
58
|
bind(input: ManagedRoomBindInput): Promise<{
|
|
58
59
|
binding: import("./room-binding-client.js").RemoteRoomBinding;
|
|
60
|
+
resumeSequence: string;
|
|
59
61
|
idempotent: boolean;
|
|
60
62
|
}>;
|
|
61
63
|
restart(input: ManagedRoomRestartInput): Promise<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{roomRuntimeAdapterForAgent as I}from"@shennian/wire";import y from"node:crypto";class b{deps;constructor(e){this.deps=e}async bind(e){const{machineId:t,machineToken:i}=this.deps.getMachineCredentials();if(!t||!i)throw new Error("Machine is not paired with Shennian.");const s=g(e.agentType),o=this.deps.hostProcessIdentity(),d=this.deps.registry.register({machineId:t,runtimeAdapter:s,sourceSessionKey:e.sourceSessionKey,nianSessionId:e.nianSessionId,hostProcessIdentity:o,capabilities:{canIdentifySession:!0,canBindAutomatically:!0,canOpenEmbeddedRoomPage:!1,canResumeIdleSession:!0,canActivateWhileHostOpen:!0}}),n=await this.deps.bindingClient.create({machineToken:i,bindingIntentId:e.assignmentIntentId,roomId:e.roomId,agentIdentityId:e.agentIdentityId,nianSessionId:e.nianSessionId,runtimeAdapter:s,sessionRegistryKey:d.registryKey,activationCapability:"managed",activationMode:e.activationMode??"auto_follow"});this.deps.registry.setBinding({registryKey:d.registryKey,bindingId:n.binding.id,roomId:n.binding.roomId,agentIdentityId:n.binding.agentIdentityId,agentMembershipId:n.binding.agentMembershipId,status:n.binding.status,activationMode:n.binding.activationMode,replaceOwner:!0});const r=await this.resolveServerOwnedWorkspace({machineToken:i,roomId:n.binding.roomId,bindingId:n.binding.id,agentIdentityId:e.agentIdentityId});return this.deps.observations.observe({machineId:t,runtimeAdapter:s,sourceSessionKey:e.sourceSessionKey,hostContextHash:c(o),workspaceHash:c(r),workspacePath:r,lifecycleState:"active",eventName:"ManagedBind"}),n}async restart(e){const{machineId:t,machineToken:i}=this.deps.getMachineCredentials();if(!t||!i)throw new Error("Machine is not paired with Shennian.");const s=g(e.agentType),o=this.deps.hostProcessIdentity(),d=this.deps.registry.register({machineId:t,runtimeAdapter:s,sourceSessionKey:e.sourceSessionKey,nianSessionId:e.nianSessionId,hostProcessIdentity:o,capabilities:{canIdentifySession:!0,canBindAutomatically:!0,canOpenEmbeddedRoomPage:!1,canResumeIdleSession:!0,canActivateWhileHostOpen:!0}}),n=await this.deps.bindingClient.restart({machineToken:i,bindingId:e.bindingId,previousNianSessionId:e.previousNianSessionId,roomId:e.roomId,agentIdentityId:e.agentIdentityId,nianSessionId:e.nianSessionId,runtimeAdapter:s,sessionRegistryKey:d.registryKey});this.deps.registry.setBinding({registryKey:d.registryKey,bindingId:n.binding.id,roomId:n.binding.roomId,agentIdentityId:n.binding.agentIdentityId,agentMembershipId:n.binding.agentMembershipId,status:n.binding.status,activationMode:n.binding.activationMode,replaceOwner:!0,initialSequence:n.resumeSequence});const r=await this.resolveServerOwnedWorkspace({machineToken:i,roomId:n.binding.roomId,bindingId:n.binding.id,agentIdentityId:e.agentIdentityId});return this.deps.observations.observe({machineId:t,runtimeAdapter:s,sourceSessionKey:e.sourceSessionKey,hostContextHash:c(o),workspaceHash:c(r),workspacePath:r,lifecycleState:"active",eventName:"ManagedRestart"}),n}async resolveServerOwnedWorkspace(e){const t=await this.deps.bindingClient.status(e),i=t.managedWorkspacePath?.trim(),s=t.agent?.sessionMode;if(t.agentIdentityId!==e.agentIdentityId||s!=="standard"&&s!=="manager"||!i)throw new Error("managed_room_workspace_unavailable");return i}}function c(a){return y.createHash("sha256").update(a,"utf8").digest("hex")}function g(a){const e=I(a);if(e)return e;throw new Error(`Agent ${a} does not support managed Room binding.`)}export{b as TrustedManagedRoomBinder};
|
|
1
|
+
import{roomRuntimeAdapterForAgent as I}from"@shennian/wire";import y from"node:crypto";class b{deps;constructor(e){this.deps=e}async bind(e){const{machineId:t,machineToken:i}=this.deps.getMachineCredentials();if(!t||!i)throw new Error("Machine is not paired with Shennian.");const s=g(e.agentType),o=this.deps.hostProcessIdentity(),d=this.deps.registry.register({machineId:t,runtimeAdapter:s,sourceSessionKey:e.sourceSessionKey,nianSessionId:e.nianSessionId,hostProcessIdentity:o,capabilities:{canIdentifySession:!0,canBindAutomatically:!0,canOpenEmbeddedRoomPage:!1,canResumeIdleSession:!0,canActivateWhileHostOpen:!0}}),n=await this.deps.bindingClient.create({machineToken:i,bindingIntentId:e.assignmentIntentId,roomId:e.roomId,agentIdentityId:e.agentIdentityId,nianSessionId:e.nianSessionId,runtimeAdapter:s,sessionRegistryKey:d.registryKey,activationCapability:"managed",activationMode:e.activationMode??"auto_follow"});this.deps.registry.setBinding({registryKey:d.registryKey,bindingId:n.binding.id,roomId:n.binding.roomId,agentIdentityId:n.binding.agentIdentityId,agentMembershipId:n.binding.agentMembershipId,status:n.binding.status,activationMode:n.binding.activationMode,replaceOwner:!0,initialSequence:n.resumeSequence});const r=await this.resolveServerOwnedWorkspace({machineToken:i,roomId:n.binding.roomId,bindingId:n.binding.id,agentIdentityId:e.agentIdentityId});return this.deps.observations.observe({machineId:t,runtimeAdapter:s,sourceSessionKey:e.sourceSessionKey,hostContextHash:c(o),workspaceHash:c(r),workspacePath:r,lifecycleState:"active",eventName:"ManagedBind"}),n}async restart(e){const{machineId:t,machineToken:i}=this.deps.getMachineCredentials();if(!t||!i)throw new Error("Machine is not paired with Shennian.");const s=g(e.agentType),o=this.deps.hostProcessIdentity(),d=this.deps.registry.register({machineId:t,runtimeAdapter:s,sourceSessionKey:e.sourceSessionKey,nianSessionId:e.nianSessionId,hostProcessIdentity:o,capabilities:{canIdentifySession:!0,canBindAutomatically:!0,canOpenEmbeddedRoomPage:!1,canResumeIdleSession:!0,canActivateWhileHostOpen:!0}}),n=await this.deps.bindingClient.restart({machineToken:i,bindingId:e.bindingId,previousNianSessionId:e.previousNianSessionId,roomId:e.roomId,agentIdentityId:e.agentIdentityId,nianSessionId:e.nianSessionId,runtimeAdapter:s,sessionRegistryKey:d.registryKey});this.deps.registry.setBinding({registryKey:d.registryKey,bindingId:n.binding.id,roomId:n.binding.roomId,agentIdentityId:n.binding.agentIdentityId,agentMembershipId:n.binding.agentMembershipId,status:n.binding.status,activationMode:n.binding.activationMode,replaceOwner:!0,initialSequence:n.resumeSequence});const r=await this.resolveServerOwnedWorkspace({machineToken:i,roomId:n.binding.roomId,bindingId:n.binding.id,agentIdentityId:e.agentIdentityId});return this.deps.observations.observe({machineId:t,runtimeAdapter:s,sourceSessionKey:e.sourceSessionKey,hostContextHash:c(o),workspaceHash:c(r),workspacePath:r,lifecycleState:"active",eventName:"ManagedRestart"}),n}async resolveServerOwnedWorkspace(e){const t=await this.deps.bindingClient.status(e),i=t.managedWorkspacePath?.trim(),s=t.agent?.sessionMode;if(t.agentIdentityId!==e.agentIdentityId||s!=="standard"&&s!=="manager"||!i)throw new Error("managed_room_workspace_unavailable");return i}}function c(a){return y.createHash("sha256").update(a,"utf8").digest("hex")}function g(a){const e=I(a);if(e)return e;throw new Error(`Agent ${a} does not support managed Room binding.`)}export{b as TrustedManagedRoomBinder};
|
|
@@ -50,6 +50,7 @@ export declare class HttpRoomBindingClient {
|
|
|
50
50
|
pause: HttpRoomActionClient['pause'];
|
|
51
51
|
create(input: CreateRemoteRoomBindingInput): Promise<{
|
|
52
52
|
binding: RemoteRoomBinding;
|
|
53
|
+
resumeSequence: string;
|
|
53
54
|
idempotent: boolean;
|
|
54
55
|
}>;
|
|
55
56
|
restart(input: RestartRemoteRoomBindingInput): Promise<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{HttpRoomActionClient as r}from"./room-action-client.js";class o extends Error{code;constructor(e,
|
|
1
|
+
import{HttpRoomActionClient as r}from"./room-action-client.js";class o extends Error{code;constructor(e,t){super(t),this.code=e,this.name="RoomBindingHttpError"}}class l{fetchImpl;baseUrl;actionClient;constructor(e,t=fetch){this.fetchImpl=t,this.baseUrl=e.replace(/\/+$/,""),this.actionClient=new r(e,t)}status=e=>this.actionClient.status(e);getAttachmentCapability=e=>this.actionClient.getAttachmentCapability(e);initializeAttachment=e=>this.actionClient.initializeAttachment(e);completeAttachment=e=>this.actionClient.completeAttachment(e);createRoom=e=>this.actionClient.createRoom(e);createOperatorRoom=e=>this.actionClient.createOperatorRoom(e);resolveNativeSession=e=>this.actionClient.resolveNativeSession(e);joinRoom=e=>this.actionClient.joinRoom(e);read=e=>this.actionClient.read(e);send=e=>this.actionClient.send(e);mode=e=>this.actionClient.mode(e);pause=e=>this.actionClient.pause(e);async create(e){let t;try{t=await this.fetchImpl(`${this.baseUrl}/api/rooms/${encodeURIComponent(e.roomId)}/bindings`,{method:"POST",headers:{authorization:`Bearer ${e.machineToken}`,"content-type":"application/json"},body:JSON.stringify({bindingIntentId:e.bindingIntentId,agentIdentityId:e.agentIdentityId,nianSessionId:e.nianSessionId,runtimeAdapter:e.runtimeAdapter,sessionRegistryKey:e.sessionRegistryKey,activationCapability:e.activationCapability,activationMode:e.activationMode}),signal:AbortSignal.timeout(1e4)})}catch{throw new o("room_binding_server_unavailable","Shennian Room service is unavailable.")}if(!t.ok){let i=`room_binding_http_${t.status}`;try{const s=await t.json();typeof s.error=="string"&&/^[a-z0-9_-]{1,80}$/.test(s.error)&&(i=s.error)}catch{}throw new o(i,"Shennian Room binding request failed.")}let n;try{n=await t.json()}catch{throw new o("invalid_room_binding_response","Invalid Room binding response.")}return d(n,e)}async restart(e){let t;try{t=await this.fetchImpl(`${this.baseUrl}/api/rooms/${encodeURIComponent(e.roomId)}/bindings/${encodeURIComponent(e.bindingId)}/session`,{method:"PATCH",headers:{authorization:`Bearer ${e.machineToken}`,"content-type":"application/json"},body:JSON.stringify({agentIdentityId:e.agentIdentityId,previousNianSessionId:e.previousNianSessionId,nianSessionId:e.nianSessionId,runtimeAdapter:e.runtimeAdapter,sessionRegistryKey:e.sessionRegistryKey}),signal:AbortSignal.timeout(1e4)})}catch{throw new o("room_binding_server_unavailable","Shennian Room service is unavailable.")}const n=await t.json().catch(()=>null);if(!t.ok){const s=n&&typeof n.error=="string"&&/^[a-z0-9_-]{1,80}$/.test(n.error)?n.error:`room_binding_http_${t.status}`;throw new o(s,"Shennian Room restart request failed.")}const i=n;if(!i?.binding||i.binding.id!==e.bindingId||i.binding.roomId!==e.roomId||i.binding.agentIdentityId!==e.agentIdentityId||i.binding.nianSessionId!==e.nianSessionId||i.binding.runtimeAdapter!==e.runtimeAdapter||typeof i.binding.agentMembershipId!="string"||typeof i.resumeSequence!="string"||!/^(0|[1-9]\d{0,19})$/.test(i.resumeSequence)||typeof i.idempotent!="boolean")throw new o("invalid_room_binding_response","Invalid Room restart response.");return{binding:i.binding,resumeSequence:i.resumeSequence,idempotent:i.idempotent}}}function d(a,e){if(!a||typeof a!="object")throw new o("invalid_room_binding_response","Invalid Room binding response.");const t=a,n=t.binding;if(!n||typeof n.id!="string"||typeof n.agentMembershipId!="string"||n.roomId!==e.roomId||n.agentIdentityId!==e.agentIdentityId||n.nianSessionId!==e.nianSessionId||n.runtimeAdapter!==e.runtimeAdapter||n.activationCapability!==e.activationCapability||!c(n.activationMode,e)||!["pending_approval","active","paused","detached"].includes(n.status??"")||typeof t.resumeSequence!="string"||!/^(0|[1-9]\d{0,19})$/.test(t.resumeSequence)||typeof t.idempotent!="boolean")throw new o("invalid_room_binding_response","Room binding response does not match the request.");return{binding:n,resumeSequence:t.resumeSequence,idempotent:t.idempotent}}function c(a,e){return a===e.activationMode}export{l as HttpRoomBindingClient,o as RoomBindingHttpError};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type CliDistribution = {
|
|
2
|
+
source: 'desktop-managed' | 'global-cli';
|
|
3
|
+
cliVersion: string;
|
|
4
|
+
desktopVersion?: string;
|
|
5
|
+
updateOwner: 'desktop' | 'cli';
|
|
6
|
+
};
|
|
7
|
+
export declare const DESKTOP_UPGRADE_REQUIRED = "desktop_update_required: This Client is included in Shennian Desktop. Update Desktop in Settings \u2192 Version & Updates. Do not install or upgrade a separate npm Client.";
|
|
8
|
+
/** Read our own package, not argv, PATH, daemon state or inherited Agent env. */
|
|
9
|
+
export declare function readCliDistribution(startFile?: string): CliDistribution;
|
|
10
|
+
export declare function isDesktopDistribution(): boolean;
|
|
11
|
+
export declare function assertStandaloneUpgrade(distribution?: CliDistribution): void;
|
|
12
|
+
/** The private Agent PATH uses signed package resources, not a user's shell alias. */
|
|
13
|
+
export declare function desktopCommandDirectory(startFile?: string): string | null;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import s from"node:fs";import e from"node:path";import{fileURLToPath as u}from"node:url";const m="desktop_update_required: This Client is included in Shennian Desktop. Update Desktop in Settings \u2192 Version & Updates. Do not install or upgrade a separate npm Client.";function d(t=u(import.meta.url)){let n=e.dirname(t);for(let i=0;i<7;i++){const r=e.join(n,"package.json");if(s.existsSync(r)){const o=JSON.parse(s.readFileSync(r,"utf8"));if(o.name==="shennian"){const a=o.shennianDistribution!==void 0;if(a&&o.shennianDistribution?.source!=="desktop-managed")throw new Error("Invalid Shennian distribution metadata. Repair the Desktop installation.");return{source:a?"desktop-managed":"global-cli",cliVersion:o.version??"unknown",...a?{desktopVersion:o.shennianDistribution?.desktopVersion}:{},updateOwner:a?"desktop":"cli"}}}const p=e.dirname(n);if(p===n)break;n=p}throw new Error("Cannot identify the Shennian CLI installation.")}function k(){return d().source==="desktop-managed"}function h(t=d()){if(t.updateOwner==="desktop")throw new Error(m)}function D(t=u(import.meta.url)){if(d(t).source!=="desktop-managed")return null;let n=e.dirname(t);for(let i=0;i<7;i++){const r=e.join(n,"package.json");if(s.existsSync(r)&&JSON.parse(s.readFileSync(r,"utf8")).name==="shennian")return e.join(e.dirname(n),"bin");n=e.dirname(n)}throw new Error("Desktop command directory is unavailable.")}export{m as DESKTOP_UPGRADE_REQUIRED,h as assertStandaloneUpgrade,D as desktopCommandDirectory,k as isDesktopDistribution,d as readCliDistribution};
|
|
@@ -57,3 +57,10 @@ export type ManagedAgentPolicyCompileInput = ManagedAgentRuntimeResolutionInput
|
|
|
57
57
|
* user's authorization ceiling while each adapter applies what it supports.
|
|
58
58
|
*/
|
|
59
59
|
export declare function compileManagedAgentPolicy(input: ManagedAgentPolicyCompileInput): Promise<ManagedAgentPolicyCompileResult>;
|
|
60
|
+
export declare function validateManagedAgentWorkDir(rawWorkDir: string, resolver: ManagedAgentPolicyCompileInput['resolveWorkDir']): Promise<{
|
|
61
|
+
ok: true;
|
|
62
|
+
workDir: string;
|
|
63
|
+
} | {
|
|
64
|
+
ok: false;
|
|
65
|
+
errorCode: 'managed_agent_workdir_invalid' | 'managed_agent_workdir_unavailable';
|
|
66
|
+
}>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import c from"node:fs/promises";import
|
|
1
|
+
import c from"node:fs/promises";import a from"node:path";import{managedAgentExecutionMode as y,parseManagedAgentPermissionPolicy as b,resolveManagedAgentEffectivePermissionPolicy as _}from"@shennian/wire";import{getManagedAgentBaseEnvKeys as k,getManagedAgentProviderEnvKeys as u,getManagedAgentSessionEnvKeys as d}from"../agent-env.js";import{collectPolicyEnforcementGaps as x,resolveManagedAgentRuntimeProtection as P,resolveManagedAgentSecurityProfile as w}from"./managed-agent-runtime.js";import{resolveProtectedShennianDirectories as A}from"./managed-agent-credential-paths.js";function V(e){return!!(e&&(e.executionMode==="native-full"||e.requestedPolicy.executionMode==="native-full"))}function z(e){return{...e.enforcement,effectivePolicy:{version:e.adapterPolicy.version,executionMode:e.executionMode??"restricted",fileAccess:e.adapterPolicy.fileAccess,externalCapabilities:{...e.adapterPolicy.externalCapabilities}}}}async function I(e){const r=b(e.policy);if(!r)return{ok:!1,errorCode:"managed_agent_policy_invalid"};const t=y(r);if((t==="native-full"||r.fileAccess==="host-full")&&!e.hostFullAccessConfirmed)return{ok:!1,errorCode:"managed_agent_host_full_confirmation_required"};const o=await h(e.workDir,e.resolveWorkDir);if(!o.ok)return o;const i=C(e),n=P(i),l=w(i),s=t==="restricted"?x(r,l):{unavailableCapabilities:[],overgrantedCapabilities:[]},v=g([...t==="restricted"&&n.level!=="hard"?["runtime.hardIsolation"]:[],...t==="restricted"?n.unsupportedCapabilities:[],...s.unavailableCapabilities,...s.overgrantedCapabilities]),p=_(r,n),m={level:n.level,effectivePolicy:p,unsupportedCapabilities:v,runtimeVersion:n.runtimeVersion,platform:n.platform,...n.executableFingerprint?{executableFingerprint:n.executableFingerprint}:{}};return{ok:!0,value:{requestedPolicy:F(r),executionMode:t,enforcement:m,adapterPolicy:{version:r.version,workDir:o.workDir,fileAccess:r.fileAccess,externalCapabilities:{...r.externalCapabilities},denySensitiveFiles:!0,failClosed:!0},environmentPolicy:{inheritDaemonEnv:!1,loadLoginShell:!1,providerEnvKeys:[...u(e.agentType)],sessionEnvKeys:[...d(e.agentType)],shellSubprocess:{inherit:"none",allowedEnvKeys:g([...k(e.platform),...d(e.agentType)]),deniedEnvKeys:[...u(e.agentType)]}}}}}function C(e){return{agentType:e.agentType,runtimeVersion:e.runtimeVersion,platform:e.platform,isWsl:e.isWsl,executableFingerprint:e.executableFingerprint,requireExecutableFingerprint:e.requireExecutableFingerprint,securityDeclaration:e.securityDeclaration}}async function h(e,r){const t=e.trim();if(!t||!a.isAbsolute(t))return{ok:!1,errorCode:"managed_agent_workdir_invalid"};try{const o=await(r??M)(t);if(!o||!a.isAbsolute(o))return{ok:!1,errorCode:"managed_agent_workdir_invalid"};const i=a.normalize(o);return await E(i)?{ok:!1,errorCode:"managed_agent_workdir_invalid"}:{ok:!0,workDir:i}}catch{return{ok:!1,errorCode:"managed_agent_workdir_unavailable"}}}async function M(e){const r=await c.realpath(e);if(!(await c.stat(r)).isDirectory())throw new Error("managed_agent_workdir_not_directory");return r}async function E(e){return A().some(t=>D(e,t,process.platform))}function D(e,r,t){const o=l=>{const s=a.resolve(l);return t==="win32"?s.toLowerCase():s},i=o(e),n=o(r);return f(i,n)||f(n,i)}function f(e,r){const t=a.relative(e,r);return t===""||!t.startsWith("..")&&!a.isAbsolute(t)}function F(e){return{...e,externalCapabilities:{...e.externalCapabilities}}}function g(e){return[...new Set(e)]}export{I as compileManagedAgentPolicy,V as isManagedAgentNativeFull,z as resolveManagedAgentAdapterEnforcement,h as validateManagedAgentWorkDir};
|
|
@@ -18,6 +18,7 @@ type ValidationDependencies = {
|
|
|
18
18
|
registerPiSession?: () => Promise<PiValidationSession>;
|
|
19
19
|
createAdapter?: (type: AgentType) => AgentAdapter | null;
|
|
20
20
|
resolveWorkDir?: (workDir: string) => Promise<string>;
|
|
21
|
+
validateWorkDir?: (workDir: string) => Promise<boolean>;
|
|
21
22
|
timeoutMs?: number;
|
|
22
23
|
cleanupNativeSession?: (agentType: AgentType, sessionId: string, workDir: string) => void;
|
|
23
24
|
ignoreNativeSession?: (agentType: AgentType, sessionId: string) => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{registerPiValidationSession as I}from"./pi-validation-session.js";import y from"node:fs/promises";import{randomUUID as
|
|
1
|
+
import{registerPiValidationSession as I}from"./pi-validation-session.js";import y from"node:fs/promises";import b from"node:path";import{randomUUID as C}from"node:crypto";import{isAvailableAgentType as E}from"@shennian/wire";import{createAgent as N}from"../../agents/adapter.js";import{buildManagedAgentEnv as S}from"../../agents/config-status.js";import{ManagerModeAdapter as T}from"../../agents/manager.js";import{NativeSessionLifecycleRegistry as O}from"../native-session-lifecycle.js";import{validateManagedAgentWorkDir as M}from"../../security/managed-agent-policy.js";const x="\u8BF7\u56DE\u590D hello\uFF0C\u7528\u4E8E\u786E\u8BA4\u4F60\u5DF2\u51C6\u5907\u597D\u3002",R=6e4,W=new O;async function L(e,r={}){try{return await P(e,r)}catch(t){const a=t instanceof Error?t.message:"";return{ok:!1,errorCode:a==="agent_validation_cleanup_failed"?a:v(t,"start")}}}async function P(e,r){if(!E(e.runtimeAdapter)||e.runtimeAdapter==="manager"||e.runtimeAdapter==="pi"||e.runtimeAdapter==="openclaw")return{ok:!1,errorCode:"agent_validation_runtime_unavailable"};if(!b.isAbsolute(e.workDir.trim()))return{ok:!1,errorCode:"agent_validation_workdir_invalid"};let t;try{t=await(r.resolveWorkDir??U)(e.workDir)}catch(i){return{ok:!1,errorCode:V(i)}}if(!(r.validateWorkDir?await r.validateWorkDir(t):(await M(t,async()=>t)).ok))return{ok:!1,errorCode:"agent_validation_workdir_invalid"};const s=(r.createAdapter??N)(e.runtimeAdapter);if(!s)return{ok:!1,errorCode:"agent_validation_runtime_unavailable"};const o=e.sessionMode==="manager"?new T(s,e.runtimeAdapter):s;let f=`agent-validation-${C()}`,l,u="",m="",_=null,c=null;const D=new Promise(i=>{c=i}),p=i=>{i.agentSessionId&&(u=i.agentSessionId,r.ignoreNativeSession?.(e.runtimeAdapter,u)),i.state==="delta"&&!i.thinking&&i.text&&(m+=i.text),i.state==="final"?(i.text&&(m+=i.text),c?.({kind:"final"})):(i.state==="error"||i.state==="aborted")&&c?.({kind:"error",error:i.message??i.state})},w=i=>c?.({kind:"error",error:i});o.on("agentEvent",p),o.on("error",w);const h=async()=>{e.runtimeAdapter==="pi-coding-agent"&&(l=await(r.registerPiSession??I)(),f=l.sessionId),o.configure?.({sessionId:f,env:S(e.runtimeAdapter)});try{await o.start(f,t,null,{persistNativeSession:!1})}catch(n){return{ok:!1,errorCode:v(n,"start")}}const i=new Promise(n=>{_=setTimeout(()=>n({kind:"timeout"}),r.timeoutMs??R)}),k=o.send(x,e.modelId??void 0).then(()=>D).catch(n=>({kind:"error",error:n})),d=await Promise.race([k,i]);return d.kind==="timeout"?{ok:!1,errorCode:"agent_validation_response_timeout"}:d.kind==="error"?{ok:!1,errorCode:v(d.error,"response")}:m.toLowerCase().includes("hello")?{ok:!0}:{ok:!1,errorCode:"agent_validation_response_mismatch"}};let A,g=!1;try{A=await h()}finally{_&&clearTimeout(_),o.removeListener("agentEvent",p),o.removeListener("error",w);try{await o.stop()}catch{l&&(g=!0)}finally{await l?.dispose().catch(()=>{g=!0})}u&&(r.cleanupNativeSession??((k,d,n)=>{W.execute(k,d,{workDir:n}).catch(()=>{})}))(e.runtimeAdapter,u,t)}return g?{ok:!1,errorCode:"agent_validation_cleanup_failed"}:A}async function Y(e,r){const t=j(r.params);if(!t){e.client.sendRes({type:"res",id:r.id,ok:!1,error:"agent_validation_invalid_configuration"});return}const a=await L(t,{ignoreNativeSession:(s,o)=>{e.nativeFusion?.ignoreNativeSource(s,o)}});e.client.sendRes(a.ok?{type:"res",id:r.id,ok:!0,payload:{ready:!0}}:{type:"res",id:r.id,ok:!1,error:a.errorCode})}async function U(e){const r=e.trim();if(!r)throw Object.assign(new Error("workdir_required"),{code:"EINVAL"});const t=await y.realpath(r);if(!(await y.stat(t)).isDirectory())throw Object.assign(new Error("workdir_not_directory"),{code:"ENOTDIR"});return t}function V(e){const r=F(e);return r==="ENOENT"?"agent_validation_workdir_not_found":r==="ENOTDIR"?"agent_validation_workdir_not_directory":r==="EACCES"||r==="EPERM"?"agent_validation_workdir_unavailable":"agent_validation_workdir_invalid"}function v(e,r){const t=e instanceof Error?e.message:String(e);return t==="agent_validation_server_upgrade_required"?t:/model.*(?:not found|unavailable|unsupported|invalid)|unknown model/i.test(t)?"agent_validation_model_unavailable":/quota|insufficient.*(?:credit|balance)|billing|rate.?limit|\b429\b/i.test(t)?"agent_validation_quota_exceeded":/not found|enoent|command .* missing|is .* installed/i.test(t)?"agent_validation_runtime_unavailable":/unauth|authentication|not logged|login required|api key|token.*missing|forbidden|401|403/i.test(t)?"agent_validation_runtime_auth_required":/ECONNREFUSED|ECONNRESET|ENOTFOUND|ETIMEDOUT|network|fetch failed|connection.*(?:failed|closed|refused)/i.test(t)?"agent_validation_network_failed":r==="start"?"agent_validation_start_failed":"agent_validation_response_failed"}function F(e){return typeof e=="object"&&e&&"code"in e?String(e.code):""}function j(e){const r=typeof e.runtimeAdapter=="string"?e.runtimeAdapter:"",t=typeof e.workDir=="string"?e.workDir:"",a=e.sessionMode==="manager"?"manager":"standard";return!E(r)||!t.trim()?null:{runtimeAdapter:r,workDir:t,sessionMode:a,modelId:typeof e.modelId=="string"?e.modelId:null}}export{Y as handleAgentWorkspaceValidation,L as validateAgentWorkspaceConfiguration};
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
import type { ReqFrame } from '@shennian/wire';
|
|
1
|
+
import type { AgentInfo, ReqFrame } from '@shennian/wire';
|
|
2
2
|
import type { SessionManagerRuntime } from '../types.js';
|
|
3
|
+
/** Shared discovery path for the App refresh and local CLI. Never sends a model prompt. */
|
|
4
|
+
export declare function scanMachineAgents(refresh?: boolean): Promise<{
|
|
5
|
+
detectedAgents: import("../../agents/detect.js").DetectedAgent[];
|
|
6
|
+
agents: AgentInfo[];
|
|
7
|
+
agentList: import("@shennian/wire").AgentType[];
|
|
8
|
+
}>;
|
|
3
9
|
export declare function refreshMachineAgents(runtime: SessionManagerRuntime, req?: ReqFrame): Promise<void>;
|
|
4
10
|
export declare function handleModelsRefresh(runtime: SessionManagerRuntime, req: ReqFrame): Promise<void>;
|
|
5
11
|
export declare function handleAgentsRefresh(runtime: SessionManagerRuntime, req: ReqFrame): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{SERVERS as
|
|
1
|
+
import{SERVERS as p}from"../../region.js";import{loadConfig as l}from"../../config/index.js";import{detectAgents as g}from"../../agents/detect.js";import{refreshAgentInfos as d}from"../../agents/model-registry.js";import{resetCommandSpecCache as r}from"../../agents/command-spec.js";import{clearAcpCapabilityCache as c}from"../../agents/acp/capability-cache.js";const i=new WeakMap;async function h(e=!1){e&&(r(),c());const t=l(),n={serverUrl:t.serverUrl??p.cn.url,authToken:t.machineToken??t.accessToken},s=await g(n),a=s.length?await d(s,n):[];return{detectedAgents:s,agents:a,agentList:s.map(o=>o.type)}}async function f(e,t){try{const{agentList:n,agents:s}=await h(),a={agentList:n,agents:s};t&&e.client.sendRes({type:"res",id:t.id,ok:!0,payload:a});const o=JSON.stringify(a);i.get(e.client)!==o&&(i.set(e.client,o),e.client.sendEvent({type:"event",event:"machine.agents",payload:a}))}catch(n){if(!t)return;e.client.sendRes({type:"res",id:t.id,ok:!1,error:n instanceof Error?n.message:String(n)})}}async function C(e,t){await f(e,t)}async function R(e,t){r(),c(),e.reloadCustomAgents(),await f(e,t)}export{R as handleAgentsRefresh,C as handleModelsRefresh,f as refreshMachineAgents,h as scanMachineAgents};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type AgentType, type SessionMode } from '@shennian/wire';
|
|
2
|
+
import type { AgentSendContext } from '../../agents/adapter.js';
|
|
3
|
+
import type { ManagedRoomPromptSnapshot } from '../../room/managed-prompt-composer.js';
|
|
4
|
+
import type { CompiledManagedAgentPolicy } from '../../security/managed-agent-policy.js';
|
|
5
|
+
import type { SessionManagerRuntime } from '../types.js';
|
|
6
|
+
export type ChatSendExecutionContext = (AgentSendContext & {
|
|
7
|
+
source: 'room_activation';
|
|
8
|
+
/** Local-only snapshot produced by RoomActivationService; never accepted from Relay input. */
|
|
9
|
+
managedAgentPolicy: CompiledManagedAgentPolicy;
|
|
10
|
+
managedRoomPromptSnapshot: ManagedRoomPromptSnapshot;
|
|
11
|
+
}) | {
|
|
12
|
+
/** A Worker event may reuse the Room's current permission ceiling, but it
|
|
13
|
+
* is a private Manager continuation and must never receive Room tools. */
|
|
14
|
+
source: 'manager_event';
|
|
15
|
+
managedAgentPolicy: CompiledManagedAgentPolicy;
|
|
16
|
+
managedRoomPolicyVersion: string;
|
|
17
|
+
managedRuntimePolicyHash: string | null;
|
|
18
|
+
};
|
|
19
|
+
export declare function validateManagedRoomExecutionContext(context: ChatSendExecutionContext, systemPrompt: string | null | undefined, managedRoomContextToken: string | null | undefined): void;
|
|
20
|
+
export declare function sendSessionUpdateEvent(runtime: SessionManagerRuntime, input: {
|
|
21
|
+
sessionId: string;
|
|
22
|
+
agentType: AgentType;
|
|
23
|
+
sessionMode?: SessionMode;
|
|
24
|
+
managerConfig?: import('@shennian/wire').ManagerConfig | null;
|
|
25
|
+
workDir: string;
|
|
26
|
+
agentSessionId?: string | null;
|
|
27
|
+
modelId?: string;
|
|
28
|
+
managerDefaultWorkerAgentType?: AgentType | null;
|
|
29
|
+
managerDefaultWorkerModelId?: string | null;
|
|
30
|
+
}): void;
|
|
31
|
+
export declare function resolveNativeSessionVisibility(sessionId: string, agentType: AgentType, sessionMode: SessionMode, managed: boolean): 'user' | 'internal';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{isInternalSession as a}from"@shennian/wire";import{createHash as t}from"node:crypto";import{listSessionRecords as d,recordSession as m}from"../store.js";function f(o,e,n){if(o.source!=="room_activation")throw new Error("managed_room_execution_context_required");if(!o.managedAgentPolicy)throw new Error("managed_agent_policy_missing");const r=o.managedRoomPromptSnapshot;if(!r)throw new Error("managed_room_prompt_snapshot_missing");if(!e?.trim())throw new Error("managed_room_prompt_incomplete");if(!n||!/^mrc_[A-Za-z0-9_-]{43}$/.test(n))throw new Error("managed_room_context_unavailable");const s=t("sha256").update(JSON.stringify({requestedPolicy:o.managedAgentPolicy.requestedPolicy,enforcement:o.managedAgentPolicy.enforcement})).digest("hex"),i=t("sha256").update(o.managedAgentPolicy.adapterPolicy.workDir).digest("hex");if(r.runtimePermissionPolicyHash!==s||r.resolvedWorkDirHash!==i||r.enforcementLevel!==o.managedAgentPolicy.enforcement.level)throw new Error("managed_room_prompt_snapshot_mismatch")}function y(o,e){const n=m({sessionId:e.sessionId,agentType:e.agentType,sessionMode:e.sessionMode,managerConfig:e.managerConfig,workDir:e.workDir,agentSessionId:e.agentSessionId??null,modelId:e.modelId??null,managerDefaultWorkerAgentType:e.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:e.managerDefaultWorkerModelId});o.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:e.sessionId,agentType:e.agentType,nativeSessionVisibility:n.nativeSessionVisibility,sessionMode:e.sessionMode,managerConfig:e.managerConfig,agentSessionId:e.agentSessionId??null,modelId:e.modelId??null,workDir:e.workDir,status:"active",externalChannel:null}}})}function _(o,e,n,r){const s=d().find(i=>i.sessionId===o);return r||a({...s,id:o})||a({id:o,agentType:e,sessionMode:n,nativeSessionVisibility:s?.nativeSessionVisibility})?"internal":"user"}export{_ as resolveNativeSessionVisibility,y as sendSessionUpdateEvent,f as validateManagedRoomExecutionContext};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { AgentAdapter } from '../../agents/adapter.js';
|
|
2
1
|
import { type AgentType } from '@shennian/wire';
|
|
2
|
+
import type { AgentAdapter } from '../../agents/adapter.js';
|
|
3
3
|
import type { SessionManagerRuntime } from '../types.js';
|
|
4
4
|
export declare function getNativeSourceAgentType(agentType: AgentType, modelId?: string | null): string;
|
|
5
5
|
export declare function bindAdapterEvents(runtime: SessionManagerRuntime, sessionId: string, agentType: AgentType, adapter: AgentAdapter): void;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{
|
|
2
|
-
`),n=e>0?Math.min(e,80):Math.min(a.length,80);return a.slice(0,n)}function
|
|
1
|
+
import{buildApprovalPendingPayload as b}from"@shennian/wire";import{createHash as x}from"node:crypto";import{reportLog as $}from"../../log-reporter.js";import{registerInternalNativeSource as k}from"../../native-fusion/source-visibility.js";import{requestSessionIndexPublish as f}from"../index-publisher.js";import{appendMessage as g,recordSession as p,upsertMessage as M}from"../store.js";import{writeToolDetail as w}from"../tool-detail-store.js";import{refreshMachineAgents as y}from"./agents.js";function S(a,e){const n=e?.managedRoomContextToken;!e||!n||(e.managedRoomContextToken=null,a.revokeManagedRoomContextToken?.(n))}function D(a){const e=a.indexOf(`
|
|
2
|
+
`),n=e>0?Math.min(e,80):Math.min(a.length,80);return a.slice(0,n)}function q(a,e,n){return`${a}:${x("sha256").update(`${e}\0${n??""}`).digest("hex")}`}function _(a,e,n={}){if(a.state==="tool-call"||a.state==="tool-result"){const u={runId:a.runId,sourceSeq:a.seq};return{state:a.state,runId:a.runId,seq:a.seq,sessionId:e,detailRef:u,status:a.state==="tool-call"?"running":"completed",detailAvailable:!0,...a.durationMs!==void 0?{durationMs:a.durationMs}:{},...a.name?{name:a.name}:{},...a.source?{source:a.source}:{},...a.agentSessionId?{agentSessionId:a.agentSessionId}:{},...n}}return{...a,sessionId:e,...n}}const P=3e4;function C(a){return a.state==="heartbeat"?a.runPhase??null:a.state==="tool-call"||a.state==="tool-result"?"tool_running":a.state==="approval-pending"?"waiting_approval":a.state==="delta"?a.thinking?"thinking":"streaming_text":a.state==="init"||a.state==="start"?"thinking":null}function R(a,e){return a!=="manager"?a:e==="claude"?"claude":"codex"}function Q(a,e,n,u){let I=null;function h(t,r={}){a.client.sendAgentEvent({type:"event",event:"agent",payload:_(t,e,r),seq:t.seq,id:`agent-evt-${t.runId}-${t.seq}`})}function T(t){t?.heartbeatTimer&&(clearInterval(t.heartbeatTimer),t.heartbeatTimer=null)}function A(t){if(!t.currentRunId||!t.currentRunPhase)return;const r=t.heartbeatSeq++;a.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:e,runId:t.currentRunId,seq:r,runPhase:t.currentRunPhase},seq:r,id:`agent-heartbeat-${t.currentRunId}-${r}-${Date.now()}`})}function E(t){!t||t.heartbeatTimer||(t.heartbeatTimer=setInterval(()=>{A(t)},P),t.heartbeatTimer.unref?.())}function i(t){const r=t?.pendingTextEvent;!t||!r||!r.text||(g(e,{id:`agent-${r.runId}-${r.seq}`,sessionId:e,role:"agent",ts:Date.now(),payload:r.text}),f(a.client),h({state:"delta",runId:r.runId,seq:r.seq,text:r.text,thinking:r.thinking||void 0}),t.pendingTextEvent=null)}u.on("agentEvent",t=>{t.errorCode==="acp_auth_required"&&y(a).catch(()=>{});const r=a.sessions.get(e),c=C(t),d=t.state==="final"||t.state==="error"||t.state==="aborted";if(r&&(r.nextEventSeq=t.seq+1,d||(r.currentRunId=t.runId),!d&&c&&(r.currentRunPhase=c,E(r)),t.agentSessionId&&(r.agentSessionId=t.agentSessionId),r.journalRunId)){const o={agentSessionId:t.agentSessionId??r.agentSessionId,providerRunId:t.runId||null,lastObservedEventSeq:t.seq,...d?{}:{phase:c==="waiting_approval"?"waiting_approval":"running"}};a.runJournal?.update(e,r.journalRunId,o)}if(a.managerRuntime?.noteAgentEvent(e,t),t.state==="delta"&&t.thinking)return;t.state!=="delta"&&$({level:"info",sessionId:e,wsEvent:`agent.${t.state}`,wsDirection:"out",metadata:{runId:t.runId,seq:t.seq,agentType:n}});let s=!1;if(t.state==="tool-call"||t.state==="tool-result"){const o=t.toolCallId?`agent-tool-${x("sha256").update(`${t.runId}\0${t.toolCallId}`).digest("hex")}`:`agent-${t.runId}-${t.seq}`;w({sessionId:e,toolCallId:o,detailRevision:t.seq,ts:Date.now(),name:t.name??"tool",...t.args!==void 0?{args:t.args}:{},...t.result!==void 0?{result:t.result}:{},status:t.toolStatus??(t.state==="tool-call"?"running":"completed"),...t.durationMs!==void 0?{durationMs:t.durationMs}:{},detailRef:{runId:t.runId,sourceSeq:t.seq}}),(t.toolCallId?M:g)(e,{id:o,sessionId:e,role:"agent",ts:Date.now(),payload:JSON.stringify({v:1,type:t.state==="tool-call"?"tool_use":"tool_result",name:t.name,toolCallId:o,status:t.toolStatus??(t.state==="tool-call"?"running":"completed"),...t.durationMs!==void 0?{durationMs:t.durationMs}:{},detailRef:{runId:t.runId,sourceSeq:t.seq},detailRevision:t.seq,detailAvailable:!0,resultOmitted:t.state==="tool-result"})}),s=!0}else t.state==="approval-pending"?(g(e,{id:`agent-${t.runId||"run"}-${t.seq}`,sessionId:e,role:"agent",ts:Date.now(),payload:b({...t.approval,permission:t.permission})}),s=!0,r&&p({sessionId:e,agentType:n,workDir:r.workDir,agentSessionId:r.agentSessionId,attentionId:q("approval",t.runId,t.seq)})):(t.state==="error"||t.state==="aborted")&&t.message&&(g(e,{id:`agent-${t.runId||"run"}-${t.seq}`,sessionId:e,role:"agent",ts:Date.now(),payload:t.message}),s=!0);s&&f(a.client);const l=`${e}:${t.runId}`;if(t.state==="delta"&&!t.thinking&&t.text&&a.runTextAcc.set(l,(a.runTextAcc.get(l)??"")+t.text),t.agentSessionId&&t.agentSessionId!==I&&(r?.nativeSessionVisibility==="internal"&&k(e,R(n,t.source)??n,t.agentSessionId),I=t.agentSessionId,a.nativeFusion?.noteManagedSourceSession(e,R(n,t.source),t.agentSessionId),r&&p({sessionId:e,agentType:n,workDir:r.workDir,agentSessionId:t.agentSessionId}),a.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:e,agentType:n,agentSessionId:t.agentSessionId}}})),t.state==="delta"){const o=t.text??"";if(!o)return;r?.pendingTextEvent&&r.pendingTextEvent.runId!==t.runId&&i(r),r&&!r.pendingTextEvent&&(r.pendingTextEvent={runId:t.runId,seq:t.seq,text:"",thinking:!1}),r?.pendingTextEvent&&(r.pendingTextEvent.text+=o,r.pendingTextEvent.seq=t.seq);return}let m={};if(t.state==="final"){i(r);const o=a.runTextAcc.get(l)??"";o&&(m={messageSummary:D(o)}),a.runTextAcc.delete(l)}else t.state==="error"||t.state==="aborted"?(i(r),a.runTextAcc.delete(l)):(t.state==="tool-call"||t.state==="tool-result"||t.state==="approval-pending")&&i(r);d&&r?.currentRunId===t.runId&&(S(a,r),r.currentRunId=null,r.currentRunPhase=null,r.nextEventSeq=0,T(r),a.chatQueue?.noteTerminal(e),p({sessionId:e,agentType:n,workDir:r.workDir,agentSessionId:r.agentSessionId,status:t.state==="final"?"completed":"failed",attentionId:q("terminal",t.runId),lastActivityAt:new Date().toISOString()}),f(a.client),r.journalRunId&&(a.runJournal?.remove(e,r.journalRunId),a.recoveryBarrier?.end(e),r.journalRunId=null)),h(t,m)}),u.on("error",t=>{console.error(`[chat.send] adapter error sessionId=${e} agentType=${n}: ${t.message}`);const r=a.sessions.get(e);S(a,r),r?.journalRunId&&(a.runJournal?.update(e,r.journalRunId,{phase:"terminal"}),a.runJournal?.remove(e,r.journalRunId),a.recoveryBarrier?.end(e),r.journalRunId=null),a.sessions.delete(e),a.chatQueue?.noteTerminal(e),a.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:e,message:t.message,runId:"",seq:0}})})}export{Q as bindAdapterEvents,R as getNativeSourceAgentType};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { AgentAdapter, AgentSendContext, AgentSendResult } from '../../agents/adapter.js';
|
|
2
1
|
import type { AgentType, ChatAttachmentMeta, ExternalChannelSessionStatus, SessionMode } from '@shennian/wire';
|
|
3
|
-
import type {
|
|
2
|
+
import type { AgentAdapter, AgentSendContext, AgentSendResult } from '../../agents/adapter.js';
|
|
4
3
|
import type { CompiledManagedAgentPolicy } from '../../security/managed-agent-policy.js';
|
|
4
|
+
import type { ActiveSession, SessionManagerRuntime } from '../types.js';
|
|
5
5
|
export declare function rememberProcessedReqId(runtime: SessionManagerRuntime, reqId: string): void;
|
|
6
6
|
export declare function disposeSession(runtime: SessionManagerRuntime, session: ActiveSession): Promise<void>;
|
|
7
7
|
export declare function sendAdapterTurn(adapter: AgentAdapter, text: string, modelId?: string, reasoningEffort?: string, attachments?: ChatAttachmentMeta[], systemPrompt?: string | null, context?: AgentSendContext): Promise<void | AgentSendResult>;
|