beeos-claw 0.6.88 → 0.6.91

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.
Files changed (48) hide show
  1. package/README.md +66 -6
  2. package/dist/acp-gateway/gateway-events.js +1 -1
  3. package/dist/acp-gateway/history-artifact-hydrator.js +1 -1
  4. package/dist/acp-gateway/history-replay.js +1 -1
  5. package/dist/acp-gateway/local-session-history.js +1 -1
  6. package/dist/canvas/html-delivery.js +1 -1
  7. package/dist/canvas/runtime-lease-token.js +1 -0
  8. package/dist/canvas/tools.js +1 -1
  9. package/dist/canvas-orchestrator.js +1 -1
  10. package/dist/index.js +1 -1
  11. package/dist/message-sanitization/transport-metadata-sanitizer.js +1 -1
  12. package/dist/runtime/agent-runtime-failure-classifier.js +1 -1
  13. package/dist/runtime/central-native-composition.js +1 -0
  14. package/dist/runtime/central-native-context-engine.js +1 -0
  15. package/dist/runtime/central-native-credentials.js +1 -0
  16. package/dist/runtime/central-native-delta.js +1 -0
  17. package/dist/runtime/central-native-host.js +1 -0
  18. package/dist/runtime/central-native-interactions.js +1 -0
  19. package/dist/runtime/central-native-memory.js +1 -0
  20. package/dist/runtime/central-native-pending-journal.js +1 -0
  21. package/dist/runtime/central-native-production.js +1 -0
  22. package/dist/runtime/central-native-response.js +1 -0
  23. package/dist/runtime/central-native-runtime.js +1 -0
  24. package/dist/runtime/central-native-titles.js +1 -0
  25. package/dist/runtime/central-session-key.js +1 -0
  26. package/dist/runtime/conversation-title.js +1 -1
  27. package/dist/runtime/identity-lane-lease.js +1 -0
  28. package/dist/runtime/im-reply-sink.js +1 -1
  29. package/dist/runtime/ms-runtime-message-channel-adapter.js +1 -1
  30. package/dist/runtime/ms-runtime-message-channel-consumer-factory.js +1 -1
  31. package/dist/runtime/native-cloud-prompt-bridge.js +1 -1
  32. package/dist/runtime/openclaw-session-write-quiescence.js +1 -1
  33. package/dist/runtime/reply-router.js +1 -1
  34. package/dist/runtime/runtime-command-journal.js +1 -1
  35. package/dist/runtime/runtime-production-authority.js +1 -1
  36. package/dist/runtime/runtime-service-contract-compatibility.js +1 -1
  37. package/dist/runtime/runtime-service-production-candidate.js +1 -1
  38. package/dist/runtime/sandbox-native-activation.js +1 -0
  39. package/dist/runtime/sandbox-native-credentials.js +1 -0
  40. package/dist/runtime/sandbox-native-execution-resolver.js +1 -0
  41. package/dist/runtime/session-mutation-coordinator.js +1 -1
  42. package/dist/terminal/runtime-terminal-outbound-registration.js +1 -1
  43. package/dist/terminal/session-manager.js +1 -1
  44. package/dist/terminal/standalone-runtime-terminal.js +1 -0
  45. package/dist/upload/native-output-tool-hook.js +1 -1
  46. package/openclaw.plugin.json +1 -1
  47. package/package.json +4 -4
  48. package/runtime-service-wire-identity.v1.json +1 -1
@@ -1 +1 @@
1
- import{createHash,randomUUID}from"node:crypto";import{readFile}from"node:fs/promises";import{canonicalizeJcs}from"@beeos-ai/beeos-types/runtime";import{createRuntimeAppendTimingRecorder}from"./runtime-append-timing.js";import{OwnedDirectoryLockContendedError,withOwnedDirectoryLock}from"./owned-directory-lock.js";import{MonotonicJournalSegments}from"./monotonic-journal-segments.js";const HASH_ONLY_RESULT_COMMANDS=new Set(["session.load","session.history","session.list","agent.listProjection","skills.list","models.list","cron.list","cron.status","cron.runs","mcp.list","terminal.ping","terminal.wait"]),SAFE_CANONICAL_RESULT_COMMANDS=new Set(["session.clear","session.setMode","session.setModel","session.cancel","skills.install","skills.uninstall","skills.setEnabled","cron.remove","mcp.prepare","mcp.set","mcp.unset","terminal.input","terminal.resize","terminal.close","terminal.release","terminal.kill","canvas.toggle","canvas.clear","canvas.reference","canvas.dimensions"]);export const runtimeCommandResultPersistencePolicy=e=>SAFE_CANONICAL_RESULT_COMMANDS.has(e)?"canonical_result":"hash_only";export const runtimeCommandIsReplayableQuery=e=>HASH_ONLY_RESULT_COMMANDS.has(e);export const runtimeCommandResultHash=e=>createHash("sha256").update(canonicalizeJcs(e)).digest("hex");const emptyDocument=()=>({version:2,lineage:{journalStoreId:randomUUID(),journalGeneration:"1"},records:[],cancellations:[]}),CHECKPOINT_INTERVAL_MS=6e5,CHECKPOINT_DELTA_COUNT=512,CHECKPOINT_DELTA_BYTES=8388608,CHECKPOINT_FOREGROUND_QUIET_MS=50;export class RuntimeCommandJournalConflictError extends Error{constructor(e){super(e),this.name="RuntimeCommandJournalConflictError"}}export class FileRuntimeCommandJournal{file;retentionMs;now;publication;segments;maintenance;forceCheckpointRequested=!1;maintenanceDue;maintenanceDueAtMs;prunePending=!1;foregroundUpdates=0;foregroundRevision=0;constructor(e,t=6048e5,n=Date.now,o={}){this.file=e,this.retentionMs=t,this.now=n,this.publication=o,this.segments=new MonotonicJournalSegments(e)}async lineage(){return await this.update(e=>({...e.lineage}))}async advanceLineageGeneration(e){return await this.update(t=>{if(t.lineage.journalStoreId!==e.journalStoreId)throw new RuntimeCommandJournalConflictError("journal store changed during incarnation advance");const n=BigInt(t.lineage.journalGeneration),o=BigInt(e.floor),r=(n>o?n:o)+1n;if(r>9223372036854775807n)throw new RuntimeCommandJournalConflictError("journal generation overflow");return t.lineage={journalStoreId:t.lineage.journalStoreId,journalGeneration:String(r)},{...t.lineage}})}async get(e){return(await this.read()).records.find(t=>t.operationId===e)??null}async getCancellation(e){return(await this.read()).cancellations.find(t=>t.operationId===e)??null}async listMcpLiveReleases(){const e=await this.read();return Object.freeze(e.records.flatMap(e=>e.mcpLiveRelease?[e.mcpLiveRelease]:[]))}async listMcpActiveDesiredStates(){const e=await this.read();return Object.freeze(e.records.flatMap(e=>e.mcpActiveDesiredState?[e.mcpActiveDesiredState]:[]))}async recordMcpDesiredStateCommit(e){const t=normalizeMcpDesiredStateCommit(e);return await this.update(e=>{const n=e.records.findIndex(e=>e.operationId===t.operationId);if(n<0)throw new RuntimeCommandJournalConflictError("MCP desired state has no command record");const o=e.records[n];if(assertMcpCommitBindsRecord(t,o),"started"!==o.phase){if(sameMcpDesiredStateCommit(o.mcpDesiredStateCommit,t))return o;throw new RuntimeCommandJournalConflictError("MCP desired state commit crossed its journal boundary")}if(o.mcpDesiredStateCommit){if(sameMcpDesiredStateCommit(o.mcpDesiredStateCommit,t))return o;throw new RuntimeCommandJournalConflictError("MCP desired state commit conflicts with journal")}const r={...o,mcpDesiredStateCommit:t,revision:o.revision+1,updatedAt:new Date(this.now()).toISOString()};return e.records[n]=r,r})}async releaseMcpLiveEffect(e){return await this.update(t=>{const n=t.records.findIndex(t=>t.operationId===e.operationId);if(n<0)throw new RuntimeCommandJournalConflictError("MCP release has no command record");const o=t.records[n];if("effect_committed"!==o.phase&&"projection_committed"!==o.phase)throw new RuntimeCommandJournalConflictError("MCP release requires a committed effect");if(!o.mcpDesiredStateCommit)throw new RuntimeCommandJournalConflictError("MCP release lacks a durable desired-state commit");if("service"===e.source&&!o.messageBinding)throw new RuntimeCommandJournalConflictError("Service MCP release lacks a Message Service request binding");const r=releaseFromCommit(o.mcpDesiredStateCommit,e,new Date(this.now()).toISOString());if(o.mcpLiveRelease){if(sameMcpLiveRelease(o.mcpLiveRelease,r))return o;throw new RuntimeCommandJournalConflictError("MCP release conflicts with journal")}const i={...o,mcpLiveRelease:r,revision:o.revision+1,updatedAt:new Date(this.now()).toISOString()};return t.records[n]=i,i})}async promoteMcpDesiredStateActive(e){return await this.update(t=>{const n=t.records.findIndex(t=>t.operationId===e.operationId);if(n<0)throw new RuntimeCommandJournalConflictError("MCP active promotion has no command record");const o=t.records[n],r=activeFromRelease(o.mcpLiveRelease,e,new Date(this.now()).toISOString());if(o.mcpActiveDesiredState){if(sameMcpActiveDesiredState(o.mcpActiveDesiredState,r))return o;throw new RuntimeCommandJournalConflictError("MCP active desired state conflicts with journal")}const i={...o,mcpActiveDesiredState:r,revision:o.revision+1,updatedAt:new Date(this.now()).toISOString()};return t.records[n]=i,i})}async acceptMessageRequest(e){return await this.update(t=>{let n=t.records.find(t=>t.operationId===e.operationId);if(n||(n={operationId:e.operationId,command:e.command,paramsHash:e.paramsHash,runtimeEpoch:e.runtimeEpoch,phase:"prepared",revision:1,updatedAt:new Date(this.now()).toISOString()},t.records.push(n)),n.command!==e.command||n.paramsHash!==e.paramsHash||n.runtimeEpoch!==e.runtimeEpoch)throw new RuntimeCommandJournalConflictError("message request conflicts with command journal");const o={requestMessageId:e.requestMessageId,requestHash:e.requestHash,acceptedAt:new Date(this.now()).toISOString()};if(n.messageBinding&&(n.messageBinding.requestMessageId!==o.requestMessageId||n.messageBinding.requestHash!==o.requestHash))throw new RuntimeCommandJournalConflictError("operation reused by a different message request");return n.messageBinding??=o,n})}async observeCancellation(e){return await this.update(t=>{const n=t.cancellations.find(t=>t.eventId===e.eventId);if(n){if(n.operationId!==e.operationId||n.requestMessageId!==e.requestMessageId||n.requestHash!==e.requestHash)throw new RuntimeCommandJournalConflictError("operation cancellation observation conflict");return n}const o=t.cancellations.find(t=>t.operationId===e.operationId);if(o&&(o.requestMessageId!==e.requestMessageId||o.requestHash!==e.requestHash))throw new RuntimeCommandJournalConflictError("operation cancellation request correlation conflict");const r={...e,observedAt:new Date(this.now()).toISOString()};return t.cancellations.push(r),r})}async prepare(e){return await this.update(t=>{const n=t.records.find(t=>t.operationId===e.operationId);if(n){if(n.command!==e.command||n.paramsHash!==e.paramsHash)throw new RuntimeCommandJournalConflictError("operation identity reused with different command or params hash");if(n.runtimeEpoch!==e.runtimeEpoch)throw new RuntimeCommandJournalConflictError("operation belongs to a different runtime epoch");return n}const o={...e,phase:"prepared",revision:1,updatedAt:new Date(this.now()).toISOString()};return t.records.push(o),o})}async transition(e){return await this.update(t=>{const n=t.records.findIndex(t=>t.operationId===e.operationId);if(n<0)throw new RuntimeCommandJournalConflictError("operation was not prepared");const o=t.records[n];if(o.revision!==e.expectedRevision)throw new RuntimeCommandJournalConflictError("journal revision conflict");const r="effect_committed"===o.phase&&"effect_committed"===e.phase&&void 0!==e.agentProjection&&void 0===o.agentProjectionIntent&&"pending"===o.agentProjectionPreparationFailure?.state;if("projection_committed"===o.phase||"failed"===o.phase||"outcome_unknown"===o.phase||"effect_committed"===o.phase&&"projection_committed"!==e.phase&&!r)throw new RuntimeCommandJournalConflictError("terminal journal record is immutable");if(void 0!==e.projectionSnapshot&&"projection_committed"!==e.phase)throw new RuntimeCommandJournalConflictError("projection receipt requires projection_committed phase");if(void 0!==e.agentProjection&&"effect_committed"!==e.phase)throw new RuntimeCommandJournalConflictError("agent projection intent requires effect_committed phase");const i=void 0===e.agentProjection?void 0:normalizeAgentProjectionPreparation(e.agentProjection,o);if(r&&i&&!sameProjectionPreparationBase(o.agentProjectionPreparationFailure,i))throw new RuntimeCommandJournalConflictError("agent projection preparation conflicts with committed operation");const{agentProjectionIntent:a,agentProjectionPreparationFailure:s,...c}=o,d={...void 0===i?o:c,phase:e.phase,revision:o.revision+1,updatedAt:new Date(this.now()).toISOString(),...void 0===e.result?{}:persistedResult(o.command,e.result),...void 0===e.projectionSnapshot?{}:{projectionReceipt:projectionReceipt(e.projectionSnapshot)},...void 0===i?{}:isAgentProjectionIntent(i)?{agentProjectionIntent:i}:{agentProjectionPreparationFailure:i},...void 0===e.errorCode?{}:{errorCode:e.errorCode}};return t.records[n]=d,d})}async recordAgentProjectionIntent(e){return await this.update(t=>{const n=t.records.findIndex(t=>t.operationId===e.operationId);if(n<0)throw new RuntimeCommandJournalConflictError("operation was not prepared");const o=t.records[n];if(o.revision!==e.expectedRevision||"effect_committed"!==o.phase)throw new RuntimeCommandJournalConflictError("projection intent cannot mutate this command journal record");const r=normalizeAgentProjectionIntent(e.intent,o);if(o.agentProjectionIntent){if(canonicalizeJcs(o.agentProjectionIntent)!==canonicalizeJcs(r))throw new RuntimeCommandJournalConflictError("agent projection intent conflicts with committed operation");return o}if("pending"!==o.agentProjectionPreparationFailure?.state||!sameProjectionPreparationBase(o.agentProjectionPreparationFailure,r))throw new RuntimeCommandJournalConflictError("projection intent requires matching retryable preparation failure");const{agentProjectionPreparationFailure:i,...a}=o,s={...a,agentProjectionIntent:r,revision:o.revision+1,updatedAt:new Date(this.now()).toISOString()};return t.records[n]=s,s})}async recordAgentProjectionBlocked(e){return await this.update(t=>{const n=t.records.findIndex(t=>t.operationId===e.operationId);if(n<0)throw new RuntimeCommandJournalConflictError("operation was not prepared");const o=t.records[n];if(o.revision!==e.expectedRevision||"effect_committed"!==o.phase||!o.agentProjectionIntent||o.agentProjectionIntent.projectionRevision!==e.revision)throw new RuntimeCommandJournalConflictError("blocked projection cannot mutate this command journal record");if(o.agentProjectionTerminal){if("blocked"===o.agentProjectionTerminal.state&&o.agentProjectionTerminal.revision===e.revision)return o;throw new RuntimeCommandJournalConflictError("agent projection terminal conflicts with committed operation")}const r=agentProjectionTerminalReceipt(e.revision),i={...o,agentProjectionTerminal:r,revision:o.revision+1,updatedAt:new Date(this.now()).toISOString()};return t.records[n]=i,i})}async compact(){return this.maintenance?await this.maintenance:!1===this.maintenanceDue&&!this.prunePending&&void 0!==this.maintenanceDueAtMs&&Date.now()<this.maintenanceDueAtMs?0:await this.startMaintenance()}async checkpoint(){return this.forceCheckpointRequested=!0,await this.startMaintenance()}async read(){return(await this.readState()).document}async readState(){const e=await this.readLegacy(),t=new Map;if(e){t.set("meta:lineage",{value:e.lineage,logicalRevision:e.lineage.journalGeneration,publisherEpoch:"0"});for(const n of e.records)t.set(`record:${n.operationId}`,{value:n,logicalRevision:String(n.revision),publisherEpoch:"0"});for(const n of e.cancellations)t.set(`cancellation:${n.eventId}`,{value:n,logicalRevision:"1",publisherEpoch:"0"})}const n=await this.segments.readState(t),o=n.values,r=o.get("meta:lineage")?.value;return{document:{version:2,lineage:r??emptyDocument().lineage,records:[...o.entries()].filter(([e])=>e.startsWith("record:")).map(([,e])=>validateRecordReceipts(migrateLegacyCommittedPhase(e.value))),cancellations:[...o.entries()].filter(([e])=>e.startsWith("cancellation:")).map(([,e])=>e.value)},authorityFloor:n.authorityFloor,checkpointAtMs:n.checkpointAtMs,deltaCount:n.deltaCount,deltaBytes:n.deltaBytes,oldestDeltaAtMs:n.oldestDeltaAtMs,hasDurableLineage:o.has("meta:lineage")&&n.hasDurableState}}async readLegacy(){try{const e=JSON.parse(await readFile(this.file,"utf8"));if(!Array.isArray(e.records))throw new Error("unsupported command journal");const t=e.records.map(e=>validateRecordReceipts(migrateLegacyCommittedPhase(e)));if(1===e.version)return{...emptyDocument(),records:t};if(2!==e.version||!e.lineage?.journalStoreId||!/^[1-9][0-9]*$/.test(e.lineage.journalGeneration))throw new Error("unsupported command journal");return{...e,records:t,cancellations:Array.isArray(e.cancellations)?e.cancellations:[]}}catch(e){if("ENOENT"===e.code)return null;throw e}}async update(e){const t=createRuntimeAppendTimingRecorder("journal.update",{file:this.file});let n=!1;this.foregroundUpdates+=1;try{return await withOwnedDirectoryLock(`${this.file}.lock`,async o=>{t.mark("lock");const r=await this.readState();t.mark("read");const i=r.document,a=structuredClone(i),s=e(i),c=[];if(r.hasDurableLineage&&sameValue(a.lineage,i.lineage)||c.push({key:"meta:lineage",logicalRevision:i.lineage.journalGeneration,value:i.lineage}),diffValues(a.records,i.records,e=>e.operationId,e=>String(e.revision),"record:",c),diffValues(a.cancellations,i.cancellations,e=>e.eventId,()=>"1","cancellation:",c),0===c.length)return t.mark("noop"),s;await(this.publication.beforePublish?.(o.epoch)),t.mark("before-publish"),await o.assertOwned(),t.mark("assert-owned");const d=await this.segments.publishDelta(o.epoch,c,r.authorityFloor);t.mark("publish-delta");const m=Date.now();return this.maintenanceDueAtMs=checkpointDueAt(r,1,d,m),n=this.maintenanceDueAtMs<=m,this.maintenanceDue=n,s},{...this.publication.lockOptions,epochFloor:()=>this.segments.authorityFloor()})}finally{this.foregroundUpdates-=1,this.foregroundRevision+=1,t.finish(),n&&this.scheduleMaintenance()}}scheduleMaintenance(){this.startMaintenance().catch(e=>{try{this.publication.onMaintenanceError?this.publication.onMaintenanceError(e):emitMaintenanceWarning(e)}catch(e){emitMaintenanceWarning(e)}})}startMaintenance(){if(this.maintenance)return this.maintenance;const e=(async()=>{let e=0,t=!1;do{let n=this.forceCheckpointRequested;this.forceCheckpointRequested=!1,n||(await this.waitForForegroundQuiet(),n=this.forceCheckpointRequested,this.forceCheckpointRequested=!1);const o=await this.runMaintenance(n);e+=o.removed,t=o.contended}while(this.forceCheckpointRequested||t);return e})().finally(()=>{this.maintenance===e&&(this.maintenance=void 0)});return this.maintenance=e,e}async waitForForegroundQuiet(){for(;;){if(this.forceCheckpointRequested)return;const e=this.foregroundRevision;if(0===this.foregroundUpdates){if(await new Promise(e=>setTimeout(e,50)),this.forceCheckpointRequested||0===this.foregroundUpdates&&this.foregroundRevision===e)return}else await new Promise(e=>setTimeout(e,50))}}async runMaintenance(e){const t=createRuntimeAppendTimingRecorder("journal.checkpoint",{file:this.file});try{if(!e&&this.prunePending)return await this.segments.prune(),this.prunePending=!1,t.mark("prune-pending"),{removed:0,contended:!1};let n=!1;const o=await withOwnedDirectoryLock(`${this.file}.lock`,async o=>{t.mark("lock");const r=await this.readState();if(t.mark("read"),!e&&!checkpointDue(r))return this.maintenanceDue=!1,this.maintenanceDueAtMs=checkpointDueAt(r),t.mark("not-due"),0;const i=r.document,a=this.now()-this.retentionMs,s=i.records.length;i.records=i.records.filter(e=>"prepared"===e.phase||"started"===e.phase||Date.parse(e.updatedAt)>=a);const c=i.cancellations.length;i.cancellations=i.cancellations.filter(e=>Date.parse(e.observedAt)>=a);const d=s-i.records.length+c-i.cancellations.length,m=documentValues(i);return await(this.publication.beforePublish?.(o.epoch)),t.mark("before-publish"),await o.assertOwned(),t.mark("assert-owned"),await this.segments.checkpointSnapshot(o.epoch,m,r.authorityFloor),t.mark("publish-snapshot"),this.maintenanceDue=!1,this.maintenanceDueAtMs=Number.POSITIVE_INFINITY,this.prunePending=!0,n=!0,d},{...this.publication.lockOptions,failIfContended:!e,epochFloor:()=>this.segments.authorityFloor()});return t.mark("lock-released"),n&&(await this.segments.prune(),this.prunePending=!1,t.mark("prune")),{removed:o,contended:!1}}catch(n){if(!e&&n instanceof OwnedDirectoryLockContendedError)return t.mark("deferred-busy"),{removed:0,contended:!0};throw n}finally{t.finish(e?"forced":"scheduled")}}}function checkpointDue(e,t=0,n=0,o=Date.now()){return checkpointDueAt(e,t,n,o)<=o}function checkpointDueAt(e,t=0,n=0,o=Date.now()){const r=e.deltaCount+t,i=e.deltaBytes+n,a=e.oldestDeltaAtMs||(t>0?o:0),s=e.checkpointAtMs||a;return r>=512||i>=8388608?o:r>0&&s>0?s+6e5:Number.POSITIVE_INFINITY}function emitMaintenanceWarning(e){process.emitWarning(e instanceof Error?e.message:String(e),{code:"BEEOS_RUNTIME_JOURNAL_CHECKPOINT_FAILED"})}function diffValues(e,t,n,o,r,i){const a=new Map(e.map(e=>[n(e),e])),s=new Map(t.map(e=>[n(e),e]));for(const[e,t]of s){const n=a.get(e);void 0!==n&&sameValue(n,t)||i.push({key:`${r}${e}`,logicalRevision:o(t),value:t})}for(const[e,t]of a)s.has(e)||i.push({key:`${r}${e}`,logicalRevision:o(t),tombstone:!0})}function documentValues(e){return[{key:"meta:lineage",logicalRevision:e.lineage.journalGeneration,value:e.lineage},...e.records.map(e=>({key:`record:${e.operationId}`,logicalRevision:String(e.revision),value:e})),...e.cancellations.map(e=>({key:`cancellation:${e.eventId}`,logicalRevision:"1",value:e}))]}function sameValue(e,t){return canonicalizeJcs(e)===canonicalizeJcs(t)}function migrateLegacyCommittedPhase(e){return{...e,phase:"committed"===e.phase?"effect_committed":e.phase}}export class InMemoryRuntimeCommandJournal{records=new Map;cancellations=new Map;async get(e){return this.records.get(e)??null}async getCancellation(e){return[...this.cancellations.values()].find(t=>t.operationId===e)??null}async listMcpLiveReleases(){return Object.freeze([...this.records.values()].flatMap(e=>e.mcpLiveRelease?[e.mcpLiveRelease]:[]))}async listMcpActiveDesiredStates(){return Object.freeze([...this.records.values()].flatMap(e=>e.mcpActiveDesiredState?[e.mcpActiveDesiredState]:[]))}async recordMcpDesiredStateCommit(e){const t=normalizeMcpDesiredStateCommit(e),n=this.records.get(t.operationId);if(!n)throw new RuntimeCommandJournalConflictError("MCP desired state has no command record");if(assertMcpCommitBindsRecord(t,n),"started"!==n.phase){if(sameMcpDesiredStateCommit(n.mcpDesiredStateCommit,t))return n;throw new RuntimeCommandJournalConflictError("MCP desired state commit crossed its journal boundary")}if(n.mcpDesiredStateCommit){if(sameMcpDesiredStateCommit(n.mcpDesiredStateCommit,t))return n;throw new RuntimeCommandJournalConflictError("MCP desired state commit conflicts with journal")}const o={...n,mcpDesiredStateCommit:t,revision:n.revision+1,updatedAt:(new Date).toISOString()};return this.records.set(t.operationId,o),o}async releaseMcpLiveEffect(e){const t=this.records.get(e.operationId);if(!t)throw new RuntimeCommandJournalConflictError("MCP release has no command record");if("effect_committed"!==t.phase&&"projection_committed"!==t.phase)throw new RuntimeCommandJournalConflictError("MCP release requires a committed effect");if(!t.mcpDesiredStateCommit)throw new RuntimeCommandJournalConflictError("MCP release lacks a durable desired-state commit");if("service"===e.source&&!t.messageBinding)throw new RuntimeCommandJournalConflictError("Service MCP release lacks a Message Service request binding");const n=releaseFromCommit(t.mcpDesiredStateCommit,e,(new Date).toISOString());if(t.mcpLiveRelease){if(sameMcpLiveRelease(t.mcpLiveRelease,n))return t;throw new RuntimeCommandJournalConflictError("MCP release conflicts with journal")}const o={...t,mcpLiveRelease:n,revision:t.revision+1,updatedAt:(new Date).toISOString()};return this.records.set(e.operationId,o),o}async promoteMcpDesiredStateActive(e){const t=this.records.get(e.operationId);if(!t)throw new RuntimeCommandJournalConflictError("MCP active promotion has no command record");const n=activeFromRelease(t.mcpLiveRelease,e,(new Date).toISOString());if(t.mcpActiveDesiredState){if(sameMcpActiveDesiredState(t.mcpActiveDesiredState,n))return t;throw new RuntimeCommandJournalConflictError("MCP active desired state conflicts with journal")}const o={...t,mcpActiveDesiredState:n,revision:t.revision+1,updatedAt:(new Date).toISOString()};return this.records.set(e.operationId,o),o}async acceptMessageRequest(e){let t=this.records.get(e.operationId);if(t||(t={operationId:e.operationId,command:e.command,paramsHash:e.paramsHash,runtimeEpoch:e.runtimeEpoch,phase:"prepared",revision:1,updatedAt:(new Date).toISOString()},this.records.set(e.operationId,t)),t.command!==e.command||t.paramsHash!==e.paramsHash||t.runtimeEpoch!==e.runtimeEpoch||t.messageBinding&&(t.messageBinding.requestMessageId!==e.requestMessageId||t.messageBinding.requestHash!==e.requestHash))throw new RuntimeCommandJournalConflictError("message request conflicts with command journal");return t.messageBinding??={requestMessageId:e.requestMessageId,requestHash:e.requestHash,acceptedAt:(new Date).toISOString()},t}async observeCancellation(e){const t=this.cancellations.get(e.eventId);if(t){if(t.operationId!==e.operationId||t.requestMessageId!==e.requestMessageId||t.requestHash!==e.requestHash)throw new RuntimeCommandJournalConflictError("operation cancellation observation conflict");return t}const n=[...this.cancellations.values()].find(t=>t.operationId===e.operationId);if(n&&(n.requestMessageId!==e.requestMessageId||n.requestHash!==e.requestHash))throw new RuntimeCommandJournalConflictError("operation cancellation request correlation conflict");const o={...e,observedAt:(new Date).toISOString()};return this.cancellations.set(e.eventId,o),o}async prepare(e){const t=this.records.get(e.operationId);if(t){if(t.command!==e.command||t.paramsHash!==e.paramsHash||t.runtimeEpoch!==e.runtimeEpoch)throw new RuntimeCommandJournalConflictError("operation identity conflict");return t}const n={...e,phase:"prepared",revision:1,updatedAt:(new Date).toISOString()};return this.records.set(e.operationId,n),n}async transition(e){const t=this.records.get(e.operationId);if(!t||t.revision!==e.expectedRevision)throw new RuntimeCommandJournalConflictError("revision conflict");const n="effect_committed"===t.phase&&"effect_committed"===e.phase&&void 0!==e.agentProjection&&void 0===t.agentProjectionIntent&&"pending"===t.agentProjectionPreparationFailure?.state;if("projection_committed"===t.phase||"failed"===t.phase||"outcome_unknown"===t.phase||"effect_committed"===t.phase&&"projection_committed"!==e.phase&&!n)throw new RuntimeCommandJournalConflictError("terminal journal record is immutable");if(void 0!==e.projectionSnapshot&&"projection_committed"!==e.phase)throw new RuntimeCommandJournalConflictError("projection receipt requires projection_committed phase");if(void 0!==e.agentProjection&&"effect_committed"!==e.phase)throw new RuntimeCommandJournalConflictError("agent projection intent requires effect_committed phase");const o=void 0===e.agentProjection?void 0:normalizeAgentProjectionPreparation(e.agentProjection,t);if(n&&o&&!sameProjectionPreparationBase(t.agentProjectionPreparationFailure,o))throw new RuntimeCommandJournalConflictError("agent projection preparation conflicts with committed operation");const{agentProjectionIntent:r,agentProjectionPreparationFailure:i,...a}=t,s={...void 0===o?t:a,phase:e.phase,revision:t.revision+1,updatedAt:(new Date).toISOString(),...void 0===e.result?{}:persistedResult(t.command,e.result),...void 0===e.projectionSnapshot?{}:{projectionReceipt:projectionReceipt(e.projectionSnapshot)},...void 0===o?{}:isAgentProjectionIntent(o)?{agentProjectionIntent:o}:{agentProjectionPreparationFailure:o},...void 0===e.errorCode?{}:{errorCode:e.errorCode}};return this.records.set(e.operationId,s),s}async recordAgentProjectionIntent(e){const t=this.records.get(e.operationId);if(!t||t.revision!==e.expectedRevision||"effect_committed"!==t.phase)throw new RuntimeCommandJournalConflictError("projection intent cannot mutate this command journal record");const n=normalizeAgentProjectionIntent(e.intent,t);if(t.agentProjectionIntent){if(canonicalizeJcs(t.agentProjectionIntent)!==canonicalizeJcs(n))throw new RuntimeCommandJournalConflictError("agent projection intent conflicts with committed operation");return t}if("pending"!==t.agentProjectionPreparationFailure?.state||!sameProjectionPreparationBase(t.agentProjectionPreparationFailure,n))throw new RuntimeCommandJournalConflictError("projection intent requires matching retryable preparation failure");const{agentProjectionPreparationFailure:o,...r}=t,i={...r,agentProjectionIntent:n,revision:t.revision+1,updatedAt:(new Date).toISOString()};return this.records.set(e.operationId,i),i}async recordAgentProjectionBlocked(e){const t=this.records.get(e.operationId);if(!t||t.revision!==e.expectedRevision||"effect_committed"!==t.phase||!t.agentProjectionIntent||t.agentProjectionIntent.projectionRevision!==e.revision)throw new RuntimeCommandJournalConflictError("blocked projection cannot mutate this command journal record");if(t.agentProjectionTerminal){if("blocked"===t.agentProjectionTerminal.state&&t.agentProjectionTerminal.revision===e.revision)return t;throw new RuntimeCommandJournalConflictError("agent projection terminal conflicts with committed operation")}const n={...t,agentProjectionTerminal:agentProjectionTerminalReceipt(e.revision),revision:t.revision+1,updatedAt:(new Date).toISOString()};return this.records.set(e.operationId,n),n}async compact(){return 0}async checkpoint(){return 0}}function persistedResult(e,t){return"canonical_result"===runtimeCommandResultPersistencePolicy(e)?{result:t}:{resultReceipt:{policy:"hash_only",resultHash:runtimeCommandResultHash(t),redacted:{kind:runtimeCommandIsReplayableQuery(e)?"query_result":"mutation_result",command:e}}}}function projectionReceipt(e){if("synced"!==e.state||!/^(0|[1-9][0-9]*)$/.test(e.revision))throw new Error("invalid projection revision");if(void 0!==e.platformAgentId&&!/^[a-zA-Z0-9._:-]{1,256}$/.test(e.platformAgentId))throw new Error("invalid projection platform agent id");const t=void 0===e.mcpReceipt?void 0:normalizeMcpProjectionReceipt(e.mcpReceipt),n=Object.freeze({revision:e.revision,state:"synced",..."string"==typeof e.platformAgentId&&e.platformAgentId?{platformAgentId:e.platformAgentId}:{},...t?{mcpReceipt:t}:{}});return Object.freeze({schemaVersion:1,revision:n.revision,snapshot:n,snapshotHash:runtimeCommandResultHash(n)})}function validateRecordReceipts(e){if(void 0!==e.agentProjectionIntent&&void 0!==e.agentProjectionPreparationFailure)throw new Error("agent projection journal record is ambiguous");const t=void 0!==e.agentProjectionIntent?{...e,agentProjectionIntent:normalizeAgentProjectionIntent(e.agentProjectionIntent,e)}:void 0!==e.agentProjectionPreparationFailure?{...e,agentProjectionPreparationFailure:normalizeAgentProjectionPreparation(e.agentProjectionPreparationFailure,e)}:e,n=void 0===t.agentProjectionTerminal?t:{...t,agentProjectionTerminal:normalizeAgentProjectionTerminal(t.agentProjectionTerminal)};if(n.agentProjectionTerminal&&(!n.agentProjectionIntent||"effect_committed"!==n.phase||n.agentProjectionIntent.projectionRevision!==n.agentProjectionTerminal.revision))throw new Error("invalid agent projection terminal receipt");const o=void 0===n.mcpDesiredStateCommit?void 0:normalizeMcpDesiredStateCommit(n.mcpDesiredStateCommit);o&&assertMcpCommitBindsRecord(o,n);const r=void 0===n.mcpLiveRelease?void 0:normalizeMcpLiveRelease(n.mcpLiveRelease);if(r&&(!o||!sameMcpDesiredStateCommit(o,r)||"effect_committed"!==n.phase&&"projection_committed"!==n.phase||"service"===r.source&&!n.messageBinding))throw new Error("invalid MCP live release receipt");const i=void 0===n.mcpActiveDesiredState?void 0:normalizeMcpActiveDesiredState(n.mcpActiveDesiredState);if(i&&(!r||!sameMcpDesiredStateCommit(i,r)||i.effectRecordHash!==r.effectRecordHash||i.releasedAt!==r.releasedAt))throw new Error("invalid MCP active desired-state receipt");const a={...n,...o?{mcpDesiredStateCommit:o}:{},...r?{mcpLiveRelease:r}:{},...i?{mcpActiveDesiredState:i}:{}};if(!a.projectionReceipt)return a;const s=a.projectionReceipt,c=["revision","state",...void 0===s.snapshot?.platformAgentId?[]:["platformAgentId"],...void 0===s.snapshot?.mcpReceipt?[]:["mcpReceipt"]].sort().join(",");if("revision,schemaVersion,snapshot,snapshotHash"!==Object.keys(s).sort().join(",")||Object.keys(s.snapshot??{}).sort().join(",")!==c||"projection_committed"!==a.phase||1!==s.schemaVersion||!/^(0|[1-9][0-9]*)$/.test(s.revision)||"synced"!==s.snapshot?.state||s.snapshot.revision!==s.revision)throw new Error("invalid projection receipt");const d=projectionReceipt(s.snapshot),m=d.snapshot.mcpReceipt;if(m&&(m.operationId!==a.operationId||m.method!==a.command||m.runtimeEpoch!==a.runtimeEpoch))throw new Error("MCP projection receipt does not bind journal operation");if(d.snapshotHash!==s.snapshotHash)throw new Error("invalid projection receipt hash");return{...a,projectionReceipt:d}}function normalizeMcpDesiredStateCommit(e){if(!e||"object"!=typeof e||"desiredStateHash,desiredStateRevision,method,operationId,paramsHash,runtimeEpoch,schemaVersion,server,source"!==Object.keys(e).sort().join(",")||1!==e.schemaVersion||"service"!==e.source&&"acp_local"!==e.source||!safeReceiptIdentifier(e.operationId)||"mcp.set"!==e.method&&"mcp.unset"!==e.method||!isSha256(e.paramsHash)||!/^(0|[1-9][0-9]*)$/.test(e.runtimeEpoch)||!/^(0|[1-9][0-9]*)$/.test(e.desiredStateRevision)||!isSha256(e.desiredStateHash))throw new Error("invalid MCP desired-state commit");return Object.freeze({schemaVersion:1,source:e.source,operationId:e.operationId,method:e.method,paramsHash:e.paramsHash,runtimeEpoch:e.runtimeEpoch,desiredStateRevision:e.desiredStateRevision,desiredStateHash:e.desiredStateHash,server:normalizeMcpReleaseServer(e.server)})}function normalizeMcpLiveRelease(e){if(!e||"object"!=typeof e)throw new Error("invalid MCP live release receipt");const t=["desiredStateHash","desiredStateRevision","method","operationId","paramsHash","releasedAt","runtimeEpoch","schemaVersion","server","source",..."service"===e.source?["effectRecordHash"]:[]].sort().join(",");if(Object.keys(e).sort().join(",")!==t||"service"===e.source&&!isSha256(e.effectRecordHash)||"acp_local"===e.source&&void 0!==e.effectRecordHash||"string"!=typeof e.releasedAt||!Number.isFinite(Date.parse(e.releasedAt)))throw new Error("invalid MCP live release receipt");const n=normalizeMcpDesiredStateCommit({schemaVersion:e.schemaVersion,source:e.source,operationId:e.operationId,method:e.method,paramsHash:e.paramsHash,runtimeEpoch:e.runtimeEpoch,desiredStateRevision:e.desiredStateRevision,desiredStateHash:e.desiredStateHash,server:e.server});return Object.freeze({...n,...e.effectRecordHash?{effectRecordHash:e.effectRecordHash}:{},releasedAt:e.releasedAt})}function normalizeMcpActiveDesiredState(e){if(!e||"object"!=typeof e)throw new Error("invalid MCP active desired-state receipt");const t=["activatedAt","desiredStateHash","desiredStateRevision","method","operationId","paramsHash","releasedAt","runtimeEpoch","schemaVersion","server","source",..."service"===e.source?["effectRecordHash","terminalMessageId"]:[]].sort().join(",");if(Object.keys(e).sort().join(",")!==t||"string"!=typeof e.activatedAt||!Number.isFinite(Date.parse(e.activatedAt))||"service"===e.source&&!safeReceiptIdentifier(e.terminalMessageId))throw new Error("invalid MCP active desired-state receipt");const n=normalizeMcpLiveRelease({schemaVersion:e.schemaVersion,source:e.source,operationId:e.operationId,method:e.method,paramsHash:e.paramsHash,runtimeEpoch:e.runtimeEpoch,desiredStateRevision:e.desiredStateRevision,desiredStateHash:e.desiredStateHash,server:e.server,releasedAt:e.releasedAt,...e.effectRecordHash?{effectRecordHash:e.effectRecordHash}:{}});return Object.freeze({...n,...e.terminalMessageId?{terminalMessageId:e.terminalMessageId}:{},activatedAt:e.activatedAt})}function normalizeMcpReleaseServer(e){const t=e&&"object"==typeof e?Object.keys(e).sort().join(","):"",n=!0===e?.removed;if(!(t===(n?"name,removed":void 0===e?.credentialRef?"definitionRef,definitionSha256,name,version":"credentialRef,definitionRef,definitionSha256,name,version")&&safeReceiptIdentifier(e?.name)&&(n||safeMcpReference(e?.definitionRef)&&safeMcpReference(e?.version)&&isSha256(e?.definitionSha256)&&(void 0===e?.credentialRef||safeMcpReference(e.credentialRef)))))throw new Error("invalid MCP desired-state server identity");return Object.freeze(n?{name:e.name,removed:!0}:{name:e.name,definitionRef:e.definitionRef,version:e.version,definitionSha256:e.definitionSha256,...e.credentialRef?{credentialRef:e.credentialRef}:{}})}function assertMcpCommitBindsRecord(e,t){if(e.operationId!==t.operationId||e.method!==t.command||e.paramsHash!==t.paramsHash||e.runtimeEpoch!==t.runtimeEpoch)throw new RuntimeCommandJournalConflictError("MCP desired-state commit does not bind command journal")}function releaseFromCommit(e,t,n){if(t.operationId!==e.operationId||t.source!==e.source||"service"===t.source&&!isSha256(t.effectRecordHash)||"acp_local"===t.source&&void 0!==t.effectRecordHash)throw new RuntimeCommandJournalConflictError("MCP live release proof does not bind desired state");return Object.freeze({...e,...t.effectRecordHash?{effectRecordHash:t.effectRecordHash}:{},releasedAt:n})}function activeFromRelease(e,t,n){if(!(e&&t.operationId===e.operationId&&t.source===e.source&&Number.isFinite(Date.parse(n))&&("service"!==t.source||isSha256(t.effectRecordHash)&&t.effectRecordHash===e.effectRecordHash&&safeReceiptIdentifier(t.terminalMessageId))&&("acp_local"!==t.source||void 0===t.effectRecordHash&&void 0===t.terminalMessageId)))throw new RuntimeCommandJournalConflictError("MCP active promotion does not bind terminal release proof");return Object.freeze({...e,...t.terminalMessageId?{terminalMessageId:t.terminalMessageId}:{},activatedAt:n})}function sameMcpDesiredStateCommit(e,t){if(!e||!t)return!1;const n=e=>({schemaVersion:e.schemaVersion,source:e.source,operationId:e.operationId,method:e.method,paramsHash:e.paramsHash,runtimeEpoch:e.runtimeEpoch,desiredStateRevision:e.desiredStateRevision,desiredStateHash:e.desiredStateHash,server:e.server});return canonicalizeJcs(n(e))===canonicalizeJcs(n(t))}function sameMcpLiveRelease(e,t){const n=e=>({...e,releasedAt:""});return canonicalizeJcs(n(e))===canonicalizeJcs(n(t))}function sameMcpActiveDesiredState(e,t){const n=e=>({...e,releasedAt:"",activatedAt:""});return canonicalizeJcs(n(e))===canonicalizeJcs(n(t))}function normalizeMcpProjectionReceipt(e){if(!e||"object"!=typeof e||"desiredStateHash,desiredStateRevision,method,operationId,runtimeEpoch,schemaVersion,server"!==Object.keys(e).sort().join(",")||1!==e.schemaVersion||!safeReceiptIdentifier(e.operationId)||"mcp.set"!==e.method&&"mcp.unset"!==e.method||!/^(0|[1-9][0-9]*)$/.test(e.runtimeEpoch)||!/^(0|[1-9][0-9]*)$/.test(e.desiredStateRevision)||!isSha256(e.desiredStateHash))throw new Error("invalid MCP projection receipt");const t=e.server;if((t&&"object"==typeof t?Object.keys(t).sort().join(","):"")!==(!0===t?.removed?"name,removed":void 0===t?.definitionRef?"name":"definitionRef,definitionSha256,name")||!safeReceiptIdentifier(t?.name)||void 0!==t?.definitionRef&&(!safeReceiptIdentifier(t.definitionRef)||!isSha256(t.definitionSha256)))throw new Error("invalid MCP projection server receipt");return Object.freeze({schemaVersion:1,operationId:e.operationId,method:e.method,runtimeEpoch:e.runtimeEpoch,desiredStateRevision:e.desiredStateRevision,desiredStateHash:e.desiredStateHash,server:Object.freeze(!0===t.removed?{name:t.name,removed:!0}:void 0===t.definitionRef?{name:t.name}:{name:t.name,definitionRef:t.definitionRef,definitionSha256:t.definitionSha256})})}function isSha256(e){return"string"==typeof e&&/^[a-f0-9]{64}$/.test(e)}function safeReceiptIdentifier(e){return"string"==typeof e&&/^[a-zA-Z0-9._:@/-]{1,512}$/.test(e)&&!/(authorization|bearer|credential|secret|token|password|private(?:_|-)?key|headers?|env)/i.test(e)}function safeMcpReference(e){return"string"==typeof e&&/^[a-zA-Z0-9._:@/-]{1,512}$/.test(e)&&!/(authorization|bearer|secret|token|password|private(?:_|-)?key|headers?|env)/i.test(e)}function agentProjectionTerminalReceipt(e){if(!/^(0|[1-9][0-9]*)$/.test(e))throw new Error("invalid agent projection terminal revision");return Object.freeze({schemaVersion:1,state:"blocked",revision:e})}function normalizeAgentProjectionTerminal(e){if("revision,schemaVersion,state"!==Object.keys(e).sort().join(",")||1!==e.schemaVersion||"blocked"!==e.state)throw new Error("invalid agent projection terminal receipt");return agentProjectionTerminalReceipt(e.revision)}function normalizeAgentProjectionIntent(e,t){const n=Object.keys(e).sort(),o=["canonicalHash","coreResultHash","method","operationId","projectionRevision","runtimeAgentUid","runtimeEpoch","runtimeLocalKey","schemaVersion","snapshotRef",...void 0===e.expectedPlatformAgentId?[]:["expectedPlatformAgentId"],...void 0===e.removedBindings?[]:["removedBindings"]].sort();if(n.length!==o.length||n.some((e,t)=>e!==o[t])||1!==e.schemaVersion||!["agent.create","agent.update","agent.delete","agent.applyTemplate"].includes(e.method)||!nonEmptyText(e.operationId)||!nonEmptyText(e.runtimeAgentUid)||!nonEmptyText(e.runtimeLocalKey)||!/^(0|[1-9][0-9]*)$/.test(e.runtimeEpoch)||e.operationId!==t.operationId||e.method!==t.command||e.runtimeEpoch!==t.runtimeEpoch||void 0!==e.expectedPlatformAgentId&&!nonEmptyText(e.expectedPlatformAgentId)||!/^[0-9a-f]{64}$/.test(e.coreResultHash)||!/^(0|[1-9][0-9]*)$/.test(e.projectionRevision)||!/^[0-9a-f]{64}$/.test(e.canonicalHash)||!nonEmptyText(e.snapshotRef)||e.snapshotRef!==projectionSnapshotRef(e.runtimeEpoch,e.projectionRevision,e.canonicalHash)||void 0!==e.removedBindings&&(!Number.isSafeInteger(e.removedBindings)||e.removedBindings<0))throw new Error("invalid agent projection intent");return Object.freeze({...e})}function normalizeAgentProjectionPreparation(e,t){if(isAgentProjectionIntent(e))return normalizeAgentProjectionIntent(e,t);const n=e,o=Object.keys(n).sort(),r=["coreResultHash","errorKind","method","operationId","runtimeAgentUid","runtimeEpoch","runtimeLocalKey","schemaVersion","state",...void 0===n.expectedPlatformAgentId?[]:["expectedPlatformAgentId"],...void 0===n.removedBindings?[]:["removedBindings"]].sort();if(o.length!==r.length||o.some((e,t)=>e!==r[t])||1!==n.schemaVersion||!["agent.create","agent.update","agent.delete","agent.applyTemplate"].includes(n.method)||n.operationId!==t.operationId||n.method!==t.command||n.runtimeEpoch!==t.runtimeEpoch||!nonEmptyText(n.runtimeAgentUid)||!nonEmptyText(n.runtimeLocalKey)||!/^(0|[1-9][0-9]*)$/.test(n.runtimeEpoch)||!/^[0-9a-f]{64}$/.test(n.coreResultHash)||"pending"!==n.state&&"blocked"!==n.state||"snapshot_retryable"!==n.errorKind&&"snapshot_nonretryable"!==n.errorKind||"pending"===n.state!=("snapshot_retryable"===n.errorKind)||void 0!==n.expectedPlatformAgentId&&!nonEmptyText(n.expectedPlatformAgentId)||void 0!==n.removedBindings&&(!Number.isSafeInteger(n.removedBindings)||n.removedBindings<0))throw new Error("invalid agent projection preparation failure");return Object.freeze({...n})}function sameProjectionPreparationBase(e,t){return e.operationId===t.operationId&&e.method===t.method&&e.runtimeEpoch===t.runtimeEpoch&&e.runtimeAgentUid===t.runtimeAgentUid&&e.runtimeLocalKey===t.runtimeLocalKey&&e.coreResultHash===t.coreResultHash&&e.expectedPlatformAgentId===t.expectedPlatformAgentId&&e.removedBindings===t.removedBindings}function isAgentProjectionIntent(e){return"projectionRevision"in e}function projectionSnapshotRef(e,t,n){return`runtime-agent-projection:v1:${e}:${t}:${n}`}function nonEmptyText(e){return"string"==typeof e&&e.length>0}
1
+ import{createHash,randomUUID}from"node:crypto";import{readFile}from"node:fs/promises";import{canonicalizeJcs}from"@beeos-ai/beeos-types/runtime";import{createRuntimeAppendTimingRecorder}from"./runtime-append-timing.js";import{OwnedDirectoryLockContendedError,withOwnedDirectoryLock}from"./owned-directory-lock.js";import{MonotonicJournalSegments}from"./monotonic-journal-segments.js";const HASH_ONLY_RESULT_COMMANDS=new Set(["session.load","session.history","session.list","agent.listProjection","skills.list","models.list","cron.list","cron.status","cron.runs","mcp.list","terminal.ping","terminal.wait"]),SAFE_CANONICAL_RESULT_COMMANDS=new Set(["session.clear","session.setMode","session.setModel","session.cancel","skills.install","skills.uninstall","skills.setEnabled","cron.remove","mcp.prepare","mcp.set","mcp.unset","terminal.input","terminal.resize","terminal.close","terminal.release","terminal.kill","canvas.toggle","canvas.clear","canvas.reference","canvas.dimensions"]);export const runtimeCommandResultPersistencePolicy=e=>SAFE_CANONICAL_RESULT_COMMANDS.has(e)?"canonical_result":"hash_only";export const runtimeCommandIsReplayableQuery=e=>HASH_ONLY_RESULT_COMMANDS.has(e);export const runtimeCommandResultHash=e=>createHash("sha256").update(canonicalizeJcs(e)).digest("hex");const emptyDocument=()=>({version:2,lineage:{journalStoreId:randomUUID(),journalGeneration:"1"},records:[],cancellations:[]}),CHECKPOINT_INTERVAL_MS=6e5,CHECKPOINT_DELTA_COUNT=512,CHECKPOINT_DELTA_BYTES=8388608,CHECKPOINT_FOREGROUND_QUIET_MS=50;export class RuntimeCommandJournalConflictError extends Error{constructor(e){super(e),this.name="RuntimeCommandJournalConflictError"}}export class FileRuntimeCommandJournal{file;retentionMs;now;publication;segments;maintenance;forceCheckpointRequested=!1;maintenanceDue;maintenanceDueAtMs;prunePending=!1;foregroundUpdates=0;foregroundRevision=0;constructor(e,t=6048e5,n=Date.now,o={}){this.file=e,this.retentionMs=t,this.now=n,this.publication=o,this.segments=new MonotonicJournalSegments(e)}async lineage(){return await this.update(e=>({...e.lineage}))}async advanceLineageGeneration(e){return await this.update(t=>{if(t.lineage.journalStoreId!==e.journalStoreId)throw new RuntimeCommandJournalConflictError("journal store changed during incarnation advance");const n=BigInt(t.lineage.journalGeneration),o=BigInt(e.floor),r=(n>o?n:o)+1n;if(r>9223372036854775807n)throw new RuntimeCommandJournalConflictError("journal generation overflow");return t.lineage={journalStoreId:t.lineage.journalStoreId,journalGeneration:String(r)},{...t.lineage}})}async adoptExternalLineage(e){return await this.update(t=>{if(t.lineage.journalStoreId===e.journalStoreId&&BigInt(t.lineage.journalGeneration)>BigInt(e.journalGeneration))throw new RuntimeCommandJournalConflictError("external lineage generation regressed");return t.lineage={journalStoreId:e.journalStoreId,journalGeneration:e.journalGeneration},{...t.lineage}})}async get(e){return(await this.read()).records.find(t=>t.operationId===e)??null}async getCancellation(e){return(await this.read()).cancellations.find(t=>t.operationId===e)??null}async listMcpLiveReleases(){const e=await this.read();return Object.freeze(e.records.flatMap(e=>e.mcpLiveRelease?[e.mcpLiveRelease]:[]))}async listMcpActiveDesiredStates(){const e=await this.read();return Object.freeze(e.records.flatMap(e=>e.mcpActiveDesiredState?[e.mcpActiveDesiredState]:[]))}async recordMcpDesiredStateCommit(e){const t=normalizeMcpDesiredStateCommit(e);return await this.update(e=>{const n=e.records.findIndex(e=>e.operationId===t.operationId);if(n<0)throw new RuntimeCommandJournalConflictError("MCP desired state has no command record");const o=e.records[n];if(assertMcpCommitBindsRecord(t,o),"started"!==o.phase){if(sameMcpDesiredStateCommit(o.mcpDesiredStateCommit,t))return o;throw new RuntimeCommandJournalConflictError("MCP desired state commit crossed its journal boundary")}if(o.mcpDesiredStateCommit){if(sameMcpDesiredStateCommit(o.mcpDesiredStateCommit,t))return o;throw new RuntimeCommandJournalConflictError("MCP desired state commit conflicts with journal")}const r={...o,mcpDesiredStateCommit:t,revision:o.revision+1,updatedAt:new Date(this.now()).toISOString()};return e.records[n]=r,r})}async releaseMcpLiveEffect(e){return await this.update(t=>{const n=t.records.findIndex(t=>t.operationId===e.operationId);if(n<0)throw new RuntimeCommandJournalConflictError("MCP release has no command record");const o=t.records[n];if("effect_committed"!==o.phase&&"projection_committed"!==o.phase)throw new RuntimeCommandJournalConflictError("MCP release requires a committed effect");if(!o.mcpDesiredStateCommit)throw new RuntimeCommandJournalConflictError("MCP release lacks a durable desired-state commit");if("service"===e.source&&!o.messageBinding)throw new RuntimeCommandJournalConflictError("Service MCP release lacks a Message Service request binding");const r=releaseFromCommit(o.mcpDesiredStateCommit,e,new Date(this.now()).toISOString());if(o.mcpLiveRelease){if(sameMcpLiveRelease(o.mcpLiveRelease,r))return o;throw new RuntimeCommandJournalConflictError("MCP release conflicts with journal")}const i={...o,mcpLiveRelease:r,revision:o.revision+1,updatedAt:new Date(this.now()).toISOString()};return t.records[n]=i,i})}async promoteMcpDesiredStateActive(e){return await this.update(t=>{const n=t.records.findIndex(t=>t.operationId===e.operationId);if(n<0)throw new RuntimeCommandJournalConflictError("MCP active promotion has no command record");const o=t.records[n],r=activeFromRelease(o.mcpLiveRelease,e,new Date(this.now()).toISOString());if(o.mcpActiveDesiredState){if(sameMcpActiveDesiredState(o.mcpActiveDesiredState,r))return o;throw new RuntimeCommandJournalConflictError("MCP active desired state conflicts with journal")}const i={...o,mcpActiveDesiredState:r,revision:o.revision+1,updatedAt:new Date(this.now()).toISOString()};return t.records[n]=i,i})}async acceptMessageRequest(e){return await this.update(t=>{let n=t.records.find(t=>t.operationId===e.operationId);if(n||(n={operationId:e.operationId,command:e.command,paramsHash:e.paramsHash,runtimeEpoch:e.runtimeEpoch,phase:"prepared",revision:1,updatedAt:new Date(this.now()).toISOString()},t.records.push(n)),n.command!==e.command||n.paramsHash!==e.paramsHash||n.runtimeEpoch!==e.runtimeEpoch)throw new RuntimeCommandJournalConflictError("message request conflicts with command journal");const o={requestMessageId:e.requestMessageId,requestHash:e.requestHash,acceptedAt:new Date(this.now()).toISOString()};if(n.messageBinding&&(n.messageBinding.requestMessageId!==o.requestMessageId||n.messageBinding.requestHash!==o.requestHash))throw new RuntimeCommandJournalConflictError("operation reused by a different message request");return n.messageBinding??=o,n})}async observeCancellation(e){return await this.update(t=>{const n=t.cancellations.find(t=>t.eventId===e.eventId);if(n){if(n.operationId!==e.operationId||n.requestMessageId!==e.requestMessageId||n.requestHash!==e.requestHash)throw new RuntimeCommandJournalConflictError("operation cancellation observation conflict");return n}const o=t.cancellations.find(t=>t.operationId===e.operationId);if(o&&(o.requestMessageId!==e.requestMessageId||o.requestHash!==e.requestHash))throw new RuntimeCommandJournalConflictError("operation cancellation request correlation conflict");const r={...e,observedAt:new Date(this.now()).toISOString()};return t.cancellations.push(r),r})}async prepare(e){return await this.update(t=>{const n=t.records.find(t=>t.operationId===e.operationId);if(n){if(n.command!==e.command||n.paramsHash!==e.paramsHash)throw new RuntimeCommandJournalConflictError("operation identity reused with different command or params hash");if(n.runtimeEpoch!==e.runtimeEpoch)throw new RuntimeCommandJournalConflictError("operation belongs to a different runtime epoch");return n}const o={...e,phase:"prepared",revision:1,updatedAt:new Date(this.now()).toISOString()};return t.records.push(o),o})}async transition(e){return await this.update(t=>{const n=t.records.findIndex(t=>t.operationId===e.operationId);if(n<0)throw new RuntimeCommandJournalConflictError("operation was not prepared");const o=t.records[n];if(o.revision!==e.expectedRevision)throw new RuntimeCommandJournalConflictError("journal revision conflict");const r="effect_committed"===o.phase&&"effect_committed"===e.phase&&void 0!==e.agentProjection&&void 0===o.agentProjectionIntent&&"pending"===o.agentProjectionPreparationFailure?.state;if("projection_committed"===o.phase||"failed"===o.phase||"outcome_unknown"===o.phase||"effect_committed"===o.phase&&"projection_committed"!==e.phase&&!r)throw new RuntimeCommandJournalConflictError("terminal journal record is immutable");if(void 0!==e.projectionSnapshot&&"projection_committed"!==e.phase)throw new RuntimeCommandJournalConflictError("projection receipt requires projection_committed phase");if(void 0!==e.agentProjection&&"effect_committed"!==e.phase)throw new RuntimeCommandJournalConflictError("agent projection intent requires effect_committed phase");const i=void 0===e.agentProjection?void 0:normalizeAgentProjectionPreparation(e.agentProjection,o);if(r&&i&&!sameProjectionPreparationBase(o.agentProjectionPreparationFailure,i))throw new RuntimeCommandJournalConflictError("agent projection preparation conflicts with committed operation");const{agentProjectionIntent:a,agentProjectionPreparationFailure:s,...c}=o,d={...void 0===i?o:c,phase:e.phase,revision:o.revision+1,updatedAt:new Date(this.now()).toISOString(),...void 0===e.result?{}:persistedResult(o.command,e.result),...void 0===e.projectionSnapshot?{}:{projectionReceipt:projectionReceipt(e.projectionSnapshot)},...void 0===i?{}:isAgentProjectionIntent(i)?{agentProjectionIntent:i}:{agentProjectionPreparationFailure:i},...void 0===e.errorCode?{}:{errorCode:e.errorCode}};return t.records[n]=d,d})}async recordAgentProjectionIntent(e){return await this.update(t=>{const n=t.records.findIndex(t=>t.operationId===e.operationId);if(n<0)throw new RuntimeCommandJournalConflictError("operation was not prepared");const o=t.records[n];if(o.revision!==e.expectedRevision||"effect_committed"!==o.phase)throw new RuntimeCommandJournalConflictError("projection intent cannot mutate this command journal record");const r=normalizeAgentProjectionIntent(e.intent,o);if(o.agentProjectionIntent){if(canonicalizeJcs(o.agentProjectionIntent)!==canonicalizeJcs(r))throw new RuntimeCommandJournalConflictError("agent projection intent conflicts with committed operation");return o}if("pending"!==o.agentProjectionPreparationFailure?.state||!sameProjectionPreparationBase(o.agentProjectionPreparationFailure,r))throw new RuntimeCommandJournalConflictError("projection intent requires matching retryable preparation failure");const{agentProjectionPreparationFailure:i,...a}=o,s={...a,agentProjectionIntent:r,revision:o.revision+1,updatedAt:new Date(this.now()).toISOString()};return t.records[n]=s,s})}async recordAgentProjectionBlocked(e){return await this.update(t=>{const n=t.records.findIndex(t=>t.operationId===e.operationId);if(n<0)throw new RuntimeCommandJournalConflictError("operation was not prepared");const o=t.records[n];if(o.revision!==e.expectedRevision||"effect_committed"!==o.phase||!o.agentProjectionIntent||o.agentProjectionIntent.projectionRevision!==e.revision)throw new RuntimeCommandJournalConflictError("blocked projection cannot mutate this command journal record");if(o.agentProjectionTerminal){if("blocked"===o.agentProjectionTerminal.state&&o.agentProjectionTerminal.revision===e.revision)return o;throw new RuntimeCommandJournalConflictError("agent projection terminal conflicts with committed operation")}const r=agentProjectionTerminalReceipt(e.revision),i={...o,agentProjectionTerminal:r,revision:o.revision+1,updatedAt:new Date(this.now()).toISOString()};return t.records[n]=i,i})}async compact(){return this.maintenance?await this.maintenance:!1===this.maintenanceDue&&!this.prunePending&&void 0!==this.maintenanceDueAtMs&&Date.now()<this.maintenanceDueAtMs?0:await this.startMaintenance()}async checkpoint(){return this.forceCheckpointRequested=!0,await this.startMaintenance()}async read(){return(await this.readState()).document}async readState(){const e=await this.readLegacy(),t=new Map;if(e){t.set("meta:lineage",{value:e.lineage,logicalRevision:e.lineage.journalGeneration,publisherEpoch:"0"});for(const n of e.records)t.set(`record:${n.operationId}`,{value:n,logicalRevision:String(n.revision),publisherEpoch:"0"});for(const n of e.cancellations)t.set(`cancellation:${n.eventId}`,{value:n,logicalRevision:"1",publisherEpoch:"0"})}const n=await this.segments.readState(t),o=n.values,r=o.get("meta:lineage")?.value;return{document:{version:2,lineage:r??emptyDocument().lineage,records:[...o.entries()].filter(([e])=>e.startsWith("record:")).map(([,e])=>validateRecordReceipts(migrateLegacyCommittedPhase(e.value))),cancellations:[...o.entries()].filter(([e])=>e.startsWith("cancellation:")).map(([,e])=>e.value)},authorityFloor:n.authorityFloor,checkpointAtMs:n.checkpointAtMs,deltaCount:n.deltaCount,deltaBytes:n.deltaBytes,oldestDeltaAtMs:n.oldestDeltaAtMs,hasDurableLineage:o.has("meta:lineage")&&n.hasDurableState}}async readLegacy(){try{const e=JSON.parse(await readFile(this.file,"utf8"));if(!Array.isArray(e.records))throw new Error("unsupported command journal");const t=e.records.map(e=>validateRecordReceipts(migrateLegacyCommittedPhase(e)));if(1===e.version)return{...emptyDocument(),records:t};if(2!==e.version||!e.lineage?.journalStoreId||!/^[1-9][0-9]*$/.test(e.lineage.journalGeneration))throw new Error("unsupported command journal");return{...e,records:t,cancellations:Array.isArray(e.cancellations)?e.cancellations:[]}}catch(e){if("ENOENT"===e.code)return null;throw e}}async update(e){const t=createRuntimeAppendTimingRecorder("journal.update",{file:this.file});let n=!1;this.foregroundUpdates+=1;try{return await withOwnedDirectoryLock(`${this.file}.lock`,async o=>{t.mark("lock");const r=await this.readState();t.mark("read");const i=r.document,a=structuredClone(i),s=e(i),c=[];if(r.hasDurableLineage&&sameValue(a.lineage,i.lineage)||c.push({key:"meta:lineage",logicalRevision:i.lineage.journalGeneration,value:i.lineage}),diffValues(a.records,i.records,e=>e.operationId,e=>String(e.revision),"record:",c),diffValues(a.cancellations,i.cancellations,e=>e.eventId,()=>"1","cancellation:",c),0===c.length)return t.mark("noop"),s;await(this.publication.beforePublish?.(o.epoch)),t.mark("before-publish"),await o.assertOwned(),t.mark("assert-owned");const d=await this.segments.publishDelta(o.epoch,c,r.authorityFloor);t.mark("publish-delta");const m=Date.now();return this.maintenanceDueAtMs=checkpointDueAt(r,1,d,m),n=this.maintenanceDueAtMs<=m,this.maintenanceDue=n,s},{...this.publication.lockOptions,epochFloor:()=>this.segments.authorityFloor()})}finally{this.foregroundUpdates-=1,this.foregroundRevision+=1,t.finish(),n&&this.scheduleMaintenance()}}scheduleMaintenance(){this.startMaintenance().catch(e=>{try{this.publication.onMaintenanceError?this.publication.onMaintenanceError(e):emitMaintenanceWarning(e)}catch(e){emitMaintenanceWarning(e)}})}startMaintenance(){if(this.maintenance)return this.maintenance;const e=(async()=>{let e=0,t=!1;do{let n=this.forceCheckpointRequested;this.forceCheckpointRequested=!1,n||(await this.waitForForegroundQuiet(),n=this.forceCheckpointRequested,this.forceCheckpointRequested=!1);const o=await this.runMaintenance(n);e+=o.removed,t=o.contended}while(this.forceCheckpointRequested||t);return e})().finally(()=>{this.maintenance===e&&(this.maintenance=void 0)});return this.maintenance=e,e}async waitForForegroundQuiet(){for(;;){if(this.forceCheckpointRequested)return;const e=this.foregroundRevision;if(0===this.foregroundUpdates){if(await new Promise(e=>setTimeout(e,50)),this.forceCheckpointRequested||0===this.foregroundUpdates&&this.foregroundRevision===e)return}else await new Promise(e=>setTimeout(e,50))}}async runMaintenance(e){const t=createRuntimeAppendTimingRecorder("journal.checkpoint",{file:this.file});try{if(!e&&this.prunePending)return await this.segments.prune(),this.prunePending=!1,t.mark("prune-pending"),{removed:0,contended:!1};let n=!1;const o=await withOwnedDirectoryLock(`${this.file}.lock`,async o=>{t.mark("lock");const r=await this.readState();if(t.mark("read"),!e&&!checkpointDue(r))return this.maintenanceDue=!1,this.maintenanceDueAtMs=checkpointDueAt(r),t.mark("not-due"),0;const i=r.document,a=this.now()-this.retentionMs,s=i.records.length;i.records=i.records.filter(e=>"prepared"===e.phase||"started"===e.phase||Date.parse(e.updatedAt)>=a);const c=i.cancellations.length;i.cancellations=i.cancellations.filter(e=>Date.parse(e.observedAt)>=a);const d=s-i.records.length+c-i.cancellations.length,m=documentValues(i);return await(this.publication.beforePublish?.(o.epoch)),t.mark("before-publish"),await o.assertOwned(),t.mark("assert-owned"),await this.segments.checkpointSnapshot(o.epoch,m,r.authorityFloor),t.mark("publish-snapshot"),this.maintenanceDue=!1,this.maintenanceDueAtMs=Number.POSITIVE_INFINITY,this.prunePending=!0,n=!0,d},{...this.publication.lockOptions,failIfContended:!e,epochFloor:()=>this.segments.authorityFloor()});return t.mark("lock-released"),n&&(await this.segments.prune(),this.prunePending=!1,t.mark("prune")),{removed:o,contended:!1}}catch(n){if(!e&&n instanceof OwnedDirectoryLockContendedError)return t.mark("deferred-busy"),{removed:0,contended:!0};throw n}finally{t.finish(e?"forced":"scheduled")}}}function checkpointDue(e,t=0,n=0,o=Date.now()){return checkpointDueAt(e,t,n,o)<=o}function checkpointDueAt(e,t=0,n=0,o=Date.now()){const r=e.deltaCount+t,i=e.deltaBytes+n,a=e.oldestDeltaAtMs||(t>0?o:0),s=e.checkpointAtMs||a;return r>=512||i>=8388608?o:r>0&&s>0?s+6e5:Number.POSITIVE_INFINITY}function emitMaintenanceWarning(e){process.emitWarning(e instanceof Error?e.message:String(e),{code:"BEEOS_RUNTIME_JOURNAL_CHECKPOINT_FAILED"})}function diffValues(e,t,n,o,r,i){const a=new Map(e.map(e=>[n(e),e])),s=new Map(t.map(e=>[n(e),e]));for(const[e,t]of s){const n=a.get(e);void 0!==n&&sameValue(n,t)||i.push({key:`${r}${e}`,logicalRevision:o(t),value:t})}for(const[e,t]of a)s.has(e)||i.push({key:`${r}${e}`,logicalRevision:o(t),tombstone:!0})}function documentValues(e){return[{key:"meta:lineage",logicalRevision:e.lineage.journalGeneration,value:e.lineage},...e.records.map(e=>({key:`record:${e.operationId}`,logicalRevision:String(e.revision),value:e})),...e.cancellations.map(e=>({key:`cancellation:${e.eventId}`,logicalRevision:"1",value:e}))]}function sameValue(e,t){return canonicalizeJcs(e)===canonicalizeJcs(t)}function migrateLegacyCommittedPhase(e){return{...e,phase:"committed"===e.phase?"effect_committed":e.phase}}export class InMemoryRuntimeCommandJournal{records=new Map;cancellations=new Map;async get(e){return this.records.get(e)??null}async getCancellation(e){return[...this.cancellations.values()].find(t=>t.operationId===e)??null}async listMcpLiveReleases(){return Object.freeze([...this.records.values()].flatMap(e=>e.mcpLiveRelease?[e.mcpLiveRelease]:[]))}async listMcpActiveDesiredStates(){return Object.freeze([...this.records.values()].flatMap(e=>e.mcpActiveDesiredState?[e.mcpActiveDesiredState]:[]))}async recordMcpDesiredStateCommit(e){const t=normalizeMcpDesiredStateCommit(e),n=this.records.get(t.operationId);if(!n)throw new RuntimeCommandJournalConflictError("MCP desired state has no command record");if(assertMcpCommitBindsRecord(t,n),"started"!==n.phase){if(sameMcpDesiredStateCommit(n.mcpDesiredStateCommit,t))return n;throw new RuntimeCommandJournalConflictError("MCP desired state commit crossed its journal boundary")}if(n.mcpDesiredStateCommit){if(sameMcpDesiredStateCommit(n.mcpDesiredStateCommit,t))return n;throw new RuntimeCommandJournalConflictError("MCP desired state commit conflicts with journal")}const o={...n,mcpDesiredStateCommit:t,revision:n.revision+1,updatedAt:(new Date).toISOString()};return this.records.set(t.operationId,o),o}async releaseMcpLiveEffect(e){const t=this.records.get(e.operationId);if(!t)throw new RuntimeCommandJournalConflictError("MCP release has no command record");if("effect_committed"!==t.phase&&"projection_committed"!==t.phase)throw new RuntimeCommandJournalConflictError("MCP release requires a committed effect");if(!t.mcpDesiredStateCommit)throw new RuntimeCommandJournalConflictError("MCP release lacks a durable desired-state commit");if("service"===e.source&&!t.messageBinding)throw new RuntimeCommandJournalConflictError("Service MCP release lacks a Message Service request binding");const n=releaseFromCommit(t.mcpDesiredStateCommit,e,(new Date).toISOString());if(t.mcpLiveRelease){if(sameMcpLiveRelease(t.mcpLiveRelease,n))return t;throw new RuntimeCommandJournalConflictError("MCP release conflicts with journal")}const o={...t,mcpLiveRelease:n,revision:t.revision+1,updatedAt:(new Date).toISOString()};return this.records.set(e.operationId,o),o}async promoteMcpDesiredStateActive(e){const t=this.records.get(e.operationId);if(!t)throw new RuntimeCommandJournalConflictError("MCP active promotion has no command record");const n=activeFromRelease(t.mcpLiveRelease,e,(new Date).toISOString());if(t.mcpActiveDesiredState){if(sameMcpActiveDesiredState(t.mcpActiveDesiredState,n))return t;throw new RuntimeCommandJournalConflictError("MCP active desired state conflicts with journal")}const o={...t,mcpActiveDesiredState:n,revision:t.revision+1,updatedAt:(new Date).toISOString()};return this.records.set(e.operationId,o),o}async acceptMessageRequest(e){let t=this.records.get(e.operationId);if(t||(t={operationId:e.operationId,command:e.command,paramsHash:e.paramsHash,runtimeEpoch:e.runtimeEpoch,phase:"prepared",revision:1,updatedAt:(new Date).toISOString()},this.records.set(e.operationId,t)),t.command!==e.command||t.paramsHash!==e.paramsHash||t.runtimeEpoch!==e.runtimeEpoch||t.messageBinding&&(t.messageBinding.requestMessageId!==e.requestMessageId||t.messageBinding.requestHash!==e.requestHash))throw new RuntimeCommandJournalConflictError("message request conflicts with command journal");return t.messageBinding??={requestMessageId:e.requestMessageId,requestHash:e.requestHash,acceptedAt:(new Date).toISOString()},t}async observeCancellation(e){const t=this.cancellations.get(e.eventId);if(t){if(t.operationId!==e.operationId||t.requestMessageId!==e.requestMessageId||t.requestHash!==e.requestHash)throw new RuntimeCommandJournalConflictError("operation cancellation observation conflict");return t}const n=[...this.cancellations.values()].find(t=>t.operationId===e.operationId);if(n&&(n.requestMessageId!==e.requestMessageId||n.requestHash!==e.requestHash))throw new RuntimeCommandJournalConflictError("operation cancellation request correlation conflict");const o={...e,observedAt:(new Date).toISOString()};return this.cancellations.set(e.eventId,o),o}async prepare(e){const t=this.records.get(e.operationId);if(t){if(t.command!==e.command||t.paramsHash!==e.paramsHash||t.runtimeEpoch!==e.runtimeEpoch)throw new RuntimeCommandJournalConflictError("operation identity conflict");return t}const n={...e,phase:"prepared",revision:1,updatedAt:(new Date).toISOString()};return this.records.set(e.operationId,n),n}async transition(e){const t=this.records.get(e.operationId);if(!t||t.revision!==e.expectedRevision)throw new RuntimeCommandJournalConflictError("revision conflict");const n="effect_committed"===t.phase&&"effect_committed"===e.phase&&void 0!==e.agentProjection&&void 0===t.agentProjectionIntent&&"pending"===t.agentProjectionPreparationFailure?.state;if("projection_committed"===t.phase||"failed"===t.phase||"outcome_unknown"===t.phase||"effect_committed"===t.phase&&"projection_committed"!==e.phase&&!n)throw new RuntimeCommandJournalConflictError("terminal journal record is immutable");if(void 0!==e.projectionSnapshot&&"projection_committed"!==e.phase)throw new RuntimeCommandJournalConflictError("projection receipt requires projection_committed phase");if(void 0!==e.agentProjection&&"effect_committed"!==e.phase)throw new RuntimeCommandJournalConflictError("agent projection intent requires effect_committed phase");const o=void 0===e.agentProjection?void 0:normalizeAgentProjectionPreparation(e.agentProjection,t);if(n&&o&&!sameProjectionPreparationBase(t.agentProjectionPreparationFailure,o))throw new RuntimeCommandJournalConflictError("agent projection preparation conflicts with committed operation");const{agentProjectionIntent:r,agentProjectionPreparationFailure:i,...a}=t,s={...void 0===o?t:a,phase:e.phase,revision:t.revision+1,updatedAt:(new Date).toISOString(),...void 0===e.result?{}:persistedResult(t.command,e.result),...void 0===e.projectionSnapshot?{}:{projectionReceipt:projectionReceipt(e.projectionSnapshot)},...void 0===o?{}:isAgentProjectionIntent(o)?{agentProjectionIntent:o}:{agentProjectionPreparationFailure:o},...void 0===e.errorCode?{}:{errorCode:e.errorCode}};return this.records.set(e.operationId,s),s}async recordAgentProjectionIntent(e){const t=this.records.get(e.operationId);if(!t||t.revision!==e.expectedRevision||"effect_committed"!==t.phase)throw new RuntimeCommandJournalConflictError("projection intent cannot mutate this command journal record");const n=normalizeAgentProjectionIntent(e.intent,t);if(t.agentProjectionIntent){if(canonicalizeJcs(t.agentProjectionIntent)!==canonicalizeJcs(n))throw new RuntimeCommandJournalConflictError("agent projection intent conflicts with committed operation");return t}if("pending"!==t.agentProjectionPreparationFailure?.state||!sameProjectionPreparationBase(t.agentProjectionPreparationFailure,n))throw new RuntimeCommandJournalConflictError("projection intent requires matching retryable preparation failure");const{agentProjectionPreparationFailure:o,...r}=t,i={...r,agentProjectionIntent:n,revision:t.revision+1,updatedAt:(new Date).toISOString()};return this.records.set(e.operationId,i),i}async recordAgentProjectionBlocked(e){const t=this.records.get(e.operationId);if(!t||t.revision!==e.expectedRevision||"effect_committed"!==t.phase||!t.agentProjectionIntent||t.agentProjectionIntent.projectionRevision!==e.revision)throw new RuntimeCommandJournalConflictError("blocked projection cannot mutate this command journal record");if(t.agentProjectionTerminal){if("blocked"===t.agentProjectionTerminal.state&&t.agentProjectionTerminal.revision===e.revision)return t;throw new RuntimeCommandJournalConflictError("agent projection terminal conflicts with committed operation")}const n={...t,agentProjectionTerminal:agentProjectionTerminalReceipt(e.revision),revision:t.revision+1,updatedAt:(new Date).toISOString()};return this.records.set(e.operationId,n),n}async compact(){return 0}async checkpoint(){return 0}}function persistedResult(e,t){return"canonical_result"===runtimeCommandResultPersistencePolicy(e)?{result:t}:{resultReceipt:{policy:"hash_only",resultHash:runtimeCommandResultHash(t),redacted:{kind:runtimeCommandIsReplayableQuery(e)?"query_result":"mutation_result",command:e}}}}function projectionReceipt(e){if("synced"!==e.state||!/^(0|[1-9][0-9]*)$/.test(e.revision))throw new Error("invalid projection revision");if(void 0!==e.platformAgentId&&!/^[a-zA-Z0-9._:-]{1,256}$/.test(e.platformAgentId))throw new Error("invalid projection platform agent id");const t=void 0===e.mcpReceipt?void 0:normalizeMcpProjectionReceipt(e.mcpReceipt),n=Object.freeze({revision:e.revision,state:"synced",..."string"==typeof e.platformAgentId&&e.platformAgentId?{platformAgentId:e.platformAgentId}:{},...t?{mcpReceipt:t}:{}});return Object.freeze({schemaVersion:1,revision:n.revision,snapshot:n,snapshotHash:runtimeCommandResultHash(n)})}function validateRecordReceipts(e){if(void 0!==e.agentProjectionIntent&&void 0!==e.agentProjectionPreparationFailure)throw new Error("agent projection journal record is ambiguous");const t=void 0!==e.agentProjectionIntent?{...e,agentProjectionIntent:normalizeAgentProjectionIntent(e.agentProjectionIntent,e)}:void 0!==e.agentProjectionPreparationFailure?{...e,agentProjectionPreparationFailure:normalizeAgentProjectionPreparation(e.agentProjectionPreparationFailure,e)}:e,n=void 0===t.agentProjectionTerminal?t:{...t,agentProjectionTerminal:normalizeAgentProjectionTerminal(t.agentProjectionTerminal)};if(n.agentProjectionTerminal&&(!n.agentProjectionIntent||"effect_committed"!==n.phase||n.agentProjectionIntent.projectionRevision!==n.agentProjectionTerminal.revision))throw new Error("invalid agent projection terminal receipt");const o=void 0===n.mcpDesiredStateCommit?void 0:normalizeMcpDesiredStateCommit(n.mcpDesiredStateCommit);o&&assertMcpCommitBindsRecord(o,n);const r=void 0===n.mcpLiveRelease?void 0:normalizeMcpLiveRelease(n.mcpLiveRelease);if(r&&(!o||!sameMcpDesiredStateCommit(o,r)||"effect_committed"!==n.phase&&"projection_committed"!==n.phase||"service"===r.source&&!n.messageBinding))throw new Error("invalid MCP live release receipt");const i=void 0===n.mcpActiveDesiredState?void 0:normalizeMcpActiveDesiredState(n.mcpActiveDesiredState);if(i&&(!r||!sameMcpDesiredStateCommit(i,r)||i.effectRecordHash!==r.effectRecordHash||i.releasedAt!==r.releasedAt))throw new Error("invalid MCP active desired-state receipt");const a={...n,...o?{mcpDesiredStateCommit:o}:{},...r?{mcpLiveRelease:r}:{},...i?{mcpActiveDesiredState:i}:{}};if(!a.projectionReceipt)return a;const s=a.projectionReceipt,c=["revision","state",...void 0===s.snapshot?.platformAgentId?[]:["platformAgentId"],...void 0===s.snapshot?.mcpReceipt?[]:["mcpReceipt"]].sort().join(",");if("revision,schemaVersion,snapshot,snapshotHash"!==Object.keys(s).sort().join(",")||Object.keys(s.snapshot??{}).sort().join(",")!==c||"projection_committed"!==a.phase||1!==s.schemaVersion||!/^(0|[1-9][0-9]*)$/.test(s.revision)||"synced"!==s.snapshot?.state||s.snapshot.revision!==s.revision)throw new Error("invalid projection receipt");const d=projectionReceipt(s.snapshot),m=d.snapshot.mcpReceipt;if(m&&(m.operationId!==a.operationId||m.method!==a.command||m.runtimeEpoch!==a.runtimeEpoch))throw new Error("MCP projection receipt does not bind journal operation");if(d.snapshotHash!==s.snapshotHash)throw new Error("invalid projection receipt hash");return{...a,projectionReceipt:d}}function normalizeMcpDesiredStateCommit(e){if(!e||"object"!=typeof e||"desiredStateHash,desiredStateRevision,method,operationId,paramsHash,runtimeEpoch,schemaVersion,server,source"!==Object.keys(e).sort().join(",")||1!==e.schemaVersion||"service"!==e.source&&"acp_local"!==e.source||!safeReceiptIdentifier(e.operationId)||"mcp.set"!==e.method&&"mcp.unset"!==e.method||!isSha256(e.paramsHash)||!/^(0|[1-9][0-9]*)$/.test(e.runtimeEpoch)||!/^(0|[1-9][0-9]*)$/.test(e.desiredStateRevision)||!isSha256(e.desiredStateHash))throw new Error("invalid MCP desired-state commit");return Object.freeze({schemaVersion:1,source:e.source,operationId:e.operationId,method:e.method,paramsHash:e.paramsHash,runtimeEpoch:e.runtimeEpoch,desiredStateRevision:e.desiredStateRevision,desiredStateHash:e.desiredStateHash,server:normalizeMcpReleaseServer(e.server)})}function normalizeMcpLiveRelease(e){if(!e||"object"!=typeof e)throw new Error("invalid MCP live release receipt");const t=["desiredStateHash","desiredStateRevision","method","operationId","paramsHash","releasedAt","runtimeEpoch","schemaVersion","server","source",..."service"===e.source?["effectRecordHash"]:[]].sort().join(",");if(Object.keys(e).sort().join(",")!==t||"service"===e.source&&!isSha256(e.effectRecordHash)||"acp_local"===e.source&&void 0!==e.effectRecordHash||"string"!=typeof e.releasedAt||!Number.isFinite(Date.parse(e.releasedAt)))throw new Error("invalid MCP live release receipt");const n=normalizeMcpDesiredStateCommit({schemaVersion:e.schemaVersion,source:e.source,operationId:e.operationId,method:e.method,paramsHash:e.paramsHash,runtimeEpoch:e.runtimeEpoch,desiredStateRevision:e.desiredStateRevision,desiredStateHash:e.desiredStateHash,server:e.server});return Object.freeze({...n,...e.effectRecordHash?{effectRecordHash:e.effectRecordHash}:{},releasedAt:e.releasedAt})}function normalizeMcpActiveDesiredState(e){if(!e||"object"!=typeof e)throw new Error("invalid MCP active desired-state receipt");const t=["activatedAt","desiredStateHash","desiredStateRevision","method","operationId","paramsHash","releasedAt","runtimeEpoch","schemaVersion","server","source",..."service"===e.source?["effectRecordHash","terminalMessageId"]:[]].sort().join(",");if(Object.keys(e).sort().join(",")!==t||"string"!=typeof e.activatedAt||!Number.isFinite(Date.parse(e.activatedAt))||"service"===e.source&&!safeReceiptIdentifier(e.terminalMessageId))throw new Error("invalid MCP active desired-state receipt");const n=normalizeMcpLiveRelease({schemaVersion:e.schemaVersion,source:e.source,operationId:e.operationId,method:e.method,paramsHash:e.paramsHash,runtimeEpoch:e.runtimeEpoch,desiredStateRevision:e.desiredStateRevision,desiredStateHash:e.desiredStateHash,server:e.server,releasedAt:e.releasedAt,...e.effectRecordHash?{effectRecordHash:e.effectRecordHash}:{}});return Object.freeze({...n,...e.terminalMessageId?{terminalMessageId:e.terminalMessageId}:{},activatedAt:e.activatedAt})}function normalizeMcpReleaseServer(e){const t=e&&"object"==typeof e?Object.keys(e).sort().join(","):"",n=!0===e?.removed;if(!(t===(n?"name,removed":void 0===e?.credentialRef?"definitionRef,definitionSha256,name,version":"credentialRef,definitionRef,definitionSha256,name,version")&&safeReceiptIdentifier(e?.name)&&(n||safeMcpReference(e?.definitionRef)&&safeMcpReference(e?.version)&&isSha256(e?.definitionSha256)&&(void 0===e?.credentialRef||safeMcpReference(e.credentialRef)))))throw new Error("invalid MCP desired-state server identity");return Object.freeze(n?{name:e.name,removed:!0}:{name:e.name,definitionRef:e.definitionRef,version:e.version,definitionSha256:e.definitionSha256,...e.credentialRef?{credentialRef:e.credentialRef}:{}})}function assertMcpCommitBindsRecord(e,t){if(e.operationId!==t.operationId||e.method!==t.command||e.paramsHash!==t.paramsHash||e.runtimeEpoch!==t.runtimeEpoch)throw new RuntimeCommandJournalConflictError("MCP desired-state commit does not bind command journal")}function releaseFromCommit(e,t,n){if(t.operationId!==e.operationId||t.source!==e.source||"service"===t.source&&!isSha256(t.effectRecordHash)||"acp_local"===t.source&&void 0!==t.effectRecordHash)throw new RuntimeCommandJournalConflictError("MCP live release proof does not bind desired state");return Object.freeze({...e,...t.effectRecordHash?{effectRecordHash:t.effectRecordHash}:{},releasedAt:n})}function activeFromRelease(e,t,n){if(!(e&&t.operationId===e.operationId&&t.source===e.source&&Number.isFinite(Date.parse(n))&&("service"!==t.source||isSha256(t.effectRecordHash)&&t.effectRecordHash===e.effectRecordHash&&safeReceiptIdentifier(t.terminalMessageId))&&("acp_local"!==t.source||void 0===t.effectRecordHash&&void 0===t.terminalMessageId)))throw new RuntimeCommandJournalConflictError("MCP active promotion does not bind terminal release proof");return Object.freeze({...e,...t.terminalMessageId?{terminalMessageId:t.terminalMessageId}:{},activatedAt:n})}function sameMcpDesiredStateCommit(e,t){if(!e||!t)return!1;const n=e=>({schemaVersion:e.schemaVersion,source:e.source,operationId:e.operationId,method:e.method,paramsHash:e.paramsHash,runtimeEpoch:e.runtimeEpoch,desiredStateRevision:e.desiredStateRevision,desiredStateHash:e.desiredStateHash,server:e.server});return canonicalizeJcs(n(e))===canonicalizeJcs(n(t))}function sameMcpLiveRelease(e,t){const n=e=>({...e,releasedAt:""});return canonicalizeJcs(n(e))===canonicalizeJcs(n(t))}function sameMcpActiveDesiredState(e,t){const n=e=>({...e,releasedAt:"",activatedAt:""});return canonicalizeJcs(n(e))===canonicalizeJcs(n(t))}function normalizeMcpProjectionReceipt(e){if(!e||"object"!=typeof e||"desiredStateHash,desiredStateRevision,method,operationId,runtimeEpoch,schemaVersion,server"!==Object.keys(e).sort().join(",")||1!==e.schemaVersion||!safeReceiptIdentifier(e.operationId)||"mcp.set"!==e.method&&"mcp.unset"!==e.method||!/^(0|[1-9][0-9]*)$/.test(e.runtimeEpoch)||!/^(0|[1-9][0-9]*)$/.test(e.desiredStateRevision)||!isSha256(e.desiredStateHash))throw new Error("invalid MCP projection receipt");const t=e.server;if((t&&"object"==typeof t?Object.keys(t).sort().join(","):"")!==(!0===t?.removed?"name,removed":void 0===t?.definitionRef?"name":"definitionRef,definitionSha256,name")||!safeReceiptIdentifier(t?.name)||void 0!==t?.definitionRef&&(!safeReceiptIdentifier(t.definitionRef)||!isSha256(t.definitionSha256)))throw new Error("invalid MCP projection server receipt");return Object.freeze({schemaVersion:1,operationId:e.operationId,method:e.method,runtimeEpoch:e.runtimeEpoch,desiredStateRevision:e.desiredStateRevision,desiredStateHash:e.desiredStateHash,server:Object.freeze(!0===t.removed?{name:t.name,removed:!0}:void 0===t.definitionRef?{name:t.name}:{name:t.name,definitionRef:t.definitionRef,definitionSha256:t.definitionSha256})})}function isSha256(e){return"string"==typeof e&&/^[a-f0-9]{64}$/.test(e)}function safeReceiptIdentifier(e){return"string"==typeof e&&/^[a-zA-Z0-9._:@/-]{1,512}$/.test(e)&&!/(authorization|bearer|credential|secret|token|password|private(?:_|-)?key|headers?|env)/i.test(e)}function safeMcpReference(e){return"string"==typeof e&&/^[a-zA-Z0-9._:@/-]{1,512}$/.test(e)&&!/(authorization|bearer|secret|token|password|private(?:_|-)?key|headers?|env)/i.test(e)}function agentProjectionTerminalReceipt(e){if(!/^(0|[1-9][0-9]*)$/.test(e))throw new Error("invalid agent projection terminal revision");return Object.freeze({schemaVersion:1,state:"blocked",revision:e})}function normalizeAgentProjectionTerminal(e){if("revision,schemaVersion,state"!==Object.keys(e).sort().join(",")||1!==e.schemaVersion||"blocked"!==e.state)throw new Error("invalid agent projection terminal receipt");return agentProjectionTerminalReceipt(e.revision)}function normalizeAgentProjectionIntent(e,t){const n=Object.keys(e).sort(),o=["canonicalHash","coreResultHash","method","operationId","projectionRevision","runtimeAgentUid","runtimeEpoch","runtimeLocalKey","schemaVersion","snapshotRef",...void 0===e.expectedPlatformAgentId?[]:["expectedPlatformAgentId"],...void 0===e.removedBindings?[]:["removedBindings"]].sort();if(n.length!==o.length||n.some((e,t)=>e!==o[t])||1!==e.schemaVersion||!["agent.create","agent.update","agent.delete","agent.applyTemplate"].includes(e.method)||!nonEmptyText(e.operationId)||!nonEmptyText(e.runtimeAgentUid)||!nonEmptyText(e.runtimeLocalKey)||!/^(0|[1-9][0-9]*)$/.test(e.runtimeEpoch)||e.operationId!==t.operationId||e.method!==t.command||e.runtimeEpoch!==t.runtimeEpoch||void 0!==e.expectedPlatformAgentId&&!nonEmptyText(e.expectedPlatformAgentId)||!/^[0-9a-f]{64}$/.test(e.coreResultHash)||!/^(0|[1-9][0-9]*)$/.test(e.projectionRevision)||!/^[0-9a-f]{64}$/.test(e.canonicalHash)||!nonEmptyText(e.snapshotRef)||e.snapshotRef!==projectionSnapshotRef(e.runtimeEpoch,e.projectionRevision,e.canonicalHash)||void 0!==e.removedBindings&&(!Number.isSafeInteger(e.removedBindings)||e.removedBindings<0))throw new Error("invalid agent projection intent");return Object.freeze({...e})}function normalizeAgentProjectionPreparation(e,t){if(isAgentProjectionIntent(e))return normalizeAgentProjectionIntent(e,t);const n=e,o=Object.keys(n).sort(),r=["coreResultHash","errorKind","method","operationId","runtimeAgentUid","runtimeEpoch","runtimeLocalKey","schemaVersion","state",...void 0===n.expectedPlatformAgentId?[]:["expectedPlatformAgentId"],...void 0===n.removedBindings?[]:["removedBindings"]].sort();if(o.length!==r.length||o.some((e,t)=>e!==r[t])||1!==n.schemaVersion||!["agent.create","agent.update","agent.delete","agent.applyTemplate"].includes(n.method)||n.operationId!==t.operationId||n.method!==t.command||n.runtimeEpoch!==t.runtimeEpoch||!nonEmptyText(n.runtimeAgentUid)||!nonEmptyText(n.runtimeLocalKey)||!/^(0|[1-9][0-9]*)$/.test(n.runtimeEpoch)||!/^[0-9a-f]{64}$/.test(n.coreResultHash)||"pending"!==n.state&&"blocked"!==n.state||"snapshot_retryable"!==n.errorKind&&"snapshot_nonretryable"!==n.errorKind||"pending"===n.state!=("snapshot_retryable"===n.errorKind)||void 0!==n.expectedPlatformAgentId&&!nonEmptyText(n.expectedPlatformAgentId)||void 0!==n.removedBindings&&(!Number.isSafeInteger(n.removedBindings)||n.removedBindings<0))throw new Error("invalid agent projection preparation failure");return Object.freeze({...n})}function sameProjectionPreparationBase(e,t){return e.operationId===t.operationId&&e.method===t.method&&e.runtimeEpoch===t.runtimeEpoch&&e.runtimeAgentUid===t.runtimeAgentUid&&e.runtimeLocalKey===t.runtimeLocalKey&&e.coreResultHash===t.coreResultHash&&e.expectedPlatformAgentId===t.expectedPlatformAgentId&&e.removedBindings===t.removedBindings}function isAgentProjectionIntent(e){return"projectionRevision"in e}function projectionSnapshotRef(e,t,n){return`runtime-agent-projection:v1:${e}:${t}:${n}`}function nonEmptyText(e){return"string"==typeof e&&e.length>0}
@@ -1 +1 @@
1
- import{createHash,randomUUID}from"node:crypto";import{join}from"node:path";import{CONVERSATION_RUNTIME_CANONICAL_COMMAND_NAMES,RUNTIME_METHOD_PARITY_MANIFEST,RUNTIME_RESOURCE_BINDING_CONTRACT_REVISION,RUNTIME_RPC_CONTRACT_REVISION}from"@beeos-ai/beeos-types/runtime";import{FileRuntimeCommandJournal}from"./runtime-command-journal.js";import{FileRuntimeAgentIdentityRegistry}from"./runtime-agent-identity-registry.js";import{RuntimeRegistrationIdentityStore}from"./runtime-registration-identity-store.js";import{InstanceAuthorityRuntimeRegistrationTransport,RuntimeRegistrationCoordinator,RuntimeRegistrationJournalAdvanceRequiredError,signAgentMessage}from"@beeos-ai/cloud-harness-sdk";import{verifiedRuntimeParityManifestDigest}from"./runtime-parity-manifest-digest.js";import{intersectRuntimeServiceMethodsWithBackendCapabilities}from"./runtime-service-contract-compatibility.js";export function cloudRuntimeTargetInstanceId(t){if(void 0!==t){if(!/^[A-Za-z0-9_-]{1,128}$/.test(t))throw new Error("Cloud runtime target must match the provisioned BeeOS instance identity");return t}}export class RuntimeProductionAuthority{options;journal;identities;identityStore;handlerIdentity;listeners=new Set;coordinator;unsubscribeCoordinator;starting;rolling=new Map;refreshing;stoppedCoordinators=new WeakSet;coordinatorGeneration=0;lineage;identity;preparing;runtimeConsumerLive=!1;runtimeServiceMethods=Object.freeze([]);realtimeCapabilityReady=new Map;resourceBindingContractRevision;manifestDigest=verifiedRuntimeParityManifestDigest();started=!1;lifecycleVersion=0;authorityLeasePending=!1;constructor(t){if(this.options=t,void 0!==t.targetInstanceId&&!/^[A-Za-z0-9_-]{1,128}$/.test(t.targetInstanceId))throw new Error("Cloud runtime target instance ID is invalid");this.handlerIdentity=t.handlerIdentity??(process.env.HOSTNAME?.trim()||process.env.POD_NAME?.trim()||randomUUID()),this.journal=new FileRuntimeCommandJournal(join(t.stateDirectory,"runtime-command-journal.json"),void 0,void 0,{onMaintenanceError:e=>{const i=`[runtime-journal] event=checkpoint_failed trigger=threshold error=${e instanceof Error?e.message:String(e)}`;t.logger?.error?t.logger.error(i):t.logger?.warn?.(i)}}),this.identities=new FileRuntimeAgentIdentityRegistry(join(t.stateDirectory,"runtime-agent-identities.json")),this.identityStore=new RuntimeRegistrationIdentityStore(join(t.stateDirectory,"runtime-registration-identity.json"))}get agentKeys(){return this.options.keys}get stateDirectory(){return this.options.stateDirectory}get cloudTargetInstanceId(){return this.options.targetInstanceId}bindCloudTarget(t){if(void 0!==this.options.targetInstanceId){if(this.options.targetInstanceId!==t)throw new Error("Cloud runtime authority target changed during process lifetime")}else this.options.targetInstanceId=t}get authorityLease(){return this.coordinator?.authorityLease??null}subscribeAuthority(t){return this.listeners.add(t),()=>{this.listeners.delete(t)}}currentConsumerBinding(){return this.identity&&this.lineage?this.consumerBinding(this.identity,this.lineage):null}async prepareConsumerBinding(){return this.preparing||(this.preparing=(async()=>{const t=await this.identityStore.acquire(this.handlerIdentity,this.journal),e={journalStoreId:t.journalStoreId,journalGeneration:t.journalGeneration};return this.lineage=e,this.identity=t,this.consumerBinding(t,e)})().finally(()=>{this.preparing=void 0})),await this.preparing}setRuntimeConsumerLive(t){if(this.started)throw new Error("runtime consumer availability is immutable while registered");this.runtimeConsumerLive=t}setRuntimeServiceMethods(t){if(this.started)throw new Error("runtime Service methods are immutable while registered");this.runtimeServiceMethods=intersectRuntimeServiceMethodsWithBackendCapabilities(t)}setRuntimeRealtimeCapabilityReady(t,e){const i=this.realtimeCapabilityReady.get(t)!==e;if(this.realtimeCapabilityReady.set(t,e),!i||!this.started)return;const r=this.lifecycleVersion;this.refreshRealtimeCapabilities(r).catch(t=>this.options.logger?.warn?.(`[runtime-authority] authority lease refresh fenced (realtime capability): ${t instanceof Error?t.message:t}`))}setRuntimeResourceBindingContractRevision(t){if(this.started)throw new Error("runtime resource binding revision is immutable while registered");if(void 0!==t&&t!==RUNTIME_RESOURCE_BINDING_CONTRACT_REVISION)throw new Error("unsupported runtime resource binding revision");this.resourceBindingContractRevision=t}async start(t){if(this.starting)return await this.starting;if(this.started)return;if(t?.aborted)return;const e=this.lifecycleVersion,i=this.startOnce(e,t).finally(()=>{this.starting===i&&(this.starting=void 0)});this.starting=i,await i}stop(){this.lifecycleVersion++,this.starting=void 0,this.started=!1;const t=this.coordinator;t&&this.detachCoordinator(t),this.publish(null)}async startOnce(t,e){if(await this.prepareConsumerBinding(),t!==this.lifecycleVersion||e?.aborted)return;this.lineage;const i=this.identity;this.started=!0,await this.activate(i,t)}async activate(t,e){if(!this.isLifecycleCurrent(e))return;if(this.resourceBindingContractRevision&&!this.runtimeConsumerLive)throw new Error("resource binding revision requires a live Message Service consumer");if(!this.runtimeConsumerLive||0===this.runtimeServiceMethods.length)throw new Error(`runtime registration refused: consumerLive=${this.runtimeConsumerLive} methods=${this.runtimeServiceMethods.length}`);const i=[...this.runtimeServiceMethods],r=runtimeCapabilities(this.runtimeServiceMethods,this.realtimeCapabilityReady);this.options.logger?.info?.(`[runtime-authority] advertising ${i.length} methods (renew-safe)`);const n=++this.coordinatorGeneration,o=new RuntimeRegistrationCoordinator(new InstanceAuthorityRuntimeRegistrationTransport({gateway:this.options.gateway,registrationId:t.registrationId}),{registrationId:t.registrationId,handlerIdentity:t.handlerIdentity,contractRevision:RUNTIME_RPC_CONTRACT_REVISION,runtimeMethods:i,capabilities:r,manifestDigest:this.manifestDigest,journalStoreId:t.journalStoreId,journalGeneration:t.journalGeneration,instanceIdentityKeyId:createHash("sha256").update(this.options.keys.publicKey).digest("hex"),targetInstanceId:this.options.targetInstanceId,signIdentityProof:t=>signAgentMessage(t,this.options.keys.privateKey),getMSConnectionStatus:this.options.getMSConnectionStatus},(t,i)=>{if(!this.isCoordinatorCurrent(e,n,o))return;const r=i instanceof Error?i.message:void 0!==i?String(i):"";this.options.logger?.warn?.(r?`[runtime-authority] lease lost: ${t}: ${r}`:`[runtime-authority] lease lost: ${t}`),"fenced"===t&&this.publish(null,"fenced");const s=i instanceof RuntimeRegistrationJournalAdvanceRequiredError?i:void 0;("expired"===t||s)&&this.rollExpired(e,n,o,s).catch(t=>this.options.logger?.warn?.(`[runtime-authority] authority lease roll fenced (expired registration): ${t instanceof Error?t.message:t}`))});this.coordinator=o,this.unsubscribeCoordinator=o.subscribeAuthority(t=>{this.isCoordinatorCurrent(e,n,o)&&(t&&this.authorityLeasePending?(this.authorityLeasePending=!1,this.options.logger?.info?.("[runtime-authority] registration active (recovered after handoff)")):t||(this.authorityLeasePending=!0),this.publish(t))});const s=await o.start();this.isCoordinatorCurrent(e,n,o)?(this.options.logger?.info?.("[runtime-authority] registration "+(s?"active":"pending")),s||(this.authorityLeasePending=!0)):this.stopCoordinatorOnce(o)}async rollExpired(t,e,i,r){if(!this.lineage||!this.isCoordinatorCurrent(t,e,i))return;const n=this.rolling.get(e);if(n)return await n;let o=!1,s=!1;const a=(async()=>{if(!this.isCoordinatorCurrent(t,e,i))return;this.detachCoordinator(i),o=!0;const n=this.identity;if(!n)return;let a,h=0;for(;;){if(!this.isLifecycleCurrent(t)||void 0!==this.coordinator)return;try{a=await this.identityStore.roll(n,this.journal,{authorityCurrentGeneration:r?.currentJournalGeneration,expectedJournalGeneration:r?.expectedJournalGeneration});break}catch(t){if(!isRetryableRegistrationRollError(t))throw t;s||(this.publishDetachedWithoutLease("expired registration roll retry"),s=!0),this.options.logger?.warn?.(`[runtime-authority] retrying durable registration roll: ${t instanceof Error?t.message:t}`),await new Promise(t=>setTimeout(t,Math.min(250*2**Math.min(h++,4),4e3)))}}this.isLifecycleCurrent(t)&&void 0===this.coordinator&&(this.identity=a,this.lineage={journalStoreId:a.journalStoreId,journalGeneration:a.journalGeneration},await this.activate(a,t))})().finally(()=>{this.rolling.get(e)===a&&this.rolling.delete(e),o&&!s&&this.publishDetachedWithoutLease("expired registration roll")});this.rolling.set(e,a),await a}async refreshRealtimeCapabilities(t){if(!this.isLifecycleCurrent(t))return;const e=this.refreshing;if(e)return await e;let i=!1;const r=(async()=>{if(!this.isLifecycleCurrent(t))return;const e=this.identity,r=this.coordinator;e&&r&&(r.authorityLease?(this.detachCoordinator(r),i=!0,this.isLifecycleCurrent(t)&&void 0===this.coordinator&&await this.activate(e,t)):this.options.logger?.info?.("[runtime-authority] deferring realtime capability refresh until registration is active"))})().finally(()=>{this.refreshing===r&&(this.refreshing=void 0),i&&this.publishDetachedWithoutLease("realtime capability refresh")});this.refreshing=r,await r}isLifecycleCurrent(t){return this.started&&t===this.lifecycleVersion}isCoordinatorCurrent(t,e,i){return this.isLifecycleCurrent(t)&&this.coordinatorGeneration===e&&this.coordinator===i}detachCoordinator(t){this.coordinator===t&&(this.unsubscribeCoordinator?.(),this.unsubscribeCoordinator=void 0,this.coordinator=void 0),this.stopCoordinatorOnce(t)}publishDetachedWithoutLease(t){null===this.authorityLease&&(this.authorityLeasePending=!0,this.options.logger?.warn?.(`[runtime-authority] authority lease cleared with no live successor lease (${t})`),this.publish(null))}stopCoordinatorOnce(t){this.stoppedCoordinators.has(t)||(this.stoppedCoordinators.add(t),t.stop())}publish(t,e){for(const i of this.listeners)try{i(t,e)}catch{}}consumerBinding(t,e){return Object.freeze({handlerIdentity:t.handlerIdentity,journalStoreId:e.journalStoreId,journalGeneration:e.journalGeneration})}}const REALTIME_CAPABILITY_NAMES=["terminal","canvas"];function isRetryableRegistrationRollError(t){return t instanceof Error&&(t.message.includes("owned directory lock lease expired")||t.message.includes("owned directory lock was fenced")||t.message.includes("timed out acquiring lock")||t.message.includes("owned directory lock is busy"))}function runtimeCapabilities(t,e){const i=new Set(t),r=RUNTIME_METHOD_PARITY_MANIFEST.filter(t=>t.serviceAllowed&&void 0!==runtimeInvocationMethod(t)&&i.has(runtimeInvocationMethod(t))).map(t=>t.capability),n=REALTIME_CAPABILITY_NAMES.filter(t=>!0===e.get(t));return[...new Set([...r,...n])]}function runtimeInvocationMethod(t){return"methods"===t.serviceRoute&&t.serviceMethod?t.serviceMethod:Object.entries(CONVERSATION_RUNTIME_CANONICAL_COMMAND_NAMES).find(([,e])=>e===t.canonicalCommand)?.[0]}
1
+ import{createHash,randomUUID}from"node:crypto";import{join}from"node:path";import{CONVERSATION_RUNTIME_CANONICAL_COMMAND_NAMES,RUNTIME_METHOD_PARITY_MANIFEST,RUNTIME_RESOURCE_BINDING_CONTRACT_REVISION,RUNTIME_RPC_CONTRACT_REVISION}from"@beeos-ai/beeos-types/runtime";import{FileRuntimeCommandJournal}from"./runtime-command-journal.js";import{FileRuntimeAgentIdentityRegistry}from"./runtime-agent-identity-registry.js";import{RuntimeRegistrationIdentityStore}from"./runtime-registration-identity-store.js";import{InstanceAuthorityRuntimeRegistrationTransport,RuntimeRegistrationCoordinator,RuntimeRegistrationJournalAdvanceRequiredError,signAgentMessage}from"@beeos-ai/cloud-harness-sdk";import{verifiedRuntimeParityManifestDigest}from"./runtime-parity-manifest-digest.js";import{intersectRuntimeServiceMethodsWithBackendCapabilities}from"./runtime-service-contract-compatibility.js";export function cloudRuntimeTargetInstanceId(t){if(void 0!==t){if(!/^[A-Za-z0-9_-]{1,128}$/.test(t))throw new Error("Cloud runtime target must match the provisioned BeeOS instance identity");return t}}export class RuntimeProductionAuthority{options;journal;identities;identityStore;handlerIdentity;listeners=new Set;coordinator;unsubscribeCoordinator;starting;rolling=new Map;refreshing;stoppedCoordinators=new WeakSet;coordinatorGeneration=0;lineage;identity;preparing;runtimeConsumerLive=!1;runtimeServiceMethods=Object.freeze([]);realtimeCapabilityReady=new Map;resourceBindingContractRevision;manifestDigest=verifiedRuntimeParityManifestDigest();started=!1;lifecycleVersion=0;authorityLeasePending=!1;laneSnapshot=null;laneTimer;constructor(t){if(this.options=t,void 0!==t.targetInstanceId&&!/^[A-Za-z0-9_-]{1,128}$/.test(t.targetInstanceId))throw new Error("Cloud runtime target instance ID is invalid");this.handlerIdentity=t.handlerIdentity??(process.env.HOSTNAME?.trim()||process.env.POD_NAME?.trim()||randomUUID()),this.journal=new FileRuntimeCommandJournal(join(t.stateDirectory,"runtime-command-journal.json"),void 0,void 0,{onMaintenanceError:e=>{const i=`[runtime-journal] event=checkpoint_failed trigger=threshold error=${e instanceof Error?e.message:String(e)}`;t.logger?.error?t.logger.error(i):t.logger?.warn?.(i)}}),this.identities=new FileRuntimeAgentIdentityRegistry(join(t.stateDirectory,"runtime-agent-identities.json")),this.identityStore=new RuntimeRegistrationIdentityStore(join(t.stateDirectory,"runtime-registration-identity.json"))}get agentKeys(){return this.options.keys}get stateDirectory(){return this.options.stateDirectory}get cloudTargetInstanceId(){return this.options.targetInstanceId}bindCloudTarget(t){if(void 0!==this.options.targetInstanceId){if(this.options.targetInstanceId!==t)throw new Error("Cloud runtime authority target changed during process lifetime")}else this.options.targetInstanceId=t}get authorityLease(){if(this.options.identityLane){const t=this.laneSnapshot?.lease;return t&&Date.parse(t.leaseExpiresAt)>Date.now()?t:null}return this.coordinator?.authorityLease??null}subscribeAuthority(t){return this.listeners.add(t),()=>{this.listeners.delete(t)}}currentConsumerBinding(){return this.identity&&this.lineage?this.consumerBinding(this.identity,this.lineage):null}async prepareConsumerBinding(){return this.preparing||(this.preparing=(async()=>{if(this.options.identityLane)return await this.prepareLaneBinding();const t=await this.identityStore.acquire(this.handlerIdentity,this.journal),e={journalStoreId:t.journalStoreId,journalGeneration:t.journalGeneration};return this.lineage=e,this.identity=t,this.consumerBinding(t,e)})().finally(()=>{this.preparing=void 0})),await this.preparing}setRuntimeConsumerLive(t){if(this.started)throw new Error("runtime consumer availability is immutable while registered");this.runtimeConsumerLive=t}setRuntimeServiceMethods(t){if(this.started)throw new Error("runtime Service methods are immutable while registered");this.runtimeServiceMethods=intersectRuntimeServiceMethodsWithBackendCapabilities(t)}setRuntimeRealtimeCapabilityReady(t,e){const i=this.realtimeCapabilityReady.get(t)!==e;if(this.realtimeCapabilityReady.set(t,e),!i||!this.started)return;const r=this.lifecycleVersion;this.refreshRealtimeCapabilities(r).catch(t=>this.options.logger?.warn?.(`[runtime-authority] authority lease refresh fenced (realtime capability): ${t instanceof Error?t.message:t}`))}setRuntimeResourceBindingContractRevision(t){if(this.started)throw new Error("runtime resource binding revision is immutable while registered");if(void 0!==t&&t!==RUNTIME_RESOURCE_BINDING_CONTRACT_REVISION)throw new Error("unsupported runtime resource binding revision");this.resourceBindingContractRevision=t}async start(t){if(this.starting)return await this.starting;if(this.started)return;if(t?.aborted)return;const e=this.lifecycleVersion,i=this.startOnce(e,t).finally(()=>{this.starting===i&&(this.starting=void 0)});this.starting=i,await i}stop(){this.lifecycleVersion++,this.laneTimer&&clearTimeout(this.laneTimer),this.laneTimer=void 0,this.laneSnapshot=null,this.starting=void 0,this.started=!1;const t=this.coordinator;t&&this.detachCoordinator(t),this.publish(null)}async startOnce(t,e){if(await this.prepareConsumerBinding(),t!==this.lifecycleVersion||e?.aborted)return;this.lineage;const i=this.identity;if(this.started=!0,this.options.identityLane)return this.publish(this.authorityLease),void this.scheduleLanePoll(t);await this.activate(i,t)}async prepareLaneBinding(){for(let t=0;;t++){const{snapshot:e,reason:i}=await this.options.identityLane.read();if(e)return await this.adoptLane(e),this.consumerBinding(this.identity,this.lineage);t%15==0&&this.options.logger?.info?.(`[runtime-authority] identity lane pending: ${i}`),await new Promise(t=>setTimeout(t,this.options.identityLanePollMs??2e3))}}async adoptLane(t){const e=await this.journal.adoptExternalLineage({journalStoreId:t.journalStoreId,journalGeneration:t.journalGeneration});this.lineage=e,this.identity={registrationId:t.registrationId,handlerIdentity:t.lease.handlerIdentity,journalStoreId:e.journalStoreId,journalGeneration:e.journalGeneration},this.laneSnapshot=t}scheduleLanePoll(t){this.laneTimer=setTimeout(()=>{(async()=>{if(!this.isLifecycleCurrent(t))return;const e=this.authorityLease;try{const{snapshot:e}=await this.options.identityLane.read();if(!this.isLifecycleCurrent(t))return;e?await this.adoptLane(e):this.laneSnapshot=null}catch(t){this.laneSnapshot=null,this.options.logger?.warn?.(`[runtime-authority] identity lane read failed: ${t instanceof Error?t.message:t}`)}const i=this.authorityLease;e?.leaseId===i?.leaseId&&e?.runtimeLeaseCredential===i?.runtimeLeaseCredential&&e?.runtimeEpoch===i?.runtimeEpoch||this.publish(i),this.isLifecycleCurrent(t)&&this.scheduleLanePoll(t)})()},this.options.identityLanePollMs??2e3),this.laneTimer.unref?.()}async activate(t,e){if(!this.isLifecycleCurrent(e))return;if(this.resourceBindingContractRevision&&!this.runtimeConsumerLive)throw new Error("resource binding revision requires a live Message Service consumer");if(!this.runtimeConsumerLive||0===this.runtimeServiceMethods.length)throw new Error(`runtime registration refused: consumerLive=${this.runtimeConsumerLive} methods=${this.runtimeServiceMethods.length}`);const i=[...this.runtimeServiceMethods],r=runtimeCapabilities(this.runtimeServiceMethods,this.realtimeCapabilityReady);this.options.logger?.info?.(`[runtime-authority] advertising ${i.length} methods (renew-safe)`);const n=++this.coordinatorGeneration,s=new RuntimeRegistrationCoordinator(new InstanceAuthorityRuntimeRegistrationTransport({gateway:this.options.gateway,registrationId:t.registrationId}),{registrationId:t.registrationId,handlerIdentity:t.handlerIdentity,contractRevision:RUNTIME_RPC_CONTRACT_REVISION,runtimeMethods:i,capabilities:r,manifestDigest:this.manifestDigest,journalStoreId:t.journalStoreId,journalGeneration:t.journalGeneration,instanceIdentityKeyId:createHash("sha256").update(this.options.keys.publicKey).digest("hex"),targetInstanceId:this.options.targetInstanceId,signIdentityProof:t=>signAgentMessage(t,this.options.keys.privateKey),getMSConnectionStatus:this.options.getMSConnectionStatus},(t,i)=>{if(!this.isCoordinatorCurrent(e,n,s))return;const r=i instanceof Error?i.message:void 0!==i?String(i):"";this.options.logger?.warn?.(r?`[runtime-authority] lease lost: ${t}: ${r}`:`[runtime-authority] lease lost: ${t}`),"fenced"===t&&this.publish(null,"fenced");const o=i instanceof RuntimeRegistrationJournalAdvanceRequiredError?i:void 0;("expired"===t||o)&&this.rollExpired(e,n,s,o).catch(t=>this.options.logger?.warn?.(`[runtime-authority] authority lease roll fenced (expired registration): ${t instanceof Error?t.message:t}`))});this.coordinator=s,this.unsubscribeCoordinator=s.subscribeAuthority(t=>{this.isCoordinatorCurrent(e,n,s)&&(t&&this.authorityLeasePending?(this.authorityLeasePending=!1,this.options.logger?.info?.("[runtime-authority] registration active (recovered after handoff)")):t||(this.authorityLeasePending=!0),this.publish(t))});const o=await s.start();this.isCoordinatorCurrent(e,n,s)?(this.options.logger?.info?.("[runtime-authority] registration "+(o?"active":"pending")),o||(this.authorityLeasePending=!0)):this.stopCoordinatorOnce(s)}async rollExpired(t,e,i,r){if(!this.lineage||!this.isCoordinatorCurrent(t,e,i))return;const n=this.rolling.get(e);if(n)return await n;let s=!1,o=!1;const a=(async()=>{if(!this.isCoordinatorCurrent(t,e,i))return;this.detachCoordinator(i),s=!0;const n=this.identity;if(!n)return;let a,h=0;for(;;){if(!this.isLifecycleCurrent(t)||void 0!==this.coordinator)return;try{a=await this.identityStore.roll(n,this.journal,{authorityCurrentGeneration:r?.currentJournalGeneration,expectedJournalGeneration:r?.expectedJournalGeneration});break}catch(t){if(!isRetryableRegistrationRollError(t))throw t;o||(this.publishDetachedWithoutLease("expired registration roll retry"),o=!0),this.options.logger?.warn?.(`[runtime-authority] retrying durable registration roll: ${t instanceof Error?t.message:t}`),await new Promise(t=>setTimeout(t,Math.min(250*2**Math.min(h++,4),4e3)))}}this.isLifecycleCurrent(t)&&void 0===this.coordinator&&(this.identity=a,this.lineage={journalStoreId:a.journalStoreId,journalGeneration:a.journalGeneration},await this.activate(a,t))})().finally(()=>{this.rolling.get(e)===a&&this.rolling.delete(e),s&&!o&&this.publishDetachedWithoutLease("expired registration roll")});this.rolling.set(e,a),await a}async refreshRealtimeCapabilities(t){if(!this.isLifecycleCurrent(t))return;const e=this.refreshing;if(e)return await e;let i=!1;const r=(async()=>{if(!this.isLifecycleCurrent(t))return;const e=this.identity,r=this.coordinator;e&&r&&(r.authorityLease?(this.detachCoordinator(r),i=!0,this.isLifecycleCurrent(t)&&void 0===this.coordinator&&await this.activate(e,t)):this.options.logger?.info?.("[runtime-authority] deferring realtime capability refresh until registration is active"))})().finally(()=>{this.refreshing===r&&(this.refreshing=void 0),i&&this.publishDetachedWithoutLease("realtime capability refresh")});this.refreshing=r,await r}isLifecycleCurrent(t){return this.started&&t===this.lifecycleVersion}isCoordinatorCurrent(t,e,i){return this.isLifecycleCurrent(t)&&this.coordinatorGeneration===e&&this.coordinator===i}detachCoordinator(t){this.coordinator===t&&(this.unsubscribeCoordinator?.(),this.unsubscribeCoordinator=void 0,this.coordinator=void 0),this.stopCoordinatorOnce(t)}publishDetachedWithoutLease(t){null===this.authorityLease&&(this.authorityLeasePending=!0,this.options.logger?.warn?.(`[runtime-authority] authority lease cleared with no live successor lease (${t})`),this.publish(null))}stopCoordinatorOnce(t){this.stoppedCoordinators.has(t)||(this.stoppedCoordinators.add(t),t.stop())}publish(t,e){for(const i of this.listeners)try{i(t,e)}catch{}}consumerBinding(t,e){return Object.freeze({handlerIdentity:t.handlerIdentity,journalStoreId:e.journalStoreId,journalGeneration:e.journalGeneration})}}const REALTIME_CAPABILITY_NAMES=["terminal","canvas"];function isRetryableRegistrationRollError(t){return t instanceof Error&&(t.message.includes("owned directory lock lease expired")||t.message.includes("owned directory lock was fenced")||t.message.includes("timed out acquiring lock")||t.message.includes("owned directory lock is busy"))}function runtimeCapabilities(t,e){const i=new Set(t),r=RUNTIME_METHOD_PARITY_MANIFEST.filter(t=>t.serviceAllowed&&void 0!==runtimeInvocationMethod(t)&&i.has(runtimeInvocationMethod(t))).map(t=>t.capability),n=REALTIME_CAPABILITY_NAMES.filter(t=>!0===e.get(t));return[...new Set([...r,...n])]}function runtimeInvocationMethod(t){return"methods"===t.serviceRoute&&t.serviceMethod?t.serviceMethod:Object.entries(CONVERSATION_RUNTIME_CANONICAL_COMMAND_NAMES).find(([,e])=>e===t.canonicalCommand)?.[0]}
@@ -1 +1 @@
1
- import{createHash}from"node:crypto";import{readFileSync}from"node:fs";import{RUNTIME_METHOD_PARITY_MANIFEST,RUNTIME_RPC_CONTRACT_REVISION}from"@beeos-ai/beeos-types/runtime";import{RUNTIME_AUTHORITY_PORTS_REVISION,RUNTIME_AUTHORITY_PORTS_RAW_SHA256,RUNTIME_AUTHORITY_PORTS_SHA256,RUNTIME_MESSAGE_CHANNEL_REVISION,RUNTIME_MESSAGE_CHANNEL_RAW_SHA256,RUNTIME_MESSAGE_CHANNEL_SHA256,RUNTIME_PARITY_MANIFEST,RUNTIME_PARITY_MANIFEST_RAW_SHA256,RUNTIME_PARITY_MANIFEST_REVISION,RUNTIME_PARITY_MANIFEST_SHA256,RUNTIME_PARITY_READY_METHODS,RUNTIME_SESSION_CANCEL_REVISION,RUNTIME_SESSION_CANCEL_SHA256}from"./runtime-parity-manifest.js";import{verifiedRuntimeParityManifestDigest}from"./runtime-parity-manifest-digest.js";export const RUNTIME_SERVICE_WIRE_CONTRACT_ARTIFACTS=Object.freeze([Object.freeze({revision:RUNTIME_MESSAGE_CHANNEL_REVISION,sha256:RUNTIME_MESSAGE_CHANNEL_SHA256,rawSha256:RUNTIME_MESSAGE_CHANNEL_RAW_SHA256}),Object.freeze({revision:RUNTIME_AUTHORITY_PORTS_REVISION,sha256:RUNTIME_AUTHORITY_PORTS_SHA256,rawSha256:RUNTIME_AUTHORITY_PORTS_RAW_SHA256}),Object.freeze({revision:RUNTIME_SESSION_CANCEL_REVISION,sha256:RUNTIME_SESSION_CANCEL_SHA256}),Object.freeze({revision:RUNTIME_PARITY_MANIFEST_REVISION,sha256:RUNTIME_PARITY_MANIFEST_SHA256})]);export function loadRuntimeServiceContractIdentity(){return verifyPackagedRuntimeServiceWireIdentity(),Object.freeze({contractRevision:RUNTIME_RPC_CONTRACT_REVISION,canonicalManifestDigest:verifiedRuntimeParityManifestDigest(),wireArtifacts:RUNTIME_SERVICE_WIRE_CONTRACT_ARTIFACTS})}const RUNTIME_SERVICE_WIRE_IDENTITY_RAW_SHA256="cb513d81cb23d2eb5d12357c24ce431bb4f3ea9bae2558d1c8113fdac32b239d";function verifyPackagedRuntimeServiceWireIdentity(){verifyRuntimeServiceWireIdentityBytes(readFileSync(new URL("../../runtime-service-wire-identity.v1.json",import.meta.url)))}export function verifyRuntimeServiceWireIdentityBytes(e){if(createHash("sha256").update(e).digest("hex")!==RUNTIME_SERVICE_WIRE_IDENTITY_RAW_SHA256)throw new Error("packaged runtime Service wire identity bytes drifted");const i=JSON.parse(Buffer.from(e).toString("utf8"));if(1!==i.schemaVersion||"RFC8785/JCS"!==i.canonicalization||"SHA-256"!==i.hashAlgorithm||"@beeos-ai/beeos-types"!==i.canonicalTypes?.package||"4.4.2-cloud.7"!==i.canonicalTypes.version||i.canonicalTypes.runtimeRpcContractRevision!==RUNTIME_RPC_CONTRACT_REVISION||"9e33be754abb221cba4e4cd03eb760a170865248952346afd461427abd2cfaad"!==i.canonicalTypes.runtimeMethodManifestRawSHA256||i.canonicalTypes.runtimeMethodManifestJcsSHA256!==verifiedRuntimeParityManifestDigest())throw new Error("packaged runtime Service canonical Types identity drifted");const t=new Map([["runtime-parity-manifest",["openapi/runtime-parity-manifest-v1.json",RUNTIME_PARITY_MANIFEST_REVISION,RUNTIME_PARITY_MANIFEST_RAW_SHA256,RUNTIME_PARITY_MANIFEST_SHA256]],["runtime-message-channel",["openapi/runtime-message-channel-v1.contract.json",RUNTIME_MESSAGE_CHANNEL_REVISION,RUNTIME_MESSAGE_CHANNEL_RAW_SHA256,RUNTIME_MESSAGE_CHANNEL_SHA256]],["runtime-authority-ports",["openapi/runtime-authority-ports-v1.contract.json",RUNTIME_AUTHORITY_PORTS_REVISION,RUNTIME_AUTHORITY_PORTS_RAW_SHA256,RUNTIME_AUTHORITY_PORTS_SHA256]]]);if(!Array.isArray(i.artifacts)||i.artifacts.length!==t.size)throw new Error("packaged runtime Service wire artifact set drifted");for(const e of i.artifacts){const i=t.get(e.name);if(!i||e.path!==i[0]||e.revision!==i[1]||e.rawSHA256!==i[2]||e.jcsSHA256!==i[3])throw new Error(`packaged runtime Service wire artifact ${e.name||"<missing>"} drifted`);t.delete(e.name)}if(0!==t.size)throw new Error("packaged runtime Service wire artifact is missing")}export function intersectRuntimeServiceMethodsWithBackendCapabilities(e){const i=RUNTIME_PARITY_READY_METHODS,t=new Set(RUNTIME_PARITY_MANIFEST.map(e=>e.method)),r=new Set;for(const e of RUNTIME_METHOD_PARITY_MANIFEST)e.serviceAllowed&&t.has(e.acpMethod)&&(e.minimumRuntimeContractRevision&&e.minimumRuntimeContractRevision!==RUNTIME_RPC_CONTRACT_REVISION||r.add(e.acpMethod));return Object.freeze([...new Set(e)].filter(e=>i.has(e)&&r.has(e)))}export function runtimeServiceContractRequirement(){return`Types ${RUNTIME_RPC_CONTRACT_REVISION}; canonical-manifest ${verifiedRuntimeParityManifestDigest()}; `+RUNTIME_SERVICE_WIRE_CONTRACT_ARTIFACTS.map(e=>`${e.revision}:${e.sha256}`).join("; ")}
1
+ import{createHash}from"node:crypto";import{readFileSync}from"node:fs";import{RUNTIME_METHOD_PARITY_MANIFEST,RUNTIME_RPC_CONTRACT_REVISION}from"@beeos-ai/beeos-types/runtime";import{RUNTIME_AUTHORITY_PORTS_REVISION,RUNTIME_AUTHORITY_PORTS_RAW_SHA256,RUNTIME_AUTHORITY_PORTS_SHA256,RUNTIME_MESSAGE_CHANNEL_REVISION,RUNTIME_MESSAGE_CHANNEL_RAW_SHA256,RUNTIME_MESSAGE_CHANNEL_SHA256,RUNTIME_PARITY_MANIFEST,RUNTIME_PARITY_MANIFEST_RAW_SHA256,RUNTIME_PARITY_MANIFEST_REVISION,RUNTIME_PARITY_MANIFEST_SHA256,RUNTIME_PARITY_READY_METHODS,RUNTIME_SESSION_CANCEL_REVISION,RUNTIME_SESSION_CANCEL_SHA256}from"./runtime-parity-manifest.js";import{verifiedRuntimeParityManifestDigest}from"./runtime-parity-manifest-digest.js";export const RUNTIME_SERVICE_WIRE_CONTRACT_ARTIFACTS=Object.freeze([Object.freeze({revision:RUNTIME_MESSAGE_CHANNEL_REVISION,sha256:RUNTIME_MESSAGE_CHANNEL_SHA256,rawSha256:RUNTIME_MESSAGE_CHANNEL_RAW_SHA256}),Object.freeze({revision:RUNTIME_AUTHORITY_PORTS_REVISION,sha256:RUNTIME_AUTHORITY_PORTS_SHA256,rawSha256:RUNTIME_AUTHORITY_PORTS_RAW_SHA256}),Object.freeze({revision:RUNTIME_SESSION_CANCEL_REVISION,sha256:RUNTIME_SESSION_CANCEL_SHA256}),Object.freeze({revision:RUNTIME_PARITY_MANIFEST_REVISION,sha256:RUNTIME_PARITY_MANIFEST_SHA256})]);export function loadRuntimeServiceContractIdentity(){return verifyPackagedRuntimeServiceWireIdentity(),Object.freeze({contractRevision:RUNTIME_RPC_CONTRACT_REVISION,canonicalManifestDigest:verifiedRuntimeParityManifestDigest(),wireArtifacts:RUNTIME_SERVICE_WIRE_CONTRACT_ARTIFACTS})}const RUNTIME_SERVICE_WIRE_IDENTITY_RAW_SHA256="492d1f82307549d99d7a5e77c076777c715e83bfe583c35edc7c6c1812db1e7a";function verifyPackagedRuntimeServiceWireIdentity(){verifyRuntimeServiceWireIdentityBytes(readFileSync(new URL("../../runtime-service-wire-identity.v1.json",import.meta.url)))}export function verifyRuntimeServiceWireIdentityBytes(e){if(createHash("sha256").update(e).digest("hex")!==RUNTIME_SERVICE_WIRE_IDENTITY_RAW_SHA256)throw new Error("packaged runtime Service wire identity bytes drifted");const i=JSON.parse(Buffer.from(e).toString("utf8"));if(1!==i.schemaVersion||"RFC8785/JCS"!==i.canonicalization||"SHA-256"!==i.hashAlgorithm||"@beeos-ai/beeos-types"!==i.canonicalTypes?.package||"4.5.2-cloud.2"!==i.canonicalTypes.version||i.canonicalTypes.runtimeRpcContractRevision!==RUNTIME_RPC_CONTRACT_REVISION||"9e33be754abb221cba4e4cd03eb760a170865248952346afd461427abd2cfaad"!==i.canonicalTypes.runtimeMethodManifestRawSHA256||i.canonicalTypes.runtimeMethodManifestJcsSHA256!==verifiedRuntimeParityManifestDigest())throw new Error("packaged runtime Service canonical Types identity drifted");const t=new Map([["runtime-parity-manifest",["openapi/runtime-parity-manifest-v1.json",RUNTIME_PARITY_MANIFEST_REVISION,RUNTIME_PARITY_MANIFEST_RAW_SHA256,RUNTIME_PARITY_MANIFEST_SHA256]],["runtime-message-channel",["openapi/runtime-message-channel-v1.contract.json",RUNTIME_MESSAGE_CHANNEL_REVISION,RUNTIME_MESSAGE_CHANNEL_RAW_SHA256,RUNTIME_MESSAGE_CHANNEL_SHA256]],["runtime-authority-ports",["openapi/runtime-authority-ports-v1.contract.json",RUNTIME_AUTHORITY_PORTS_REVISION,RUNTIME_AUTHORITY_PORTS_RAW_SHA256,RUNTIME_AUTHORITY_PORTS_SHA256]]]);if(!Array.isArray(i.artifacts)||i.artifacts.length!==t.size)throw new Error("packaged runtime Service wire artifact set drifted");for(const e of i.artifacts){const i=t.get(e.name);if(!i||e.path!==i[0]||e.revision!==i[1]||e.rawSHA256!==i[2]||e.jcsSHA256!==i[3])throw new Error(`packaged runtime Service wire artifact ${e.name||"<missing>"} drifted`);t.delete(e.name)}if(0!==t.size)throw new Error("packaged runtime Service wire artifact is missing")}export function intersectRuntimeServiceMethodsWithBackendCapabilities(e){const i=RUNTIME_PARITY_READY_METHODS,t=new Set(RUNTIME_PARITY_MANIFEST.map(e=>e.method)),r=new Set;for(const e of RUNTIME_METHOD_PARITY_MANIFEST)e.serviceAllowed&&t.has(e.acpMethod)&&(e.minimumRuntimeContractRevision&&e.minimumRuntimeContractRevision!==RUNTIME_RPC_CONTRACT_REVISION||r.add(e.acpMethod));return Object.freeze([...new Set(e)].filter(e=>i.has(e)&&r.has(e)))}export function runtimeServiceContractRequirement(){return`Types ${RUNTIME_RPC_CONTRACT_REVISION}; canonical-manifest ${verifiedRuntimeParityManifestDigest()}; `+RUNTIME_SERVICE_WIRE_CONTRACT_ARTIFACTS.map(e=>`${e.revision}:${e.sha256}`).join("; ")}
@@ -1 +1 @@
1
- import{RUNTIME_RESOURCE_BINDING_CONTRACT_REVISION}from"@beeos-ai/beeos-types/runtime";import{TemplatePackageError,parseAndValidateTemplatePackage}from"../acp-gateway/agent-template-package.js";import{HttpRuntimeOperationResourceResolver,RuntimeResourceResolutionError}from"./runtime-operation-resource-resolver.js";import{CloudGatewayGrantJwksVerifier,GatewayGrantJwksVerifier,RuntimeJwksVerifier,createCloudGrantVerifier,warmGrantJwks}from"@beeos-ai/cloud-harness-sdk";import{fetchRuntimeLeaseAttempt}from"./message-service-runtime-authenticated-fetch.js";import{MessageServiceConversationClearHttpAuthority}from"./message-service-conversation-clear-http-authority.js";import{MessageServiceConversationHistoryBoundary}from"./message-service-conversation-history-boundary.js";import{MessageServiceSessionModelHttpAuthority}from"./message-service-session-model-http-authority.js";import{MsRuntimeMessageChannelConsumerFactory}from"./ms-runtime-message-channel-consumer-factory.js";import{RUNTIME_MESSAGE_CHANNEL_REVISION,RUNTIME_PARITY_MANIFEST}from"./runtime-parity-manifest.js";import{intersectRuntimeServiceMethodsWithBackendCapabilities,loadRuntimeServiceContractIdentity,runtimeServiceContractRequirement}from"./runtime-service-contract-compatibility.js";const CONTRACT_ARTIFACT_BLOCKER=Object.freeze({id:"contract_artifact_mismatch",owner:"backend",requiredContract:"frozen Types manifest and Service wire artifacts"}),TRANSPORT_BLOCKER=Object.freeze({id:"message_channel_transport",owner:"backend",requiredContract:`${RUNTIME_MESSAGE_CHANNEL_REVISION} HTTP delivery/history/append authority`}),JWKS_BLOCKER=Object.freeze({id:"gateway_grant_jwks",owner:"backend",requiredContract:"Purpose-bound Gateway runtime execution-grant JWKS proxy"}),POISON_JOURNAL_BLOCKER=Object.freeze({id:"poison_journal",owner:"openclaw_runtime_client",requiredContract:"Durable bounded runtime poison-delivery journal"}),grantAdapterOptions=new WeakMap;export class AgentGatewayGrantJwksReadinessAdapter{constructor(e){grantAdapterOptions.set(this,{gateway:e.gateway})}}export class AgentGatewaySplitJwksReadinessAdapter extends AgentGatewayGrantJwksReadinessAdapter{}const transportOptions=new WeakMap;export class RuntimeServiceTransportReadinessAdapter{constructor(e){transportOptions.set(this,{messageService:e.messageService,messageClient:e.messageClient,configuredServiceUrl:e.configuredServiceUrl,scopedDeliveryKey:e.scopedDeliveryKey,gateway:e.gateway})}}const cloudTransportOptions=new WeakMap;export class CloudRuntimeServiceTransportReadinessAdapter{constructor(e){cloudTransportOptions.set(this,{...e})}}const CAPABILITY_BLOCKERS=Object.freeze(RUNTIME_PARITY_MANIFEST.filter(e=>"blocked"===e.status).map(e=>Object.freeze({method:e.method,owner:e.blockerOwner,requiredContract:e.contractRevision})));export async function createRuntimeServiceProductionCandidate(e){try{loadRuntimeServiceContractIdentity()}catch{return{status:"unavailable",blockers:Object.freeze([{...CONTRACT_ARTIFACT_BLOCKER,requiredContract:contractArtifactRequirement()}])}}if(!e.poisonJournal)return{status:"unavailable",blockers:Object.freeze([POISON_JOURNAL_BLOCKER])};const r=e.cloudTransportAdapter?cloudTransportOptions.get(e.cloudTransportAdapter):void 0,t=e.authorizationAdapter?startGatewayGrantJwks(e.authorizationAdapter,Boolean(r),e.onGrantJwksWarm):void 0;if(r){if(!r.configuredServiceUrl)return{status:"unavailable",blockers:Object.freeze([TRANSPORT_BLOCKER,...t?[]:[JWKS_BLOCKER]])};if(!(t instanceof CloudGatewayGrantJwksVerifier))return{status:"unavailable",blockers:Object.freeze([JWKS_BLOCKER])};const n=intersectRuntimeServiceMethodsWithBackendCapabilities(["models/list","agent/create","agent/applyTemplate","skills/list","skills/update","skills/uninstall","skills/install","mcp/list","mcp/set","mcp/unset"]);return n.includes("models/list")?{status:"ready",blockers:[],capabilityBlockers:[],serviceMethods:n,factory:new MsRuntimeMessageChannelConsumerFactory({runtimeDelivery:r.runtimeDelivery,...r.nativePrompt?{nativePrompt:r.nativePrompt}:{},...r.sessionNewConversation?{sessionNewConversation:r.sessionNewConversation}:{},verifier:t,ports:e.attemptPorts??runtimeMessageAttemptPorts(r,r.configuredServiceUrl,{requireCredentialExpiry:!0}),journal:e.commandJournal,allowedMethods:new Set(n),options:{poisonJournal:e.poisonJournal,requestSchemaVersion:2,...e.onConsumerDeliveryError?{onDeliveryError:e.onConsumerDeliveryError}:{},...e.onConsumerLeaseStarvation?{onLeaseStarvation:e.onConsumerLeaseStarvation}:{},...e.onConsumerRequestTerminalAccepted?{onRequestTerminalAccepted:e.onConsumerRequestTerminalAccepted}:{},...e.onConsumerRequestAcknowledged?{onRequestAcknowledged:e.onConsumerRequestAcknowledged}:{},...e.onConsumerRequestFailure?{onRequestFailure:e.onConsumerRequestFailure}:{}}})}:{status:"unavailable",blockers:Object.freeze([CONTRACT_ARTIFACT_BLOCKER])}}const n=e.transportAdapter?transportOptions.get(e.transportAdapter):void 0;if(!n?.messageClient||!n.configuredServiceUrl)return{status:"unavailable",blockers:Object.freeze([TRANSPORT_BLOCKER,...t?[]:[JWKS_BLOCKER]])};if(!t)return{status:"unavailable",blockers:Object.freeze([JWKS_BLOCKER])};const a=runtimeMethods(),o=e.attemptPorts??runtimeMessageAttemptPorts(n,n.configuredServiceUrl);return{status:"ready",blockers:[],capabilityBlockers:CAPABILITY_BLOCKERS,serviceMethods:a,factory:new MsRuntimeMessageChannelConsumerFactory({client:n.messageClient,verifier:t,ports:o,journal:e.commandJournal,allowedMethods:new Set(a),options:{poisonJournal:e.poisonJournal,...e.onConsumerDeliveryError?{onDeliveryError:e.onConsumerDeliveryError}:{},...e.onConsumerLeaseStarvation?{onLeaseStarvation:e.onConsumerLeaseStarvation}:{},...e.onConsumerRequestTerminalAccepted?{onRequestTerminalAccepted:e.onConsumerRequestTerminalAccepted}:{},...e.onConsumerRequestAcknowledged?{onRequestAcknowledged:e.onConsumerRequestAcknowledged}:{},...e.onConsumerRequestFailure?{onRequestFailure:e.onConsumerRequestFailure}:{}}}),...void 0===e.attemptPorts?{resourceBindingContractRevision:RUNTIME_RESOURCE_BINDING_CONTRACT_REVISION}:{}}}function startGatewayGrantJwks(e,r,t){const n=grantAdapterOptions.get(e);if(!n)throw new Error("untrusted Gateway grant readiness adapter");const a=n.gateway.baseUrl.toString().replace(/\/+$/,""),o=r?createCloudGrantVerifier(a):new GatewayGrantJwksVerifier(new RuntimeJwksVerifier(`${a}/.well-known/runtime-execution-grant-jwks.json`));return warmGrantJwks(o,{onRetry:({attempt:e,error:r})=>t?.({ready:!1,attempt:e,error:r})}).then(e=>t?.({ready:e})),o}export function runtimeMessageAttemptPorts(e,r,t={}){return{async resolve({request:n,command:a,executionGrant:o,getLease:s,journalStoreId:i,journalGeneration:c,journalRecord:u,abortSignal:l}){const d="resourceBindings"in n?n.resourceBindings:void 0,m="resourceBindingContractRevision"in n?n.resourceBindingContractRevision:void 0,p="skills.install"===a.name||"agent.applyTemplate"===a.name?a.params.artifact:void 0,R="mcp.prepare"===a.name||"mcp.set"===a.name?a.params.definition:void 0;if((p||R)&&(m!==RUNTIME_RESOURCE_BINDING_CONTRACT_REVISION||!d))throw new RuntimeResourceResolutionError("resolution_rejected","resource-bound Service delivery requires the Types 4.1 binding carrier",void 0,`binding_contract_revision=${m??"absent"} bindings=${d?"present":"absent"}`);const C=new HttpRuntimeOperationResourceResolver({gateway:e.gateway,operationId:n.operationId,requestMessageId:n.requestMessageId,instanceId:n.instanceId,paramsHash:n.paramsHash,bindings:d??[],getAuthorization:()=>{const e=s();return{executionGrant:o,runtimeLeaseCredential:e.runtimeLeaseCredential,fence:deliveryFence(n,e,i,c)}},parseSkillArtifact:e=>parseSkillPackage(e),signal:l,...t.requireCredentialExpiry?{requireCredentialExpiry:!0}:{}});if("prepared"!==u.phase&&"started"!==u.phase||(p&&await C.readVerifiedArtifact(p),R&&await C.resolveMcpDefinition(R)),"session.setModel"===a.name){if(!e.messageService)throw new Error("Cloud runtime cannot project a personal session model");const t=e.scopedDeliveryKey;if(!t)throw new Error("session model requires X-Runtime-Delivery-Key");return{resources:C,sessionModelProjection:{project:async({conversationId:e,modelOverrideId:a})=>{const o=s(),i=new MessageServiceSessionModelHttpAuthority({messageServiceUrl:r,scopedDeliveryKey:t,fetchAuthorized:(e,r)=>fetchRuntimeLeaseAttempt(e,r??{})});return await i.project({conversationId:e,operationId:n.operationId,requestMessageId:n.requestMessageId,requestHash:n.requestHash,modelOverrideId:a,runtimeLeaseCredential:o.runtimeLeaseCredential})}}}}if("session.clear"!==a.name)return{resources:C};if(!e.messageService)throw new Error("Cloud runtime cannot access personal conversation history");a.context.conversation;const v=s(),y=new MessageServiceConversationClearHttpAuthority({messageServiceUrl:r,runtimeLeaseCredential:v.runtimeLeaseCredential,fetchAuthorized:(e,r)=>fetchRuntimeLeaseAttempt(e,r??{})});return{resources:C,historyBoundary:new MessageServiceConversationHistoryBoundary({operationId:n.operationId,requestMessageId:n.requestMessageId,requestHash:n.requestHash,fence:deliveryFence(n,v,i,c),authority:y})}}}}function runtimeMethods(){return[...intersectRuntimeServiceMethodsWithBackendCapabilities(RUNTIME_PARITY_MANIFEST.filter(e=>"ready"===e.status).map(e=>e.method))]}function contractArtifactRequirement(){try{return runtimeServiceContractRequirement()}catch{return CONTRACT_ARTIFACT_BLOCKER.requiredContract}}function parseSkillPackage(e){try{return parseAndValidateTemplatePackage(Buffer.from(e))}catch(r){if(!(r instanceof TemplatePackageError))throw r;throw new RuntimeResourceResolutionError("resolution_rejected","skill package failed validation",void 0,`package_invalid bytes=${e.byteLength} reason=${r.message}`)}}function deliveryFence(e,r,t,n){if(r.handlerIdentity!==e.runtimeHandlerIdentity||r.runtimeEpoch!==e.dispatchEpoch)throw new Error("runtime delivery fence does not match the retained request");return{claimId:e.requestMessageId,handlerIdentity:r.handlerIdentity,runtimeEpoch:r.runtimeEpoch,fencingToken:r.runtimeEpoch,journalStoreId:t,journalGeneration:n}}
1
+ import{RUNTIME_RESOURCE_BINDING_CONTRACT_REVISION}from"@beeos-ai/beeos-types/runtime";import{TemplatePackageError,parseAndValidateTemplatePackage}from"../acp-gateway/agent-template-package.js";import{HttpRuntimeOperationResourceResolver,RuntimeResourceResolutionError}from"./runtime-operation-resource-resolver.js";import{CloudGatewayGrantJwksVerifier,GatewayGrantJwksVerifier,RuntimeJwksVerifier,createCloudGrantVerifier,warmGrantJwks}from"@beeos-ai/cloud-harness-sdk";import{fetchRuntimeLeaseAttempt}from"./message-service-runtime-authenticated-fetch.js";import{MessageServiceConversationClearHttpAuthority}from"./message-service-conversation-clear-http-authority.js";import{MessageServiceConversationHistoryBoundary}from"./message-service-conversation-history-boundary.js";import{MessageServiceSessionModelHttpAuthority}from"./message-service-session-model-http-authority.js";import{MsRuntimeMessageChannelConsumerFactory}from"./ms-runtime-message-channel-consumer-factory.js";import{RUNTIME_MESSAGE_CHANNEL_REVISION,RUNTIME_PARITY_MANIFEST}from"./runtime-parity-manifest.js";import{intersectRuntimeServiceMethodsWithBackendCapabilities,loadRuntimeServiceContractIdentity,runtimeServiceContractRequirement}from"./runtime-service-contract-compatibility.js";const CONTRACT_ARTIFACT_BLOCKER=Object.freeze({id:"contract_artifact_mismatch",owner:"backend",requiredContract:"frozen Types manifest and Service wire artifacts"}),TRANSPORT_BLOCKER=Object.freeze({id:"message_channel_transport",owner:"backend",requiredContract:`${RUNTIME_MESSAGE_CHANNEL_REVISION} HTTP delivery/history/append authority`}),JWKS_BLOCKER=Object.freeze({id:"gateway_grant_jwks",owner:"backend",requiredContract:"Purpose-bound Gateway runtime execution-grant JWKS proxy"}),POISON_JOURNAL_BLOCKER=Object.freeze({id:"poison_journal",owner:"openclaw_runtime_client",requiredContract:"Durable bounded runtime poison-delivery journal"}),grantAdapterOptions=new WeakMap;export class AgentGatewayGrantJwksReadinessAdapter{constructor(e){grantAdapterOptions.set(this,{gateway:e.gateway})}}export class AgentGatewaySplitJwksReadinessAdapter extends AgentGatewayGrantJwksReadinessAdapter{}const transportOptions=new WeakMap;export class RuntimeServiceTransportReadinessAdapter{constructor(e){transportOptions.set(this,{messageService:e.messageService,messageClient:e.messageClient,configuredServiceUrl:e.configuredServiceUrl,scopedDeliveryKey:e.scopedDeliveryKey,gateway:e.gateway})}}const cloudTransportOptions=new WeakMap;export class CloudRuntimeServiceTransportReadinessAdapter{constructor(e){cloudTransportOptions.set(this,{...e})}}const CAPABILITY_BLOCKERS=Object.freeze(RUNTIME_PARITY_MANIFEST.filter(e=>"blocked"===e.status).map(e=>Object.freeze({method:e.method,owner:e.blockerOwner,requiredContract:e.contractRevision})));export async function createRuntimeServiceProductionCandidate(e){try{loadRuntimeServiceContractIdentity()}catch{return{status:"unavailable",blockers:Object.freeze([{...CONTRACT_ARTIFACT_BLOCKER,requiredContract:contractArtifactRequirement()}])}}if(!e.poisonJournal)return{status:"unavailable",blockers:Object.freeze([POISON_JOURNAL_BLOCKER])};const r=e.cloudTransportAdapter?cloudTransportOptions.get(e.cloudTransportAdapter):void 0,t=e.authorizationAdapter?startGatewayGrantJwks(e.authorizationAdapter,Boolean(r),e.onGrantJwksWarm):void 0;if(r){if(!r.configuredServiceUrl)return{status:"unavailable",blockers:Object.freeze([TRANSPORT_BLOCKER,...t?[]:[JWKS_BLOCKER]])};if(!(t instanceof CloudGatewayGrantJwksVerifier))return{status:"unavailable",blockers:Object.freeze([JWKS_BLOCKER])};const n=intersectRuntimeServiceMethodsWithBackendCapabilities(["models/list","agent/create","agent/applyTemplate","skills/list","skills/update","skills/uninstall","skills/install","mcp/list","mcp/set","mcp/unset"]);return n.includes("models/list")?{status:"ready",blockers:[],capabilityBlockers:[],serviceMethods:n,factory:new MsRuntimeMessageChannelConsumerFactory({runtimeDelivery:r.runtimeDelivery,...r.centralPrompt?{centralPrompt:r.centralPrompt}:{},...r.nativePrompt?{nativePrompt:r.nativePrompt}:{},...r.sessionNewConversation?{sessionNewConversation:r.sessionNewConversation}:{},verifier:t,ports:e.attemptPorts??runtimeMessageAttemptPorts(r,r.configuredServiceUrl,{requireCredentialExpiry:!0}),journal:e.commandJournal,allowedMethods:new Set(n),options:{poisonJournal:e.poisonJournal,requestSchemaVersion:2,...e.onConsumerDeliveryError?{onDeliveryError:e.onConsumerDeliveryError}:{},...e.onConsumerLeaseStarvation?{onLeaseStarvation:e.onConsumerLeaseStarvation}:{},...e.onConsumerRequestTerminalAccepted?{onRequestTerminalAccepted:e.onConsumerRequestTerminalAccepted}:{},...e.onConsumerRequestAcknowledged?{onRequestAcknowledged:e.onConsumerRequestAcknowledged}:{},...e.onConsumerRequestFailure?{onRequestFailure:e.onConsumerRequestFailure}:{}}})}:{status:"unavailable",blockers:Object.freeze([CONTRACT_ARTIFACT_BLOCKER])}}const n=e.transportAdapter?transportOptions.get(e.transportAdapter):void 0;if(!n?.messageClient||!n.configuredServiceUrl)return{status:"unavailable",blockers:Object.freeze([TRANSPORT_BLOCKER,...t?[]:[JWKS_BLOCKER]])};if(!t)return{status:"unavailable",blockers:Object.freeze([JWKS_BLOCKER])};const a=runtimeMethods(),o=e.attemptPorts??runtimeMessageAttemptPorts(n,n.configuredServiceUrl);return{status:"ready",blockers:[],capabilityBlockers:CAPABILITY_BLOCKERS,serviceMethods:a,factory:new MsRuntimeMessageChannelConsumerFactory({client:n.messageClient,verifier:t,ports:o,journal:e.commandJournal,allowedMethods:new Set(a),options:{poisonJournal:e.poisonJournal,...e.onConsumerDeliveryError?{onDeliveryError:e.onConsumerDeliveryError}:{},...e.onConsumerLeaseStarvation?{onLeaseStarvation:e.onConsumerLeaseStarvation}:{},...e.onConsumerRequestTerminalAccepted?{onRequestTerminalAccepted:e.onConsumerRequestTerminalAccepted}:{},...e.onConsumerRequestAcknowledged?{onRequestAcknowledged:e.onConsumerRequestAcknowledged}:{},...e.onConsumerRequestFailure?{onRequestFailure:e.onConsumerRequestFailure}:{}}}),...void 0===e.attemptPorts?{resourceBindingContractRevision:RUNTIME_RESOURCE_BINDING_CONTRACT_REVISION}:{}}}function startGatewayGrantJwks(e,r,t){const n=grantAdapterOptions.get(e);if(!n)throw new Error("untrusted Gateway grant readiness adapter");const a=n.gateway.baseUrl.toString().replace(/\/+$/,""),o=r?createCloudGrantVerifier(a):new GatewayGrantJwksVerifier(new RuntimeJwksVerifier(`${a}/.well-known/runtime-execution-grant-jwks.json`));return warmGrantJwks(o,{onRetry:({attempt:e,error:r})=>t?.({ready:!1,attempt:e,error:r})}).then(e=>t?.({ready:e})),o}export function runtimeMessageAttemptPorts(e,r,t={}){return{async resolve({request:n,command:a,executionGrant:o,getLease:s,journalStoreId:i,journalGeneration:c,journalRecord:u,abortSignal:l}){const d="resourceBindings"in n?n.resourceBindings:void 0,m="resourceBindingContractRevision"in n?n.resourceBindingContractRevision:void 0,p="skills.install"===a.name||"agent.applyTemplate"===a.name?a.params.artifact:void 0,R="mcp.prepare"===a.name||"mcp.set"===a.name?a.params.definition:void 0;if((p||R)&&(m!==RUNTIME_RESOURCE_BINDING_CONTRACT_REVISION||!d))throw new RuntimeResourceResolutionError("resolution_rejected","resource-bound Service delivery requires the Types 4.1 binding carrier",void 0,`binding_contract_revision=${m??"absent"} bindings=${d?"present":"absent"}`);const C=new HttpRuntimeOperationResourceResolver({gateway:e.gateway,operationId:n.operationId,requestMessageId:n.requestMessageId,instanceId:n.instanceId,paramsHash:n.paramsHash,bindings:d??[],getAuthorization:()=>{const e=s();return{executionGrant:o,runtimeLeaseCredential:e.runtimeLeaseCredential,fence:deliveryFence(n,e,i,c)}},parseSkillArtifact:e=>parseSkillPackage(e),signal:l,...t.requireCredentialExpiry?{requireCredentialExpiry:!0}:{}});if("prepared"!==u.phase&&"started"!==u.phase||(p&&await C.readVerifiedArtifact(p),R&&await C.resolveMcpDefinition(R)),"session.setModel"===a.name){if(!e.messageService)throw new Error("Cloud runtime cannot project a personal session model");const t=e.scopedDeliveryKey;if(!t)throw new Error("session model requires X-Runtime-Delivery-Key");return{resources:C,sessionModelProjection:{project:async({conversationId:e,modelOverrideId:a})=>{const o=s(),i=new MessageServiceSessionModelHttpAuthority({messageServiceUrl:r,scopedDeliveryKey:t,fetchAuthorized:(e,r)=>fetchRuntimeLeaseAttempt(e,r??{})});return await i.project({conversationId:e,operationId:n.operationId,requestMessageId:n.requestMessageId,requestHash:n.requestHash,modelOverrideId:a,runtimeLeaseCredential:o.runtimeLeaseCredential})}}}}if("session.clear"!==a.name)return{resources:C};if(!e.messageService)throw new Error("Cloud runtime cannot access personal conversation history");a.context.conversation;const v=s(),y=new MessageServiceConversationClearHttpAuthority({messageServiceUrl:r,runtimeLeaseCredential:v.runtimeLeaseCredential,fetchAuthorized:(e,r)=>fetchRuntimeLeaseAttempt(e,r??{})});return{resources:C,historyBoundary:new MessageServiceConversationHistoryBoundary({operationId:n.operationId,requestMessageId:n.requestMessageId,requestHash:n.requestHash,fence:deliveryFence(n,v,i,c),authority:y})}}}}function runtimeMethods(){return[...intersectRuntimeServiceMethodsWithBackendCapabilities(RUNTIME_PARITY_MANIFEST.filter(e=>"ready"===e.status).map(e=>e.method))]}function contractArtifactRequirement(){try{return runtimeServiceContractRequirement()}catch{return CONTRACT_ARTIFACT_BLOCKER.requiredContract}}function parseSkillPackage(e){try{return parseAndValidateTemplatePackage(Buffer.from(e))}catch(r){if(!(r instanceof TemplatePackageError))throw r;throw new RuntimeResourceResolutionError("resolution_rejected","skill package failed validation",void 0,`package_invalid bytes=${e.byteLength} reason=${r.message}`)}}function deliveryFence(e,r,t,n){if(r.handlerIdentity!==e.runtimeHandlerIdentity||r.runtimeEpoch!==e.dispatchEpoch)throw new Error("runtime delivery fence does not match the retained request");return{claimId:e.requestMessageId,handlerIdentity:r.handlerIdentity,runtimeEpoch:r.runtimeEpoch,fencingToken:r.runtimeEpoch,journalStoreId:t,journalGeneration:n}}
@@ -0,0 +1 @@
1
+ import{createHash,timingSafeEqual}from"node:crypto";import{readFile}from"node:fs/promises";import{isDeepStrictEqual}from"node:util";import{centralNativeRuntime}from"./central-native-runtime.js";import{resolveSyncAgentName}from"../agent-sync.js";import{injectOpenClawCredentials}from"./sandbox-native-credentials.js";function unboundActivation(e){const{execution_binding_version:t,...i}=e;return{...i,request_hash:""}}let executionResolver;export function installSandboxNativeExecutionResolver(e){if(executionResolver)throw new Error("native original execution authority is already installed");executionResolver=e}export function createSandboxNativeTargetLookup(e,t){return async({localAgentId:i,activation:n})=>{const r=e(),a=r?.authorityLease,o=t();if(!r||!a?.runtimeLeaseCredential||Date.parse(a.leaseExpiresAt)<=Date.now()||a.instanceId!==n.sandbox_id||!o?.isConnected)throw new Error("current native target projection authority/catalog transport is unavailable");const s=await o.request("agents.list",{}),c=s.agents?.filter(e=>e.id===i)??[];if(1!==c.length)throw new Error("actual native target catalog entry is missing or ambiguous");if(resolveSyncAgentName(c[0])!==i)throw new Error("actual native catalog key differs from the current identity projection key");const l=await r.identities.resolveOrCreate(i),d=r.authorityLease;if(e()!==r||!d||Date.parse(d.leaseExpiresAt)<=Date.now()||d.instanceId!==a.instanceId||d.leaseId!==a.leaseId||d.runtimeEpoch!==a.runtimeEpoch||d.handlerIdentity!==a.handlerIdentity)throw new Error("native target projection authority changed during observation");return{runtimeAgentUid:l,runtimeLocalKey:i,runtimeEpoch:d.runtimeEpoch,leaseIdentity:JSON.stringify({instanceId:d.instanceId,leaseId:d.leaseId,runtimeEpoch:d.runtimeEpoch,handlerIdentity:d.handlerIdentity})}}}const object=e=>null!==e&&"object"==typeof e&&!Array.isArray(e),sha256=e=>createHash("sha256").update(e).digest("hex"),ref=e=>`native:${sha256(JSON.stringify(e))}`,nonempty=e=>"string"==typeof e&&e.length>0,secretRef=e=>object(e)&&3===Object.keys(e).length&&("env"===e.source||"file"===e.source||"exec"===e.source)&&nonempty(e.provider)&&nonempty(e.id);export function containsApplied(e,t){return secretRef(t)?isDeepStrictEqual(e,t)||nonempty(e):Array.isArray(t)?Array.isArray(e)&&e.length===t.length&&t.every((t,i)=>containsApplied(e[i],t)):object(t)?object(e)&&Object.entries(t).every(([t,i])=>containsApplied(e[t],i)):isDeepStrictEqual(e,t)}export function sessionConfigObservation(e){const{activation:t,allocation:i,native:n,execution:r,receipt:a}=e,o=n.config_components;if(!(object(o)&&object(r.loadAuthority)&&Number.isSafeInteger(i.epoch)&&nonempty(t.resolved_request_hash)&&nonempty(t.central_read_watermark)))throw new Error("actual SessionConfigReceipt inputs (components/authority/allocation/selection) are unavailable");const s=structuredClone(r.loadAuthority),c=i.id,l=r.sessionKey,d={session_load_receipt:e.sessionLoadReceipt,prepared:a.receipt_id},u=ref({config:a.receipt_id,session:l,authority:s}),p={contractVersion:"1",revision:t.config_revision,manifestRef:{id:t.config_manifest.manifest_id,digest:t.config_sha256},appliedAt:a.prepared_at,sessionId:t.session_id,applyAt:"session_start",components:structuredClone(o),binding:{bindingId:t.binding_id,agentId:t.agent_id,harnessId:t.harness,sandboxId:t.sandbox_id,allocationId:i.id,allocationEpoch:i.epoch},applicationState:"applied",authority:s,hostInstanceId:c,nativeSessionId:l,receiptId:u,mutationScope:"session_private"};return{resolvedRequestHash:t.resolved_request_hash,readWatermark:t.central_read_watermark,admission:{attemptId:r.binding.attemptId,hostInstanceId:c,executionBindingVersion:t.execution_binding_version,nativeSessionId:l,nativeIncarnation:`${process.pid}@${i.boot_id}`,requestHash:t.request_hash},sessionConfigReceipt:{receiptId:u,commandId:t.command_id,authority:s,hostInstanceId:c,nativeSessionId:l,configuration:p,evidenceRef:{id:e.sessionLoadReceipt,digest:sha256(JSON.stringify(d))},preparedConfigReceiptId:a.receipt_id},loadedFacts:{loaded_model_id:null,actual_provider_id:null,actual_model_id:null,endpoint_ref:null,api_mode:null,reasoning:null,fallback_used:null,skills_digest:null,tool_schema_digest:null,credential_activation_revision:a.credential_activation_revision,components:[],memory_observation:"not_read",memory_read_watermark:null}}}export class SandboxNativeActivation{config;activeWriters;activationFile;targetLookup;prepared;materialized;boundNativeRequestHash;targetAuthority;persona;credentialReceipt;control=Promise.resolve();serialize(e){const t=this.control.then(e,e);return this.control=t.catch(()=>{}),t}constructor(e,t,i,n){this.config=e,this.activeWriters=t,this.activationFile=i,this.targetLookup=n}prepare(e){return this.serialize(()=>this.apply(e))}target(e){return this.serialize(()=>this.apply(e,!0))}async assertEnvelope(e,t=!1){const i=JSON.parse(await readFile(this.activationFile,"utf8")),{activation:n,allocation:r,config_manifest:a}=e,o=!t&&"execution"===n.kind&&"execution"===i.activation?.kind&&""===i.activation.request_hash&&nonempty(n.request_hash)&&isDeepStrictEqual(unboundActivation(i.activation),unboundActivation(n));if(!isDeepStrictEqual(i.activation,n)&&!o||!isDeepStrictEqual(i.allocation,r)||!isDeepStrictEqual(i.config_manifest,a))throw new Error("native preparation does not match the current immutable activation");if(!nonempty(n.preparation_id)||!nonempty(r.id)||!nonempty(r.boot_id)||!Number.isFinite(Date.parse(n.deadline_at))||Date.parse(n.deadline_at)<=Date.now())throw new Error("native preparation authority/deadline is unavailable");if(!isDeepStrictEqual(JSON.parse(await readFile(e.config_manifest_path,"utf8")),a))throw new Error("native configuration bytes differ from the verified manifest");if("credential_activation"in a)throw new Error("private credential inputs must use transient authenticated delivery");if("central_chat"in a||"centralChat"in a)throw new Error("immutable native configuration must not contain per-execution chat authority")}credentials(e){return this.serialize(async()=>{if(await this.assertEnvelope(e),0!==this.activeWriters())throw new Error("native writers must drain before credential injection");if(this.materialized&&!this.credentialReceipt)throw new Error("credentials must be injected before native provider construction");const t=injectOpenClawCredentials({delivery:e.credential_delivery,activation:e.activation,allocation:e.allocation,previous:this.credentialReceipt,mcpCredentialSlots:e.config_manifest.mcp_credential_slots});return this.credentialReceipt=t,structuredClone(t)})}async apply(e,t=!1){await this.assertEnvelope(e,t);const{activation:i,allocation:n,config_manifest:r}=e;if(!this.credentialReceipt)throw new Error("actual native credential injection receipt is required, including empty credentials");if("execution"===i.kind){if(!nonempty(i.public_request_hash))throw new Error("original public request hash is required");if(t&&(""!==i.request_hash||void 0!==this.boundNativeRequestHash))throw new Error("target observation requires the original unbound native request hash");const e=i.execution_binding_version;if(t?void 0!==e&&0!==e:!(Number.isSafeInteger(e)&&e>=1))throw new Error("State execution binding version must be unbound at target and positive at Session load");if(!t&&(!nonempty(i.request_hash)||void 0!==this.boundNativeRequestHash&&this.boundNativeRequestHash!==i.request_hash))throw new Error("native canonical request hash is missing or changed")}const{execution_grant:a,credential_delivery:o,...s}=e,c={...s,activation:unboundActivation(i)};if(this.materialized&&!isDeepStrictEqual(this.materialized,c))throw new Error("a different native materialization requires a drained process restart");let l;if("execution"===i.kind&&!t){if(!nonempty(e.execution_grant)||!executionResolver)throw new Error("authenticated original canonical execution request producer is unavailable");if(l=await executionResolver(e),!this.targetAuthority||!this.targetLookup)throw new Error("the original work has no observed native target");const t=await this.targetLookup({localAgentId:r.local_agent_id,activation:i,allocation:n});if(!isDeepStrictEqual(t,this.targetAuthority))throw new Error("native target/current runtime lease changed before session load");if(l.nativeRequestHash!==i.request_hash||l.binding.sessionId!==i.session_id||l.binding.responseId!==i.response_id||l.binding.operationId!==i.operation_id||l.runtimeAgentUid!==this.targetAuthority.runtimeAgentUid||!l.sessionKey.startsWith(`agent:${r.local_agent_id}:`))throw new Error("original execution request differs from the current native activation");this.assertSelection(l.params,e)}if(!t&&this.prepared&&isDeepStrictEqual(this.prepared.envelope,s)){if(!isDeepStrictEqual(this.prepared.execution,l))throw new Error("original execution binding changed");return l&&await centralNativeRuntime().prepareReadOnly(l.sessionKey,l.binding),structuredClone(this.prepared.receipt)}if(0!==this.activeWriters())throw new Error("native writers must drain before applying configuration");if(!["configuration","credential_restart","execution"].includes(i.kind))throw new Error("unsupported native activation kind");if(1!==r.schema_version||!nonempty(r.local_agent_id)||!object(r.openclaw_config))throw new Error("resolved OpenClaw native configuration v1 is required");const d=r.mcp_credential_slots;if(void 0!==d&&(!Array.isArray(d)||d.some(e=>"string"!=typeof e||!/^[a-z][a-z0-9_]{0,63}$/.test(e)||"openai_api_key"===e)||new Set(d).size!==d.length))throw new Error("resolved MCP credential slots are invalid");const u=new Set(["agents","models","tools","skills","mcp"]);if(Object.keys(r.openclaw_config).some(e=>!u.has(e)))throw new Error("native config may only apply agent/model/tool/Skill/MCP settings");const p=r.openclaw_config.agents;if(!object(p)||!object(p.entries)||!object(p.entries[r.local_agent_id]))throw new Error("local_agent_id is not present in the resolved native agent catalog");if(this.prepared&&!isDeepStrictEqual(this.prepared.envelope,s))throw new Error("a different native activation requires a drained process restart");if(r.persona&&(!nonempty(r.persona.persona_id)||!nonempty(r.persona.revision)||!nonempty(r.persona.source_receipt_ref)||sha256(r.persona.content)!==r.persona.sha256))throw new Error("resolved Persona source/revision/content digest is invalid");if("execution"!==i.kind&&(i.session_id||i.response_id||i.execution_binding_version))throw new Error("non-execution preparation must not manufacture a Session or Response");if(t&&"execution"!==i.kind)throw new Error("target observation requires the original execution preparation");this.materialized||await this.config.mutateConfigFile({afterWrite:{mode:"auto"},mutate:e=>{for(const[t,i]of Object.entries(r.openclaw_config))e[t]=structuredClone(i)}});const h=this.config.current();if(!containsApplied(h,r.openclaw_config))throw new Error("native config has not been applied by OpenClaw");const f=h.agents;if(!object(f)||!object(f.entries)||!object(f.entries[r.local_agent_id]))throw new Error("local_agent_id is not present in the actual resolved native agent catalog");if(this.persona=r.persona?{localAgentId:r.local_agent_id,content:r.persona.content}:void 0,this.materialized=structuredClone(c),t){if(!this.targetLookup)throw new Error("actual native target identity producer is unavailable");const e=await this.targetLookup({localAgentId:r.local_agent_id,activation:i,allocation:n});if(!nonempty(e.runtimeAgentUid)||e.runtimeLocalKey!==r.local_agent_id||!/^[1-9][0-9]*$/.test(e.runtimeEpoch)||!nonempty(e.leaseIdentity))throw new Error("actual native catalog/identity projection does not match the applied target");return this.targetAuthority=structuredClone(e),{runtime_agent_uid:e.runtimeAgentUid,runtime_local_key:e.runtimeLocalKey,harness:"openclaw",config_digest:i.config_sha256,runtime_epoch:e.runtimeEpoch}}let g="";if("execution"===i.kind){if(!l)throw new Error("original central native execution authority is unavailable");this.boundNativeRequestHash=l.nativeRequestHash,await centralNativeRuntime().prepareReadOnly(l.sessionKey,l.binding),g=ref({session_key:l.sessionKey,binding:l.binding,source:centralNativeRuntime().source(l.sessionKey)})}const _=(new Date).toISOString(),v={preparation_id:i.preparation_id,kind:i.kind,binding_id:i.binding_id,config_revision:i.config_revision,config_sha256:i.config_sha256,target_intent_digest:i.target_intent_digest,authority_ref:i.authority_ref,config_manifest:i.config_manifest,credential_set_id:i.credential_set_id,credential_set_version:i.credential_set_version,credential_activation_revision:this.credentialReceipt.config.revision,receipt_scope:"configuration"===i.kind?"current_config":"activation",credential_activation_receipt_ref:this.credentialReceipt.receipt_id,host_instance_id:n.id,runtime_lineage_ref:n.boot_id,session_load_receipt_ref:g,prepared_at:_,..."execution"===i.kind?{execution_binding_version:i.execution_binding_version}:{},receipt_id:ref({activation:i,allocation:n,config:r.openclaw_config,persona:r.persona?.sha256??null,credential_receipt:this.credentialReceipt.receipt_id,sessionLoadReceipt:g})};return l&&centralNativeRuntime().bindExecutionObservation(l.binding,sessionConfigObservation({activation:i,allocation:n,native:r,execution:l,receipt:v,sessionLoadReceipt:g})),this.prepared={envelope:structuredClone(s),execution:l&&structuredClone(l),receipt:v},v}execution(e,t){const i=this.prepared,n=i?.envelope.config_manifest,r=i?.execution;if(!(i&&"execution"===i.envelope.activation.kind&&n&&r&&isDeepStrictEqual(r.binding,t)&&containsApplied(this.config.current(),n.openclaw_config)))throw new Error("central execution does not match actual applied native configuration/Persona/binding");if(this.assertSelection(e,i.envelope),!isDeepStrictEqual(e,r.params))throw new Error("central execution differs from the prepared original request");return{sessionKey:r.sessionKey,localAgentId:n.local_agent_id,systemPrompt:n.persona?.content??""}}assertSelection(e,t){const i=e.centralChat,n=t.config_manifest.persona;if(!object(i)||!this.credentialReceipt||!isDeepStrictEqual(i.config_pin,this.credentialReceipt.config)||!isDeepStrictEqual(i.persona_ref,n?{persona_id:n.persona_id,revision:n.revision}:null)||Object.hasOwn(i,"effective_config")||Object.hasOwn(i,"persona"))throw new Error("central selection differs from the actual prepared native configuration/Persona source")}promptContext(e){if(this.persona&&object(e)&&e.agentId===this.persona.localAgentId)return{appendSystemContext:this.persona.content}}drain(e){return this.serialize(()=>this.quiesce(e))}async quiesce(e){if(!(this.prepared&&isDeepStrictEqual(e.activation,this.prepared.envelope.activation)&&isDeepStrictEqual(e.allocation,this.prepared.envelope.allocation)&&nonempty(e.command_id)))throw new Error("native drain does not match the actual prepared activation");const t=await centralNativeRuntime().drain();if(1!==t.sourceWatermarks.length)throw new Error("Runtime drain contract requires one actual central session watermark; configuration-only quiescence is not representable");const i=t.sourceWatermarks[0].watermark,n=ref({command_id:e.command_id,prepared:this.prepared.receipt.receipt_id,allocation:e.allocation,sources:t.sourceWatermarks});return{receipt_id:ref({quiesce:n,watermark:i}),central_committed_watermark:i,pending_writes:0,all_writers_quiesced:!0,quiesce_ref:n}}}let controller;async function body(e){const t=[];let i=0;for await(const n of e){const e=Buffer.isBuffer(n)?n:Buffer.from(n);if(i+=e.length,i>4194304)throw new Error("native activation payload exceeds 4 MiB");t.push(e)}try{return JSON.parse(Buffer.concat(t).toString("utf8"))}catch{throw new Error("native control request is not valid JSON")}}export function registerSandboxNativeActivation(e,t,i){const n=process.env.BEEOS_SANDBOX_ACTIVATION_FILE;if(!n)return;const r=e.runtime?.config;if(!r?.current||!r.mutateConfigFile)throw new Error("pinned OpenClaw native config APIs are required for Sandbox activation");controller??=new SandboxNativeActivation(r,t,n,i);for(const t of["credentials","target","prepare","drain"])e.registerHttpRoute({path:`/beeos/sandbox-v1/${t}`,auth:"plugin",async handler(e,i){const n=e,r=i,a=(e,t)=>{r.writeHead(e,{"content-type":"application/json","cache-control":"no-store"}),r.end(JSON.stringify(t))};if("POST"!==n.method)return void a(405,{error:{code:"method_not_allowed"}});const o=n.socket.remoteAddress;if(["127.0.0.1","::1","::ffff:127.0.0.1"].includes(o??""))try{const e=process.env.BEEOS_SANDBOX_NATIVE_TOKEN_FILE;if(!e)throw new Error("native control token file is unavailable");const i=(await readFile(e,"utf8")).trim(),r=Buffer.from(`Bearer ${i}`),o=Buffer.from(n.headers.authorization??"");if(!i||o.length!==r.length||!timingSafeEqual(o,r))return void a(401,{error:{code:"native_control_unauthorized"}});const s=await body(n);if(!object(s))throw new Error("native control request must be an object");a(200,"credentials"===t?await controller.credentials(s):"target"===t?await controller.target(s):"prepare"===t?await controller.prepare(s):await controller.drain(s))}catch(e){a(503,{error:{code:"native_preparation_unavailable",message:e instanceof Error?e.message:String(e)}})}else a(403,{error:{code:"native_loopback_required"}})}})}export function preparedSandboxNativeExecution(e,t){if(!controller)throw new Error("actual native Sandbox activation is unavailable");return controller.execution(e,t)}
@@ -0,0 +1 @@
1
+ import{randomUUID}from"node:crypto";import{isDeepStrictEqual}from"node:util";export const OPENCLAW_CREDENTIAL_SLOTS=Object.freeze([Object.freeze({slot:"openai_api_key",required:!1,injection:Object.freeze({kind:"env",name:"OPENAI_API_KEY"})})]);const object=e=>null!==e&&"object"==typeof e&&!Array.isArray(e),text=e=>"string"==typeof e&&e.length>0,positive=e=>Number.isSafeInteger(e)&&Number(e)>0,keys=(e,t)=>Object.keys(e).every(e=>t.includes(e)),uuid=e=>"string"==typeof e&&/^[a-f0-9]{8}-[a-f0-9]{4}-[1-8][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/i.test(e);export function injectOpenClawCredentials(e){const{delivery:t,activation:i,allocation:n,previous:o}=e,a=new Set(e.mcpCredentialSlots??[]);if(a.size!==(e.mcpCredentialSlots?.length??0)||[...a].some(e=>!/^[a-z][a-z0-9_]{0,63}$/.test(e)||"openai_api_key"===e))throw new Error("MCP credential slots are invalid");const r=object(i.tenant)?i.tenant:{};if(!(object(t)&&keys(t,["config","scope","grants"])&&object(t.config)&&keys(t.config,["agent_id","revision","content_sha256","credential_set_ref"])&&[t.config.agent_id,t.config.revision,t.config.content_sha256].every(text)&&object(t.scope)&&Array.isArray(t.grants)&&t.config.agent_id===i.agent_id&&t.config.revision===i.config_revision&&t.config.content_sha256===i.config_sha256))throw new Error("credential configuration pin is not current");const c=t.config.credential_set_ref;if(i.credential_set_id||i.credential_set_version){if(!object(c)||!keys(c,["set_id","version"])||c.set_id!==i.credential_set_id||!positive(c.version)||String(c.version)!==i.credential_set_version)throw new Error("credential set pin is not current")}else if(null!==c)throw new Error("empty credentials require the actual absent set pin");const s=t.scope,l=s.control,d=Date.now();if(!(keys(s,["organization_id","app_id","harness_id","binding_id","sandbox_id","allocation_id","allocation_epoch","host_instance_id","control"])&&[s.organization_id,s.app_id,s.binding_id,s.sandbox_id,s.allocation_id,s.host_instance_id].every(text)&&s.organization_id===r.organization_id&&s.app_id===r.application_id&&s.harness_id===`chrn_${t.config.agent_id}`&&"openclaw"===i.harness&&s.binding_id===i.binding_id&&s.sandbox_id===i.sandbox_id&&s.allocation_id===n.id&&positive(s.allocation_epoch)&&s.allocation_epoch===n.epoch&&s.host_instance_id===n.boot_id&&object(l)&&keys(l,["phase","purpose","allocation_id","allocation_epoch","intent_revision","deadline_at","control_authority"])&&"activation"===l.phase&&"config_binding"===l.purpose&&l.allocation_id===s.allocation_id&&l.allocation_epoch===s.allocation_epoch&&positive(l.intent_revision)&&object(l.control_authority)&&keys(l.control_authority,["kind","operation_id","controller_fence","authorization_ref"])&&"operation"===l.control_authority.kind&&text(l.control_authority.operation_id)&&positive(l.control_authority.controller_fence)&&text(l.control_authority.authorization_ref)&&l.control_authority.authorization_ref===i.authority_ref&&Number.isFinite(Date.parse(l.deadline_at))&&!(Date.parse(l.deadline_at)<=d)&&!(Date.parse(l.deadline_at)>Date.parse(String(i.deadline_at)))))throw new Error("credential placement/control is not current");const _=new Map,p=[],f=new Set;for(const e of t.grants){const i=e?.grant,n=i?.material;if(!object(e)||!keys(e,["grant","value"])||!object(i)||!keys(i,["grant_id","config","scope","material","expires_at"])||!uuid(i.grant_id)||!isDeepStrictEqual(i.config,t.config)||!isDeepStrictEqual(i.scope,s)||!object(n)||!keys(n,["slot","source_version","material_version"])||!text(n.source_version)||!text(n.material_version)||!Number.isFinite(Date.parse(i.expires_at))||Date.parse(i.expires_at)<=d||"string"!=typeof e.value||0===e.value.length||e.value.includes("\0"))throw new Error("credential grant/material is unavailable");const o=OPENCLAW_CREDENTIAL_SLOTS.find(e=>e.slot===n.slot);if(f.has(n.slot)||!o&&!a.has(n.slot))throw new Error("credential slot is undeclared or duplicated");if(f.add(n.slot),o){if(_.has(o.injection.name))throw new Error("credential slot is undeclared or duplicated");_.set(o.injection.name,e.value)}p.push({grant_id:i.grant_id,material:structuredClone(n)})}const u={config:t.config,scope:s,injections:p};let g=!1;if(o){const e=e=>{const{control:t,...i}=e.scope;return{config:e.config,placement:i,materials:e.injections.map(e=>e.material)}};if(!isDeepStrictEqual(e(o),e(u)))throw new Error("changed credential material requires a drained fresh native process");const t=Date.parse(o.scope.control.deadline_at),i={...o.scope,control:{...o.scope.control,deadline_at:l.deadline_at}};g=t>d&&t<=Date.parse(l.deadline_at)&&isDeepStrictEqual({config:o.config,scope:i,injections:o.injections},u)}if(!g)for(const e of OPENCLAW_CREDENTIAL_SLOTS){const t=_.get(e.injection.name);void 0===t?delete process.env[e.injection.name]:process.env[e.injection.name]=t}for(const e of OPENCLAW_CREDENTIAL_SLOTS)if(process.env[e.injection.name]!==_.get(e.injection.name))throw new Error("native credential environment readback failed");return g?structuredClone(o):{receipt_id:randomUUID(),...structuredClone(u),injected_at:(new Date).toISOString()}}
@@ -0,0 +1 @@
1
+ import{verifyCentralNativeIntent}from"./central-native-response.js";import{centralNativeSessionKey}from"./central-session-key.js";export function createSandboxNativeExecutionResolver(e){return async t=>{const r=t.activation,i=e.authority.authorityLease;if(!i||i.instanceId!==r.sandbox_id||"string"!=typeof r.operation_id||"string"!=typeof r.request_message_id||!t.execution_grant)throw new Error("original native preparation lease/locator/grant is unavailable");const n=await e.original.read(i,{operation_id:r.operation_id,request_message_id:r.request_message_id,native_request_hash:r.request_hash});await e.verifier.refresh();const a=t=>verifyCentralNativeIntent({request:n.request,executionGrant:t,lease:i,getLease:()=>e.authority.authorityLease,verifier:e.verifier});a(t.execution_grant);const s=a(n.execution_grant),o=e.verifier.verifyAndDecodeGrant(n.execution_grant),u=e.verifier.verifyAndDecodeGrant(t.execution_grant),d=s.params,_=s.binding;if(n.public_request_hash!==r.public_request_hash||n.native_request_hash!==r.request_hash||n.request.requestMessageId!==r.request_message_id||_.operationId!==r.operation_id||_.sessionId!==r.session_id||_.responseId!==r.response_id||d.platformAgentId!==r.agent_id||d.runtimeRunId!==r.run_id||d.taskId!==r.operation_id||d.taskRequestMessageId!==r.request_message_id||String(_.context.authorization_revision)!==r.authorization_revision||"string"!=typeof o.claims.runtimeAgentUid||!o.claims.runtimeAgentUid||o.claims.runtimeAgentUid!==u.claims.runtimeAgentUid||1e3*o.claims.exp!==Date.parse(n.execution_grant_expires_at))throw new Error("original native carrier differs from the current activation");const c=n.request,l=d.runtimeLease;if(!l||"string"!=typeof l.journalStoreId||!l.journalStoreId||"string"!=typeof l.journalGeneration||!l.journalGeneration)throw new Error("original native carrier has no runtime journal lineage");const g={kind:"runtime",executionMode:"native_delivery",instanceId:c.instanceId,leaseId:c.leaseId,handlerIdentity:c.runtimeHandlerIdentity,journalStoreId:l.journalStoreId,operationId:c.operationId,requestMessageId:c.requestMessageId,runtimeEpoch:c.dispatchEpoch,journalGeneration:l.journalGeneration,requestHash:n.native_request_hash,executionGrantHash:c.executionGrantHash};return s.assertCurrent(),e.bindCredential(_,n.execution_grant,r.request_hash,{requestMessageId:r.request_message_id,verifier:e.verifier}),{sessionKey:centralNativeSessionKey(t.config_manifest.local_agent_id,_.sessionId),binding:_,nativeRequestHash:n.native_request_hash,params:s.params,runtimeAgentUid:o.claims.runtimeAgentUid,loadAuthority:g}}}
@@ -1 +1 @@
1
- export const SESSION_RECOVERY_REQUIRED_CODE="session_recovery_required";export const SESSION_RECOVERY_REQUIRED_MESSAGE="The session could not be proven safe for another write. Recovery is required.";const DEFAULT_OBSERVATION_TIMEOUT_MS=13e4,DEFAULT_CANCELLATION_QUIESCENCE_TIMEOUT_MS=3e4,DEFAULT_CANCELLATION_TERMINAL_OBSERVATION_TIMEOUT_MS=3e4;export class SessionMutationCoordinator{gate;quiescence;logger;states=new Map;observationTimeoutMs;cancellationQuiescenceTimeoutMs;cancellationTerminalObservationTimeoutMs;setTimer;clearTimer;onRecoveryRequired;constructor(e,r,o,t={}){this.gate=e,this.quiescence=r,this.logger=o,this.observationTimeoutMs=t.observationTimeoutMs??13e4,this.cancellationQuiescenceTimeoutMs=t.cancellationQuiescenceTimeoutMs??3e4,this.cancellationTerminalObservationTimeoutMs=t.cancellationTerminalObservationTimeoutMs??3e4,this.setTimer=t.setTimer??((e,r)=>{const o=setTimeout(e,r);return o.unref?.(),o}),this.clearTimer=t.clearTimer??(e=>clearTimeout(e)),this.onRecoveryRequired=t.onRecoveryRequired}tryStartInteractive(e,r){const o=this.gate.tryStartInteractive(e);return o.ok&&this.register(o.token,r,"attachable"),o}tryStartExclusive(e,r){const o=this.gate.tryStartInteractive(e);return o.ok&&this.register(o.token,r,"exclusive"),o}async acquireInternal(e){const r=await this.gate.acquireInternal(e);return this.register(r,"internal","attachable"),r}bindRun(e,r){if(!e||!r)return;const o=this.states.get(e.operationId);o&&"recovery_required"!==o.phase&&(o.runId=r)}attachOrStartExternal(e,r,o){const t=this.stateForSession(e.sessionKey);if(t)return t.recovery?{ok:!1,reason:"session_busy",recovery:t.recovery}:"exclusive"===t.attachmentPolicy?{ok:!1,reason:"session_busy"}:t.runId&&o&&t.runId!==o?{ok:!1,reason:"run_conflict",recovery:this.fence(t,"run_conflict")}:(!t.runId&&o&&(t.runId=o),{ok:!0,ownership:"observer",ownerOperationId:t.token.operationId,attached:!0});const n=this.tryStartInteractive(e,r);return n.ok?(o&&this.bindRun(n.token,o),{ok:!0,ownership:"owner",token:n.token,attached:!1}):{ok:!1,reason:n.reason}}requestCancel(e,r){const o=this.requireState(e);if(o.recovery)return Promise.resolve({ok:!1,recovery:o.recovery});if(o.cancelPromise)return o.cancelPromise;if(o.terminalKind||o.convergencePromise)return Promise.resolve({ok:!1,reason:"terminal_already_observed"});o.phase="cancelling",o.cancelObservationPromise=new Promise(e=>{o.resolveCancelObservation=e});const t=o.cancelObservationPromise;o.cancelObservationTimer=this.setTimer(()=>{o.terminalKind||o.recovery||this.fence(o,"terminal_not_observed")},this.cancellationTerminalObservationTimeoutMs);const n=Promise.resolve().then(()=>r.chatAbort(e.sessionKey,o.runId)).then(r=>(r.aborted?(o.cancelAcknowledged=!0,this.logger.debug?.(`[session-mutation] remote abort acknowledged sessionKey=${e.sessionKey} operationId=${e.operationId} runId=${o.runId??""} returnedRunIds=${r.runIds.join(",")}`)):this.logger.debug?.(`[session-mutation] remote abort not acknowledged; continuing terminal/quiescence convergence sessionKey=${e.sessionKey} operationId=${e.operationId} runId=${o.runId??""} returnedRunIds=${r.runIds.join(",")}`),o.recovery?{ok:!1,recovery:o.recovery}:{ok:!0,acknowledged:r.aborted,terminalObservation:t})).catch(r=>(this.logger.error?.(`[session-mutation] remote abort transport failed; continuing terminal/quiescence convergence sessionKey=${e.sessionKey} operationId=${e.operationId} runId=${o.runId??""} error=${r instanceof Error?r.message:String(r)}`),o.recovery?{ok:!1,recovery:o.recovery}:{ok:!0,acknowledged:!1,terminalObservation:t}));return o.cancelPromise=n,n}requestCancelForSession(e,r){const o=this.stateForSession(e);return o?"exclusive"===o.attachmentPolicy?Promise.resolve({ok:!1,reason:"not_cancellable"}):this.requestCancel(o.token,r):Promise.resolve({ok:!1,reason:"no_active_mutation"})}converge(e,r){const o=this.requireState(e);return o.convergencePromise||(o.terminalKind=r,this.resolveTerminalObserved(o),o.convergencePromise=this.proveConvergence(o)),o.convergencePromise}release(e){const r=this.states.get(e.operationId);r&&r.token.sessionKey===e.sessionKey&&"ready_to_release"===r.phase&&(this.clearCancellationTimer(r),this.gate.release(r.token),this.states.delete(e.operationId))}releaseBeforeRuntime(e){if(!e)return;const r=this.states.get(e.operationId);r?.recovery||(r&&this.clearCancellationTimer(r),this.gate.release(e),this.states.delete(e.operationId))}requireRecovery(e,r,o){const t=this.requireState(e);return o&&(t.terminalKind=o),this.fence(t,r)}snapshot(e){for(const r of this.states.values())if(r.token.sessionKey===e)return{sessionKey:e,operationId:r.token.operationId,surface:r.surface,attachmentPolicy:r.attachmentPolicy,phase:r.phase,...r.runId?{runId:r.runId}:{},...r.terminalKind?{terminalKind:r.terminalKind}:{},cancelAcknowledged:r.cancelAcknowledged,...r.recovery?{recovery:r.recovery}:{}}}gateSnapshot(e){return this.gate.snapshot(e)}fenceActive(e){for(const r of this.states.values())r.recovery||this.fence(r,e)}register(e,r,o){this.states.set(e.operationId,{token:e,surface:r,attachmentPolicy:o,phase:"active",cancelAcknowledged:!1})}requireState(e){const r=this.states.get(e.operationId);if(!r||r.token.sessionKey!==e.sessionKey)throw new Error("session_mutation_not_registered");return r}stateForSession(e){for(const r of this.states.values())if(r.token.sessionKey===e)return r}async proveConvergence(e){if(e.recovery)return{ok:!1,recovery:e.recovery};if(!e.terminalKind)return{ok:!1,recovery:this.fence(e,"terminal_not_observed")};if(e.recovery)return{ok:!1,recovery:e.recovery};e.phase="awaiting_quiescence";const r="cancelled"===e.terminalKind?this.cancellationQuiescenceTimeoutMs:this.observationTimeoutMs;let o;try{o=await this.quiescence.waitForQuiescence(e.token.sessionKey,r)}catch{return e.recovery?{ok:!1,recovery:e.recovery}:{ok:!1,recovery:this.fence(e,"quiescence_failed")}}if(e.recovery)return{ok:!1,recovery:e.recovery};if(o.quiescent||"session_unresolved"!==o.reason){if(!o.quiescent)return{ok:!1,recovery:this.fence(e,"quiescence_timeout",o)}}else this.logger.warn?.(`[session-mutation] session store unresolved; releasing without writer proof sessionKey=${e.token.sessionKey} operationId=${e.token.operationId} terminal=${e.terminalKind} elapsed_ms=${o.elapsedMs}`);return e.phase="ready_to_release",this.logger.debug?.(`[session-mutation] convergence proven sessionKey=${e.token.sessionKey} operationId=${e.token.operationId} terminal=${e.terminalKind} elapsed_ms=${o.elapsedMs}`),{ok:!0,permit:{sessionKey:e.token.sessionKey,operationId:e.token.operationId}}}fence(e,r,o){if(e.recovery)return e.recovery;const t={code:"session_recovery_required",sessionKey:e.token.sessionKey,operationId:e.token.operationId,reason:r,surface:e.surface,...e.terminalKind?{terminalKind:e.terminalKind}:{},...o?{quiescence:o}:{}};e.recovery=t,e.phase="recovery_required",this.clearCancellationTimer(e),e.resolveCancelObservation?.({ok:!1,recovery:t}),e.resolveCancelObservation=void 0,this.logger.error?.(`[session-mutation] recovery required sessionKey=${t.sessionKey} operationId=${t.operationId} reason=${t.reason} surface=${t.surface}`);try{this.onRecoveryRequired?.(t)}catch(e){this.logger.error?.(`[session-mutation] recovery restart scheduling failed sessionKey=${t.sessionKey} operationId=${t.operationId} error=${e instanceof Error?e.message:String(e)}`)}return t}resolveTerminalObserved(e){e.recovery||(this.clearCancellationTimer(e),e.resolveCancelObservation?.({ok:!0,terminalObserved:!0}),e.resolveCancelObservation=void 0)}clearCancellationTimer(e){e.cancelObservationTimer&&(this.clearTimer(e.cancelObservationTimer),e.cancelObservationTimer=void 0)}}
1
+ export const SESSION_RECOVERY_REQUIRED_CODE="session_recovery_required";export const SESSION_RECOVERY_REQUIRED_MESSAGE="The session could not be proven safe for another write. Recovery is required.";const DEFAULT_OBSERVATION_TIMEOUT_MS=13e4,DEFAULT_CANCELLATION_QUIESCENCE_TIMEOUT_MS=3e4,DEFAULT_CANCELLATION_TERMINAL_OBSERVATION_TIMEOUT_MS=3e4;export class SessionMutationCoordinator{gate;quiescence;logger;states=new Map;get activeMutationCount(){return this.states.size}observationTimeoutMs;cancellationQuiescenceTimeoutMs;cancellationTerminalObservationTimeoutMs;setTimer;clearTimer;onRecoveryRequired;constructor(e,r,t,o={}){this.gate=e,this.quiescence=r,this.logger=t,this.observationTimeoutMs=o.observationTimeoutMs??13e4,this.cancellationQuiescenceTimeoutMs=o.cancellationQuiescenceTimeoutMs??3e4,this.cancellationTerminalObservationTimeoutMs=o.cancellationTerminalObservationTimeoutMs??3e4,this.setTimer=o.setTimer??((e,r)=>{const t=setTimeout(e,r);return t.unref?.(),t}),this.clearTimer=o.clearTimer??(e=>clearTimeout(e)),this.onRecoveryRequired=o.onRecoveryRequired}tryStartInteractive(e,r){const t=this.gate.tryStartInteractive(e);return t.ok&&this.register(t.token,r,"attachable"),t}tryStartExclusive(e,r){const t=this.gate.tryStartInteractive(e);return t.ok&&this.register(t.token,r,"exclusive"),t}async acquireInternal(e){const r=await this.gate.acquireInternal(e);return this.register(r,"internal","attachable"),r}bindRun(e,r){if(!e||!r)return;const t=this.states.get(e.operationId);t&&"recovery_required"!==t.phase&&(t.runId=r)}attachOrStartExternal(e,r,t){const o=this.stateForSession(e.sessionKey);if(o)return o.recovery?{ok:!1,reason:"session_busy",recovery:o.recovery}:"exclusive"===o.attachmentPolicy?{ok:!1,reason:"session_busy"}:o.runId&&t&&o.runId!==t?{ok:!1,reason:"run_conflict",recovery:this.fence(o,"run_conflict")}:(!o.runId&&t&&(o.runId=t),{ok:!0,ownership:"observer",ownerOperationId:o.token.operationId,attached:!0});const n=this.tryStartInteractive(e,r);return n.ok?(t&&this.bindRun(n.token,t),{ok:!0,ownership:"owner",token:n.token,attached:!1}):{ok:!1,reason:n.reason}}requestCancel(e,r){const t=this.requireState(e);if(t.recovery)return Promise.resolve({ok:!1,recovery:t.recovery});if(t.cancelPromise)return t.cancelPromise;if(t.terminalKind||t.convergencePromise)return Promise.resolve({ok:!1,reason:"terminal_already_observed"});t.phase="cancelling",t.cancelObservationPromise=new Promise(e=>{t.resolveCancelObservation=e});const o=t.cancelObservationPromise;t.cancelObservationTimer=this.setTimer(()=>{t.terminalKind||t.recovery||this.fence(t,"terminal_not_observed")},this.cancellationTerminalObservationTimeoutMs);const n=Promise.resolve().then(()=>r.chatAbort(e.sessionKey,t.runId)).then(r=>(r.aborted?(t.cancelAcknowledged=!0,this.logger.debug?.(`[session-mutation] remote abort acknowledged sessionKey=${e.sessionKey} operationId=${e.operationId} runId=${t.runId??""} returnedRunIds=${r.runIds.join(",")}`)):this.logger.debug?.(`[session-mutation] remote abort not acknowledged; continuing terminal/quiescence convergence sessionKey=${e.sessionKey} operationId=${e.operationId} runId=${t.runId??""} returnedRunIds=${r.runIds.join(",")}`),t.recovery?{ok:!1,recovery:t.recovery}:{ok:!0,acknowledged:r.aborted,terminalObservation:o})).catch(r=>(this.logger.error?.(`[session-mutation] remote abort transport failed; continuing terminal/quiescence convergence sessionKey=${e.sessionKey} operationId=${e.operationId} runId=${t.runId??""} error=${r instanceof Error?r.message:String(r)}`),t.recovery?{ok:!1,recovery:t.recovery}:{ok:!0,acknowledged:!1,terminalObservation:o}));return t.cancelPromise=n,n}requestCancelForSession(e,r){const t=this.stateForSession(e);return t?"exclusive"===t.attachmentPolicy?Promise.resolve({ok:!1,reason:"not_cancellable"}):this.requestCancel(t.token,r):Promise.resolve({ok:!1,reason:"no_active_mutation"})}converge(e,r){const t=this.requireState(e);return t.convergencePromise||(t.terminalKind=r,this.resolveTerminalObserved(t),t.convergencePromise=this.proveConvergence(t)),t.convergencePromise}release(e){const r=this.states.get(e.operationId);r&&r.token.sessionKey===e.sessionKey&&"ready_to_release"===r.phase&&(this.clearCancellationTimer(r),this.gate.release(r.token),this.states.delete(e.operationId))}releaseBeforeRuntime(e){if(!e)return;const r=this.states.get(e.operationId);r?.recovery||(r&&this.clearCancellationTimer(r),this.gate.release(e),this.states.delete(e.operationId))}requireRecovery(e,r,t){const o=this.requireState(e);return t&&(o.terminalKind=t),this.fence(o,r)}snapshot(e){for(const r of this.states.values())if(r.token.sessionKey===e)return{sessionKey:e,operationId:r.token.operationId,surface:r.surface,attachmentPolicy:r.attachmentPolicy,phase:r.phase,...r.runId?{runId:r.runId}:{},...r.terminalKind?{terminalKind:r.terminalKind}:{},cancelAcknowledged:r.cancelAcknowledged,...r.recovery?{recovery:r.recovery}:{}}}gateSnapshot(e){return this.gate.snapshot(e)}fenceActive(e){for(const r of this.states.values())r.recovery||this.fence(r,e)}register(e,r,t){this.states.set(e.operationId,{token:e,surface:r,attachmentPolicy:t,phase:"active",cancelAcknowledged:!1})}requireState(e){const r=this.states.get(e.operationId);if(!r||r.token.sessionKey!==e.sessionKey)throw new Error("session_mutation_not_registered");return r}stateForSession(e){for(const r of this.states.values())if(r.token.sessionKey===e)return r}async proveConvergence(e){if(e.recovery)return{ok:!1,recovery:e.recovery};if(!e.terminalKind)return{ok:!1,recovery:this.fence(e,"terminal_not_observed")};if(e.recovery)return{ok:!1,recovery:e.recovery};e.phase="awaiting_quiescence";const r="cancelled"===e.terminalKind?this.cancellationQuiescenceTimeoutMs:this.observationTimeoutMs;let t;try{t=await this.quiescence.waitForQuiescence(e.token.sessionKey,r)}catch{return e.recovery?{ok:!1,recovery:e.recovery}:{ok:!1,recovery:this.fence(e,"quiescence_failed")}}if(e.recovery)return{ok:!1,recovery:e.recovery};if(t.quiescent||"session_unresolved"!==t.reason){if(!t.quiescent)return{ok:!1,recovery:this.fence(e,"quiescence_timeout",t)}}else this.logger.warn?.(`[session-mutation] session store unresolved; releasing without writer proof sessionKey=${e.token.sessionKey} operationId=${e.token.operationId} terminal=${e.terminalKind} elapsed_ms=${t.elapsedMs}`);return e.phase="ready_to_release",this.logger.debug?.(`[session-mutation] convergence proven sessionKey=${e.token.sessionKey} operationId=${e.token.operationId} terminal=${e.terminalKind} elapsed_ms=${t.elapsedMs}`),{ok:!0,permit:{sessionKey:e.token.sessionKey,operationId:e.token.operationId}}}fence(e,r,t){if(e.recovery)return e.recovery;const o={code:"session_recovery_required",sessionKey:e.token.sessionKey,operationId:e.token.operationId,reason:r,surface:e.surface,...e.terminalKind?{terminalKind:e.terminalKind}:{},...t?{quiescence:t}:{}};e.recovery=o,e.phase="recovery_required",this.clearCancellationTimer(e),e.resolveCancelObservation?.({ok:!1,recovery:o}),e.resolveCancelObservation=void 0,this.logger.error?.(`[session-mutation] recovery required sessionKey=${o.sessionKey} operationId=${o.operationId} reason=${o.reason} surface=${o.surface}`);try{this.onRecoveryRequired?.(o)}catch(e){this.logger.error?.(`[session-mutation] recovery restart scheduling failed sessionKey=${o.sessionKey} operationId=${o.operationId} error=${e instanceof Error?e.message:String(e)}`)}return o}resolveTerminalObserved(e){e.recovery||(this.clearCancellationTimer(e),e.resolveCancelObservation?.({ok:!0,terminalObserved:!0}),e.resolveCancelObservation=void 0)}clearCancellationTimer(e){e.cancelObservationTimer&&(this.clearTimer(e.cancelObservationTimer),e.cancelObservationTimer=void 0)}}
@@ -1 +1 @@
1
- import{TERMINAL_MAX_FRAME_BYTES,TERMINAL_SERVICE_PROTOCOL_VERSION}from"@beeos-ai/beeos-types/runtime";import{TerminalNotFoundError}from"./session-manager.js";import{getOrCreateRuntimeTerminalService}from"./runtime-terminal-service.js";import{TerminalAuthorityAdmission}from"./terminal-authority-admission.js";const DEFAULT_REPLAY_RETENTION_MS=3e5,DEFAULT_LEASE_RECOVERY_GRACE_MS=3e5;export class RuntimeTerminalOutboundRegistration{options;service;authorityAdmission;serviceGeneration;rings=new Map;ringBytes=new Map;droppedThrough=new Map;pending=[];pendingBytes=0;activeSend;activeFlush;flushes=new Set;connected=!1;handlerIdentity;draining=!1;stopped=!1;eventGeneration=0;transportEpoch=0;drainPromise;closePromise;replayExpiryTimers=new Map;constructor(e){this.options=e,this.service=e.service??getOrCreateRuntimeTerminalService(e.manager),this.authorityAdmission=new TerminalAuthorityAdmission(e.authorityJournal,e.authorityClock),this.serviceGeneration=this.service.setOutbound(this);const t=this.eventGeneration;e.client.on("connected",(...i)=>{if(!this.acceptsEvent(t))return;if(this.connected)return;this.transportEpoch++,this.activeFlush=void 0,this.serviceGeneration??=this.service.setOutbound(this);const n=i[0]?.handlerIdentity;n&&(this.handlerIdentity=n,this.service.closeParkedServiceSessions("runtime_lease_lost",n)),this.connected=!0,this.publishAvailability(!0),this.kickFlush(),e.logger?.info?.("[terminal-service] outbound registration connected")}),e.client.on("disconnected",()=>{this.acceptsEvent(t)&&this.invalidateConnection("transport")}),e.client.on("message",(...e)=>{if(!this.acceptsEvent(t))return;const i=this.serviceGeneration,n=this.transportEpoch;if(!i||!this.acceptsTransport(n,i))return;const r=e[0],s="string"==typeof r?r:Buffer.from(r).toString("utf8");this.handleWire(s,i,n)}),e.client.on("error",(...i)=>{this.acceptsEvent(t)&&e.logger?.warn?.("[terminal-service] outbound registration error",i[0])}),e.client.on("authorityLost",(...i)=>{if(this.acceptsEvent(t)){if(!0===i[0]?.recoverable)return this.invalidateConnection("park"),void e.logger?.warn?.("[terminal-service] runtime lease lost; Service terminals parked for recovery");this.invalidateConnection("fence"),e.logger?.warn?.("[terminal-service] runtime lease lost; Service terminals fenced")}}),e.client.on("transportEpochChanged",()=>{this.acceptsEvent(t)&&(this.invalidateConnection("park"),e.logger?.info?.("[terminal-service] runtime lease rotated; prior Terminal epoch parked for resume"))})}async start(){if(this.stopped)throw new Error("terminal outbound registration is stopped");await this.options.client.connect()}isAvailable(){return this.connected&&!this.draining&&!this.stopped}send(e){if(this.stopped)return!1;const t=JSON.stringify(e),i=Buffer.byteLength(t);return this.remember(e,t,i),!this.connected||(this.enqueueWithOutputBackpressure({clientId:e.clientId,terminalId:e.terminalId,frame:e,wire:t,bytes:i}),this.kickFlush(),!0)}resume(e,t,i){const n=this.transportEpoch;if(!this.acceptsConnection(n))throw new Error("terminal outbound registration is disconnected");const r=BigInt(i),s=this.droppedThrough.get(t)??0n,a=r<s;let o=[],c=-1n;for(const i of this.rings.get(t)??[]){if(i.frame.clientId!==e)throw new Error("terminal resume ownership mismatch");const n=BigInt(i.frame.sequence);if(n<=c)throw new Error("terminal replay sequence is not monotonic");c=n,n<=r||o.push({clientId:e,terminalId:t,frame:i.frame,wire:i.wire,bytes:i.bytes})}let h=a?s:void 0;if(0n===r||a){const e=this.options.maxResumeReplayOutputBytes??262144;let t=0,i=o.length;for(;i>0&&(t+=decodedOutputBytes(o[i-1].frame),!(t>e));)i--;i>0&&(h=BigInt(o[i-1].frame.sequence),o=o.slice(i))}if(!this.acceptsConnection(n))throw new Error("terminal outbound registration epoch changed");let d=0;for(const i of this.pending)i.terminalId!==t||i.clientId!==e?this.pending[d++]=i:this.pendingBytes-=i.bytes;if(this.pending.length=d,void 0!==h){const i={protocolVersion:TERMINAL_SERVICE_PROTOCOL_VERSION,type:"terminal.output",clientId:e,terminalId:t,sequence:String(h),stream:"stdout",data:"",encoding:"base64",truncated:!0},n=JSON.stringify(i);this.enqueueWithOutputBackpressure({clientId:e,terminalId:t,frame:i,wire:n,bytes:Buffer.byteLength(n)})}for(const e of o)this.enqueueWithOutputBackpressure(e);return this.kickFlush(),{gap:a,truncated:void 0!==h}}rebindResumeClient(e,t,i){if(e===t)return;const n=this.rings.get(i);if(n?.some(t=>t.frame.clientId!==e))throw new Error("terminal resume ownership mismatch");if(n){let e=0;const r=n.map(i=>{const n=Object.freeze({...i.frame,clientId:t}),r=JSON.stringify(n),s=Buffer.byteLength(r);return e+=s,{frame:n,wire:r,bytes:s}});this.rings.set(i,r),this.ringBytes.set(i,e)}for(let n=0;n<this.pending.length;n++){const r=this.pending[n];if(r.terminalId!==i||r.clientId!==e)continue;const s={...r.frame,clientId:t},a=JSON.stringify(s),o=Buffer.byteLength(a);this.pendingBytes+=o-r.bytes,this.pending[n]={clientId:t,terminalId:i,frame:s,wire:a,bytes:o}}if(this.activeSend?.item.terminalId===i&&this.activeSend.item.clientId===e){const e=this.activeSend.item,n={...e.frame,clientId:t},r=JSON.stringify(n),s=Buffer.byteLength(r);this.activeSend.counted&&(this.pendingBytes+=s-e.bytes),this.activeSend={item:{clientId:t,terminalId:i,frame:n,wire:r,bytes:s},counted:this.activeSend.counted}}}forgetTerminal(e,t){this.prepareForgetTerminal(e,t)()}prepareForgetTerminal(e,t){const i=this.rings.get(t),n=new Set(this.pending.filter(e=>e.terminalId===t)),r=this.activeSend?.item.terminalId===t&&this.activeSend.counted?this.activeSend:void 0;if(i?.some(t=>t.frame.clientId!==e)||[...n].some(t=>t.clientId!==e)||r&&r.item.clientId!==e)throw new Error("terminal forget ownership mismatch");const s=this.replayExpiryTimers.get(t);let a=!1;return()=>{if(a)return;a=!0,s&&this.replayExpiryTimers.get(t)===s&&(clearTimeout(s),this.replayExpiryTimers.delete(t)),i&&this.rings.get(t)===i&&(this.rings.delete(t),this.ringBytes.delete(t),this.droppedThrough.delete(t));let e=0;for(const t of this.pending)n.has(t)?this.pendingBytes-=t.bytes:this.pending[e++]=t;this.pending.length=e,r&&this.activeSend===r&&r.counted&&(r.counted=!1,this.pendingBytes-=r.item.bytes),this.pendingBytes=Math.max(0,this.pendingBytes)}}startTerminalReplayExpiry(e,t){if((this.rings.get(t)??[]).some(t=>t.frame.clientId!==e))throw new Error("terminal replay expiry ownership mismatch");this.scheduleReplayExpiry(e,t)}drain(e){return this.drainPromise??=this.drainOnce(e),this.drainPromise}async drainOnce(e){this.draining=!0,this.stopped=!0,this.eventGeneration++;const t=[...this.flushes];this.invalidateConnection("fence");const i=await waitBounded(this.options.manager.drain(e.timeoutMs,e.terminateAfterTimeout),e.timeoutMs,e.signal),n=i===interrupted?"terminated":i;return i!==interrupted&&"terminated"===n&&e.terminateAfterTimeout&&await waitBounded(this.options.manager.drain(Math.min(1e3,Math.max(50,e.timeoutMs)),!1),Math.min(1e3,Math.max(50,e.timeoutMs)),e.signal),await waitBounded(Promise.allSettled(t),e.timeoutMs,e.signal),await waitBounded(this.closeClientOnce(),e.timeoutMs,e.signal),this.clearReplayState(),n}acceptsEvent(e){return!this.stopped&&e===this.eventGeneration}async handleWire(e,t,i){if(!this.acceptsTransport(i,t))return;if(Buffer.byteLength(e)>TERMINAL_MAX_FRAME_BYTES)return void this.options.logger?.warn?.("[terminal-service] oversized client frame rejected");let n,r;try{n=JSON.parse(e)}catch{return void this.options.logger?.warn?.("[terminal-service] invalid client JSON")}try{r=await this.authorityAdmission.admit(n),await this.service.handleClientFrame(r.frame,t,r.admission)}catch(e){if(this.acceptsTransport(i,t)){r&&this.sendRejectedFrame(r.frame,e);const t=e instanceof Error?e.message:String(e);this.options.logger?.warn?.(`[terminal-service] client frame rejected: ${t}`,e)}}}sendRejectedFrame(e,t){const i=(t instanceof Error?t.message:String(t)).toLowerCase(),n=t instanceof TerminalNotFoundError||i.includes("not found")?"TERMINAL_NOT_FOUND":i.includes("quota")?"TERMINAL_QUOTA_EXCEEDED":i.includes("unauthorized")||i.includes("authority")?"UNAUTHORIZED_TARGET":"TERMINAL_IO_ERROR",r=this.rings.get(e.terminalId),s="TERMINAL_NOT_FOUND"!==n&&this.options.manager.getSession(e.terminalId)?.sessionId===e.clientId&&(!r||r.every(t=>t.frame.clientId===e.clientId)),a=s?this.options.manager.getSessionIncarnation(e.terminalId):void 0,o=s||"terminal.resume"!==e.type?a?BigInt(this.options.manager.nextServerSequence(e.terminalId,a)):1n:BigInt(e.lastServerSequence)+1n,c={protocolVersion:TERMINAL_SERVICE_PROTOCOL_VERSION,type:"terminal.error",clientId:e.clientId,terminalId:e.terminalId,sequence:String(o),code:n,message:n,recoverable:"TERMINAL_IO_ERROR"===n||"TERMINAL_QUOTA_EXCEEDED"===n};if(!s){const t=JSON.stringify(c);return this.enqueueWithOutputBackpressure({clientId:e.clientId,terminalId:e.terminalId,frame:c,wire:t,bytes:Buffer.byteLength(t)}),void this.kickFlush()}this.send(c)}remember(e,t,i){const n=this.rings.get(e.terminalId)??[];n.push({frame:Object.freeze({...e}),wire:t,bytes:i});let r=(this.ringBytes.get(e.terminalId)??0)+i;const s=this.options.maxBufferedFramesPerTerminal??512,a=this.options.maxBufferedBytesPerTerminal??1048576;for(;n.length>s||r>a;){const t=n.shift();if(!t)break;r-=t.bytes,this.droppedThrough.set(e.terminalId,BigInt(t.frame.sequence))}this.rings.set(e.terminalId,n),this.ringBytes.set(e.terminalId,r),"terminal.exit"===e.type&&this.scheduleReplayExpiry(e.clientId,e.terminalId)}enqueueWithOutputBackpressure(e){const t=this.options.maxPendingSendBytes??1048576;if(this.pendingBytes+e.bytes<=t)return this.pending.push(e),void(this.pendingBytes+=e.bytes);if("terminal.output"===e.frame.type){if(this.hasPendingTruncation(e.terminalId))return;const i=this.toTruncationMarker(e);return this.compactPendingOutput(i.bytes,t),void(this.hasPendingTruncation(e.terminalId)||(this.pending.push(i),this.pendingBytes+=i.bytes))}this.compactPendingOutput(e.bytes,t),this.pending.push(e),this.pendingBytes+=e.bytes}compactPendingOutput(e,t){for(;this.pendingBytes+e>t;){const e=this.pending.findIndex(e=>"terminal.output"===e.frame.type&&!0!==e.frame.truncated);if(e<0)return;const t=this.pending[e];if(this.hasPendingTruncation(t.terminalId,t)){this.pending.splice(e,1),this.pendingBytes-=t.bytes;continue}const i=this.toTruncationMarker(t);this.pending[e]=i,this.pendingBytes+=i.bytes-t.bytes}}hasPendingTruncation(e,t){return this.activeSend?.item!==t&&this.activeSend?.item.terminalId===e&&"terminal.output"===this.activeSend.item.frame.type&&!0===this.activeSend.item.frame.truncated||this.pending.some(i=>i!==t&&i.terminalId===e&&"terminal.output"===i.frame.type&&!0===i.frame.truncated)}toTruncationMarker(e){if("terminal.output"!==e.frame.type)throw new Error("only terminal output can be truncated");const t={...e.frame,data:"",encoding:"base64",truncated:!0},i=JSON.stringify(t);return{...e,frame:t,wire:i,bytes:Buffer.byteLength(i)}}async flush(e){for(;this.acceptsConnection(e)&&this.pending.length>0;){const t=this.pending.shift(),i={item:t,counted:!0};this.activeSend=i;try{await this.options.client.send(t.wire)}catch{return void(this.transportEpoch===e&&this.invalidateConnection("transport"))}if(!this.acceptsConnection(e))return;this.activeSend===i&&(i.counted&&(this.pendingBytes-=t.bytes),this.activeSend=void 0)}}kickFlush(){if(this.activeFlush||!this.connected||0===this.pending.length)return;const e=this.transportEpoch,t=this.flush(e).finally(()=>{this.flushes.delete(t),this.activeFlush?.promise===t&&(this.activeFlush=void 0,this.acceptsConnection(e)&&this.kickFlush())});this.activeFlush={epoch:e,promise:t},this.flushes.add(t)}acceptsConnection(e){return this.connected&&!this.stopped&&e===this.transportEpoch}acceptsTransport(e,t){return this.acceptsEvent(this.eventGeneration)&&this.acceptsConnection(e)&&this.serviceGeneration===t}invalidateConnection(e){const t=this.connected;this.transportEpoch++,this.connected=!1,t&&this.publishAvailability(!1),this.activeFlush=void 0,this.activeSend=void 0,this.flushes.clear(),this.pending.length=0,this.pendingBytes=0;const i=this.serviceGeneration;switch(e){case"transport":return;case"park":i&&this.service.parkServiceSessions(i,this.handlerIdentity,this.options.leaseRecoveryGraceMs??3e5);break;case"fence":this.service.closeParkedServiceSessions("runtime_lease_lost"),i&&this.service.fenceServiceSessions("runtime_lease_lost",i);break;default:throw new Error(`unhandled connection invalidation ${String(e)}`)}i&&(this.service.clearOutbound(this,i),this.serviceGeneration===i&&(this.serviceGeneration=void 0)),this.clearReplayState()}publishAvailability(e){try{this.options.onAvailabilityChange?.(e)}catch{}}clearReplayState(){for(const e of this.replayExpiryTimers.values())clearTimeout(e);this.replayExpiryTimers.clear(),this.rings.clear(),this.ringBytes.clear(),this.droppedThrough.clear()}scheduleReplayExpiry(e,t){this.clearReplayExpiry(t);const i=this.serviceGeneration;if(!i)return;const n=setTimeout(()=>{if(this.replayExpiryTimers.get(t)===n){this.replayExpiryTimers.delete(t);try{this.service.expireServiceTerminalReplay(e,t,i)}catch(e){this.options.logger?.warn?.("[terminal-service] replay expiry failed",e)}}},Math.max(0,this.options.replayRetentionMs??3e5));n.unref?.(),this.replayExpiryTimers.set(t,n)}clearReplayExpiry(e){const t=this.replayExpiryTimers.get(e);t&&clearTimeout(t),this.replayExpiryTimers.delete(e)}closeClientOnce(){return this.closePromise??=this.options.client.close(!0),this.closePromise}}function decodedOutputBytes(e){return"terminal.output"===e.type?Buffer.byteLength(e.data,e.encoding):0}const interrupted=Symbol("interrupted");async function waitBounded(e,t,i){return i?.aborted?(e.catch(()=>{}),interrupted):await new Promise((n,r)=>{let s=!1;const a=e=>{s||(s=!0,clearTimeout(c),i?.removeEventListener("abort",o),n(e))},o=()=>a(interrupted),c=setTimeout(()=>a(interrupted),Math.max(0,t));c.unref?.(),i?.addEventListener("abort",o,{once:!0}),e.then(e=>a(e),e=>{s||(s=!0,clearTimeout(c),i?.removeEventListener("abort",o),r(e))})})}
1
+ import{TERMINAL_MAX_FRAME_BYTES,TERMINAL_SERVICE_PROTOCOL_VERSION}from"@beeos-ai/beeos-types/runtime";import{TerminalNotFoundError}from"./session-manager.js";import{getOrCreateRuntimeTerminalService}from"./runtime-terminal-service.js";import{TerminalAuthorityAdmission}from"./terminal-authority-admission.js";const DEFAULT_REPLAY_RETENTION_MS=3e5,DEFAULT_LEASE_RECOVERY_GRACE_MS=3e5;export class RuntimeTerminalOutboundRegistration{options;service;authorityAdmission;serviceGeneration;rings=new Map;ringBytes=new Map;droppedThrough=new Map;pending=[];pendingBytes=0;activeSend;activeFlush;flushes=new Set;connected=!1;handlerIdentity;draining=!1;stopped=!1;eventGeneration=0;transportEpoch=0;drainPromise;closePromise;replayExpiryTimers=new Map;constructor(e){this.options=e,this.service=e.service??getOrCreateRuntimeTerminalService(e.manager),this.authorityAdmission=e.frameAdmission??new TerminalAuthorityAdmission(e.authorityJournal,e.authorityClock),this.serviceGeneration=this.service.setOutbound(this);const t=this.eventGeneration;e.client.on("connected",(...i)=>{if(!this.acceptsEvent(t))return;if(this.connected)return;this.transportEpoch++,this.activeFlush=void 0,this.serviceGeneration??=this.service.setOutbound(this);const n=i[0]?.handlerIdentity;n&&(this.handlerIdentity=n,this.service.closeParkedServiceSessions("runtime_lease_lost",n)),this.connected=!0,this.publishAvailability(!0),this.kickFlush(),e.logger?.info?.("[terminal-service] outbound registration connected")}),e.client.on("disconnected",()=>{this.acceptsEvent(t)&&this.invalidateConnection("transport")}),e.client.on("message",(...e)=>{if(!this.acceptsEvent(t))return;const i=this.serviceGeneration,n=this.transportEpoch;if(!i||!this.acceptsTransport(n,i))return;const r=e[0],s="string"==typeof r?r:Buffer.from(r).toString("utf8");this.handleWire(s,i,n)}),e.client.on("error",(...i)=>{this.acceptsEvent(t)&&e.logger?.warn?.("[terminal-service] outbound registration error",i[0])}),e.client.on("authorityLost",(...i)=>{if(this.acceptsEvent(t)){if(!0===i[0]?.recoverable)return this.invalidateConnection("park"),void e.logger?.warn?.("[terminal-service] runtime lease lost; Service terminals parked for recovery");this.invalidateConnection("fence"),e.logger?.warn?.("[terminal-service] runtime lease lost; Service terminals fenced")}}),e.client.on("transportEpochChanged",()=>{this.acceptsEvent(t)&&(this.invalidateConnection("park"),e.logger?.info?.("[terminal-service] runtime lease rotated; prior Terminal epoch parked for resume"))})}async start(){if(this.stopped)throw new Error("terminal outbound registration is stopped");await this.options.client.connect()}isAvailable(){return this.connected&&!this.draining&&!this.stopped}send(e){if(this.stopped)return!1;const t=JSON.stringify(e),i=Buffer.byteLength(t);return this.remember(e,t,i),!this.connected||(this.enqueueWithOutputBackpressure({clientId:e.clientId,terminalId:e.terminalId,frame:e,wire:t,bytes:i}),this.kickFlush(),!0)}resume(e,t,i){const n=this.transportEpoch;if(!this.acceptsConnection(n))throw new Error("terminal outbound registration is disconnected");const r=BigInt(i),s=this.droppedThrough.get(t)??0n,a=r<s;let o=[],c=-1n;for(const i of this.rings.get(t)??[]){if(i.frame.clientId!==e)throw new Error("terminal resume ownership mismatch");const n=BigInt(i.frame.sequence);if(n<=c)throw new Error("terminal replay sequence is not monotonic");c=n,n<=r||o.push({clientId:e,terminalId:t,frame:i.frame,wire:i.wire,bytes:i.bytes})}let h=a?s:void 0;if(0n===r||a){const e=this.options.maxResumeReplayOutputBytes??262144;let t=0,i=o.length;for(;i>0&&(t+=decodedOutputBytes(o[i-1].frame),!(t>e));)i--;i>0&&(h=BigInt(o[i-1].frame.sequence),o=o.slice(i))}if(!this.acceptsConnection(n))throw new Error("terminal outbound registration epoch changed");let d=0;for(const i of this.pending)i.terminalId!==t||i.clientId!==e?this.pending[d++]=i:this.pendingBytes-=i.bytes;if(this.pending.length=d,void 0!==h){const i={protocolVersion:TERMINAL_SERVICE_PROTOCOL_VERSION,type:"terminal.output",clientId:e,terminalId:t,sequence:String(h),stream:"stdout",data:"",encoding:"base64",truncated:!0},n=JSON.stringify(i);this.enqueueWithOutputBackpressure({clientId:e,terminalId:t,frame:i,wire:n,bytes:Buffer.byteLength(n)})}for(const e of o)this.enqueueWithOutputBackpressure(e);return this.kickFlush(),{gap:a,truncated:void 0!==h}}rebindResumeClient(e,t,i){if(e===t)return;const n=this.rings.get(i);if(n?.some(t=>t.frame.clientId!==e))throw new Error("terminal resume ownership mismatch");if(n){let e=0;const r=n.map(i=>{const n=Object.freeze({...i.frame,clientId:t}),r=JSON.stringify(n),s=Buffer.byteLength(r);return e+=s,{frame:n,wire:r,bytes:s}});this.rings.set(i,r),this.ringBytes.set(i,e)}for(let n=0;n<this.pending.length;n++){const r=this.pending[n];if(r.terminalId!==i||r.clientId!==e)continue;const s={...r.frame,clientId:t},a=JSON.stringify(s),o=Buffer.byteLength(a);this.pendingBytes+=o-r.bytes,this.pending[n]={clientId:t,terminalId:i,frame:s,wire:a,bytes:o}}if(this.activeSend?.item.terminalId===i&&this.activeSend.item.clientId===e){const e=this.activeSend.item,n={...e.frame,clientId:t},r=JSON.stringify(n),s=Buffer.byteLength(r);this.activeSend.counted&&(this.pendingBytes+=s-e.bytes),this.activeSend={item:{clientId:t,terminalId:i,frame:n,wire:r,bytes:s},counted:this.activeSend.counted}}}forgetTerminal(e,t){this.prepareForgetTerminal(e,t)()}prepareForgetTerminal(e,t){const i=this.rings.get(t),n=new Set(this.pending.filter(e=>e.terminalId===t)),r=this.activeSend?.item.terminalId===t&&this.activeSend.counted?this.activeSend:void 0;if(i?.some(t=>t.frame.clientId!==e)||[...n].some(t=>t.clientId!==e)||r&&r.item.clientId!==e)throw new Error("terminal forget ownership mismatch");const s=this.replayExpiryTimers.get(t);let a=!1;return()=>{if(a)return;a=!0,s&&this.replayExpiryTimers.get(t)===s&&(clearTimeout(s.timer),this.replayExpiryTimers.delete(t)),i&&this.rings.get(t)===i&&(this.rings.delete(t),this.ringBytes.delete(t),this.droppedThrough.delete(t));let e=0;for(const t of this.pending)n.has(t)?this.pendingBytes-=t.bytes:this.pending[e++]=t;this.pending.length=e,r&&this.activeSend===r&&r.counted&&(r.counted=!1,this.pendingBytes-=r.item.bytes),this.pendingBytes=Math.max(0,this.pendingBytes)}}startTerminalReplayExpiry(e,t){if((this.rings.get(t)??[]).some(t=>t.frame.clientId!==e))throw new Error("terminal replay expiry ownership mismatch");this.scheduleReplayExpiry(e,t)}drain(e){return this.drainPromise??=this.drainOnce(e),this.drainPromise}async drainOnce(e){this.draining=!0,this.stopped=!0,this.eventGeneration++;const t=[...this.flushes];this.invalidateConnection("fence");const i=await waitBounded(this.options.manager.drain(e.timeoutMs,e.terminateAfterTimeout),e.timeoutMs,e.signal),n=i===interrupted?"terminated":i;return i!==interrupted&&"terminated"===n&&e.terminateAfterTimeout&&await waitBounded(this.options.manager.drain(Math.min(1e3,Math.max(50,e.timeoutMs)),!1),Math.min(1e3,Math.max(50,e.timeoutMs)),e.signal),await waitBounded(Promise.allSettled(t),e.timeoutMs,e.signal),await waitBounded(this.closeClientOnce(),e.timeoutMs,e.signal),this.clearReplayState(),n}acceptsEvent(e){return!this.stopped&&e===this.eventGeneration}async handleWire(e,t,i){if(!this.acceptsTransport(i,t))return;if(Buffer.byteLength(e)>TERMINAL_MAX_FRAME_BYTES)return void this.options.logger?.warn?.("[terminal-service] oversized client frame rejected");let n,r;try{n=JSON.parse(e)}catch{return void this.options.logger?.warn?.("[terminal-service] invalid client JSON")}try{r=await this.authorityAdmission.admit(n),await this.service.handleClientFrame(r.frame,t,r.admission)}catch(e){if(this.acceptsTransport(i,t)){r&&this.sendRejectedFrame(r.frame,e);const t=e instanceof Error?e.message:String(e);this.options.logger?.warn?.(`[terminal-service] client frame rejected: ${t}`,e)}}}sendRejectedFrame(e,t){const i=(t instanceof Error?t.message:String(t)).toLowerCase(),n=t instanceof TerminalNotFoundError||i.includes("not found")?"TERMINAL_NOT_FOUND":i.includes("quota")?"TERMINAL_QUOTA_EXCEEDED":i.includes("unauthorized")||i.includes("authority")?"UNAUTHORIZED_TARGET":"TERMINAL_IO_ERROR",r=this.rings.get(e.terminalId),s="TERMINAL_NOT_FOUND"!==n&&this.options.manager.getSession(e.terminalId)?.sessionId===e.clientId&&(!r||r.every(t=>t.frame.clientId===e.clientId)),a=s?this.options.manager.getSessionIncarnation(e.terminalId):void 0,o=s||"terminal.resume"!==e.type?a?BigInt(this.options.manager.nextServerSequence(e.terminalId,a)):1n:BigInt(e.lastServerSequence)+1n,c={protocolVersion:TERMINAL_SERVICE_PROTOCOL_VERSION,type:"terminal.error",clientId:e.clientId,terminalId:e.terminalId,sequence:String(o),code:n,message:n,recoverable:"TERMINAL_IO_ERROR"===n||"TERMINAL_QUOTA_EXCEEDED"===n};if(!s){const t=JSON.stringify(c);return this.enqueueWithOutputBackpressure({clientId:e.clientId,terminalId:e.terminalId,frame:c,wire:t,bytes:Buffer.byteLength(t)}),void this.kickFlush()}this.send(c)}remember(e,t,i){const n=this.rings.get(e.terminalId)??[];n.push({frame:Object.freeze({...e}),wire:t,bytes:i});let r=(this.ringBytes.get(e.terminalId)??0)+i;const s=this.options.maxBufferedFramesPerTerminal??512,a=this.options.maxBufferedBytesPerTerminal??1048576;for(;n.length>s||r>a;){const t=n.shift();if(!t)break;r-=t.bytes,this.droppedThrough.set(e.terminalId,BigInt(t.frame.sequence))}this.rings.set(e.terminalId,n),this.ringBytes.set(e.terminalId,r),"terminal.exit"===e.type&&this.scheduleReplayExpiry(e.clientId,e.terminalId)}enqueueWithOutputBackpressure(e){const t=this.options.maxPendingSendBytes??1048576;if(this.pendingBytes+e.bytes<=t)return this.pending.push(e),void(this.pendingBytes+=e.bytes);if("terminal.output"===e.frame.type){if(this.hasPendingTruncation(e.terminalId))return;const i=this.toTruncationMarker(e);return this.compactPendingOutput(i.bytes,t),void(this.hasPendingTruncation(e.terminalId)||(this.pending.push(i),this.pendingBytes+=i.bytes))}this.compactPendingOutput(e.bytes,t),this.pending.push(e),this.pendingBytes+=e.bytes}compactPendingOutput(e,t){for(;this.pendingBytes+e>t;){const e=this.pending.findIndex(e=>"terminal.output"===e.frame.type&&!0!==e.frame.truncated);if(e<0)return;const t=this.pending[e];if(this.hasPendingTruncation(t.terminalId,t)){this.pending.splice(e,1),this.pendingBytes-=t.bytes;continue}const i=this.toTruncationMarker(t);this.pending[e]=i,this.pendingBytes+=i.bytes-t.bytes}}hasPendingTruncation(e,t){return this.activeSend?.item!==t&&this.activeSend?.item.terminalId===e&&"terminal.output"===this.activeSend.item.frame.type&&!0===this.activeSend.item.frame.truncated||this.pending.some(i=>i!==t&&i.terminalId===e&&"terminal.output"===i.frame.type&&!0===i.frame.truncated)}toTruncationMarker(e){if("terminal.output"!==e.frame.type)throw new Error("only terminal output can be truncated");const t={...e.frame,data:"",encoding:"base64",truncated:!0},i=JSON.stringify(t);return{...e,frame:t,wire:i,bytes:Buffer.byteLength(i)}}async flush(e){for(;this.acceptsConnection(e)&&this.pending.length>0;){const t=this.pending.shift(),i={item:t,counted:!0};this.activeSend=i;try{await this.options.client.send(t.wire)}catch{return void(this.transportEpoch===e&&this.invalidateConnection("transport"))}if(!this.acceptsConnection(e))return;this.activeSend===i&&(i.counted&&(this.pendingBytes-=t.bytes),this.activeSend=void 0)}}kickFlush(){if(this.activeFlush||!this.connected||0===this.pending.length)return;const e=this.transportEpoch,t=this.flush(e).finally(()=>{this.flushes.delete(t),this.activeFlush?.promise===t&&(this.activeFlush=void 0,this.acceptsConnection(e)&&this.kickFlush())});this.activeFlush={epoch:e,promise:t},this.flushes.add(t)}acceptsConnection(e){return this.connected&&!this.stopped&&e===this.transportEpoch}acceptsTransport(e,t){return this.acceptsEvent(this.eventGeneration)&&this.acceptsConnection(e)&&this.serviceGeneration===t}invalidateConnection(e){const t=this.connected;this.transportEpoch++,this.connected=!1,t&&this.publishAvailability(!1),this.activeFlush=void 0,this.activeSend=void 0,this.flushes.clear(),this.pending.length=0,this.pendingBytes=0;const i=this.serviceGeneration;switch(e){case"transport":return;case"park":i&&this.service.parkServiceSessions(i,this.handlerIdentity,this.options.leaseRecoveryGraceMs??3e5);break;case"fence":this.service.closeParkedServiceSessions("runtime_lease_lost"),i&&this.service.fenceServiceSessions("runtime_lease_lost",i);break;default:throw new Error(`unhandled connection invalidation ${String(e)}`)}i&&(this.service.clearOutbound(this,i),this.serviceGeneration===i&&(this.serviceGeneration=void 0)),this.clearReplayState()}publishAvailability(e){try{this.options.onAvailabilityChange?.(e)}catch{}}get replayWindowSeconds(){return Math.ceil(Math.max(0,this.options.replayRetentionMs??3e5)/1e3)}replayObservation(e){return{deadline:this.replayExpiryTimers.get(e)?.deadline??null,windowSeconds:this.replayWindowSeconds}}clearReplayState(){for(const e of this.replayExpiryTimers.values())clearTimeout(e.timer);this.replayExpiryTimers.clear(),this.rings.clear(),this.ringBytes.clear(),this.droppedThrough.clear()}scheduleReplayExpiry(e,t){this.clearReplayExpiry(t);const i=this.serviceGeneration;if(!i)return;const n=Math.max(0,this.options.replayRetentionMs??3e5),r=Date.now()+n,s=setTimeout(()=>{if(this.replayExpiryTimers.get(t)?.timer===s){this.replayExpiryTimers.delete(t);try{this.service.expireServiceTerminalReplay(e,t,i)}catch(e){this.options.logger?.warn?.("[terminal-service] replay expiry failed",e)}}},n);s.unref?.(),this.replayExpiryTimers.set(t,{timer:s,deadline:r})}clearReplayExpiry(e){const t=this.replayExpiryTimers.get(e);t&&clearTimeout(t.timer),this.replayExpiryTimers.delete(e)}closeClientOnce(){return this.closePromise??=this.options.client.close(!0),this.closePromise}}function decodedOutputBytes(e){return"terminal.output"===e.type?Buffer.byteLength(e.data,e.encoding):0}const interrupted=Symbol("interrupted");async function waitBounded(e,t,i){return i?.aborted?(e.catch(()=>{}),interrupted):await new Promise((n,r)=>{let s=!1;const a=e=>{s||(s=!0,clearTimeout(c),i?.removeEventListener("abort",o),n(e))},o=()=>a(interrupted),c=setTimeout(()=>a(interrupted),Math.max(0,t));c.unref?.(),i?.addEventListener("abort",o,{once:!0}),e.then(e=>a(e),e=>{s||(s=!0,clearTimeout(c),i?.removeEventListener("abort",o),r(e))})})}