beeos-claw 0.5.62 → 0.5.64-staging.0

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 (152) hide show
  1. package/dist/acp-gateway/bridge-core.js +1 -1
  2. package/dist/acp-gateway/mcp-credential-reference-port.js +1 -0
  3. package/dist/acp-gateway/mcp-desired-state-controller.js +1 -0
  4. package/dist/acp-gateway/mcp-desired-state-store.js +1 -0
  5. package/dist/acp-gateway/mcp-public-config-security.js +1 -0
  6. package/dist/acp-gateway/mcp-service.js +1 -1
  7. package/dist/acp-gateway/mcp-tool-bridge.js +1 -0
  8. package/dist/acp-gateway/session-state.js +1 -1
  9. package/dist/acp-server.js +1 -1
  10. package/dist/agent-auth.js +1 -1
  11. package/dist/agent-sync.js +1 -1
  12. package/dist/canvas/runtime-canvas-service.js +1 -0
  13. package/dist/config.js +1 -1
  14. package/dist/cron-overrides.js +1 -1
  15. package/dist/index.js +1 -1
  16. package/dist/openclaw-cli.js +1 -1
  17. package/dist/runtime/agent-gateway-runtime-attempt-authorization.js +1 -0
  18. package/dist/runtime/agent-projection-production.js +1 -0
  19. package/dist/runtime/agent-projection-worker.js +1 -0
  20. package/dist/runtime/canvas-control-authority.js +1 -0
  21. package/dist/runtime/composite-active-run-port.js +1 -0
  22. package/dist/runtime/conversation-clear-use-case.js +1 -0
  23. package/dist/runtime/core-execution-attempt-context.js +1 -0
  24. package/dist/runtime/message-service-conversation-clear-http-authority.js +1 -0
  25. package/dist/runtime/message-service-conversation-history-boundary.js +1 -0
  26. package/dist/runtime/message-service-conversation-run-boundary.js +1 -0
  27. package/dist/runtime/message-service-runtime-authenticated-fetch.js +1 -0
  28. package/dist/runtime/message-service-runtime-claim-authority.js +1 -0
  29. package/dist/runtime/message-service-runtime-invocation-authority.js +1 -0
  30. package/dist/runtime/message-service-runtime-message-channel.js +1 -0
  31. package/dist/runtime/monotonic-journal-segments.js +1 -0
  32. package/dist/runtime/ms-runtime-message-channel-adapter.js +1 -0
  33. package/dist/runtime/ms-runtime-message-channel-consumer-factory.js +1 -0
  34. package/dist/runtime/ms-runtime-rpc-adapter.js +1 -0
  35. package/dist/runtime/ms-runtime-rpc-consumer-factory.js +1 -0
  36. package/dist/runtime/openclaw-runtime-capabilities.js +1 -0
  37. package/dist/runtime/owned-directory-lock.js +1 -0
  38. package/dist/runtime/runtime-agent-identity-registry.js +1 -0
  39. package/dist/runtime/runtime-command-dispatcher.js +1 -0
  40. package/dist/runtime/runtime-command-journal.js +1 -0
  41. package/dist/runtime/runtime-core-authority-ports.js +1 -0
  42. package/dist/runtime/runtime-core-authorization-journal.js +1 -0
  43. package/dist/runtime/runtime-cron-service.js +1 -0
  44. package/dist/runtime/runtime-jwks-verifier.js +1 -0
  45. package/dist/runtime/runtime-local-parity-matrix.js +1 -0
  46. package/dist/runtime/runtime-message-channel-contract.js +1 -0
  47. package/dist/runtime/runtime-operation-lease.js +1 -0
  48. package/dist/runtime/runtime-operation-resource-resolver.js +1 -0
  49. package/dist/runtime/runtime-parity-manifest-digest.js +1 -0
  50. package/dist/runtime/runtime-parity-manifest.js +1 -0
  51. package/dist/runtime/runtime-poison-delivery-journal.js +1 -0
  52. package/dist/runtime/runtime-production-authority.js +1 -0
  53. package/dist/runtime/runtime-receipt-history-port.js +1 -0
  54. package/dist/runtime/runtime-registration-identity-store.js +1 -0
  55. package/dist/runtime/runtime-registration.js +1 -0
  56. package/dist/runtime/runtime-rpc-consumer.js +1 -0
  57. package/dist/runtime/runtime-service-contract-compatibility.js +1 -0
  58. package/dist/runtime/runtime-service-production-candidate.js +1 -0
  59. package/dist/runtime/runtime-session-control-service.js +1 -0
  60. package/dist/runtime/runtime-session-list-policy.js +1 -0
  61. package/dist/runtime/runtime-skills-service.js +1 -0
  62. package/dist/runtime/service-runtime-attempt-ports-resolver.js +1 -0
  63. package/dist/runtime/session-mutation-coordinator.js +1 -1
  64. package/dist/runtime/session-set-model-mapping.js +1 -0
  65. package/dist/runtime/split-runtime-authorization-verifier.js +1 -0
  66. package/dist/terminal/bridge-handler.js +1 -1
  67. package/dist/terminal/local-terminal-authority.js +1 -0
  68. package/dist/terminal/rpc.js +1 -1
  69. package/dist/terminal/runtime-terminal-outbound-registration.js +1 -0
  70. package/dist/terminal/runtime-terminal-service.js +1 -0
  71. package/dist/terminal/session-manager.js +1 -1
  72. package/dist/terminal/terminal-authority-admission.js +1 -0
  73. package/dist/terminal/terminal-authority-contract.js +1 -0
  74. package/dist/terminal/terminal-authority-journal.js +1 -0
  75. package/dist/terminal/terminal-lease-bound-bridge-client.js +1 -0
  76. package/openclaw.plugin.json +10 -0
  77. package/package.json +26 -6
  78. package/scripts/runtime-blackbox-worker.mjs +242 -0
  79. package/scripts/runtime-poison-blackbox-lib.mjs +59 -0
  80. package/scripts/runtime-poison-blackbox.mjs +21 -0
  81. package/scripts/runtime-security.mjs +124 -0
  82. package/dist/a2a/tools.test.js +0 -1
  83. package/dist/acp-gateway/agent-template-package.test.js +0 -1
  84. package/dist/acp-gateway/assistant-mq-lifecycle.test.js +0 -1
  85. package/dist/acp-gateway/beeos-user-envelope.test.js +0 -1
  86. package/dist/acp-gateway/bridge-core-agent-create.test.js +0 -1
  87. package/dist/acp-gateway/bridge-core-agent-lifecycle.test.js +0 -1
  88. package/dist/acp-gateway/bridge-core-apply-template.test.js +0 -1
  89. package/dist/acp-gateway/bridge-core-cron-channel-reuse.test.js +0 -1
  90. package/dist/acp-gateway/bridge-core-cron-internal-delivery.test.js +0 -1
  91. package/dist/acp-gateway/bridge-core-cron-meta-hydration.test.js +0 -1
  92. package/dist/acp-gateway/bridge-core-cron-timeout.test.js +0 -1
  93. package/dist/acp-gateway/bridge-core-history-page.test.js +0 -1
  94. package/dist/acp-gateway/bridge-core-persisted-tool-result.test.js +0 -1
  95. package/dist/acp-gateway/bridge-core-ping.test.js +0 -1
  96. package/dist/acp-gateway/bridge-core-session-mutation-lifecycle.test.js +0 -1
  97. package/dist/acp-gateway/bridge-core-session-new.test.js +0 -1
  98. package/dist/acp-gateway/error-message-formatter.test.js +0 -1
  99. package/dist/acp-gateway/external-protocol-tags.test.js +0 -1
  100. package/dist/acp-gateway/file-artifact-presenter.test.js +0 -1
  101. package/dist/acp-gateway/gateway-events-cron-backfill.test.js +0 -1
  102. package/dist/acp-gateway/gateway-events-cron-name.test.js +0 -1
  103. package/dist/acp-gateway/gateway-events-file-artifact.test.js +0 -1
  104. package/dist/acp-gateway/gateway-events-heartbeat.test.js +0 -1
  105. package/dist/acp-gateway/history-replay.test.js +0 -1
  106. package/dist/acp-gateway/history-turn-completion-policy.test.js +0 -1
  107. package/dist/acp-gateway/local-session-history.test.js +0 -1
  108. package/dist/acp-gateway/mcp-service.test.js +0 -1
  109. package/dist/acp-gateway/missing-artifacts-reconciler.test.js +0 -1
  110. package/dist/acp-gateway/pending-user-turn-updates.test.js +0 -1
  111. package/dist/acp-gateway/session-load-inflight-user.test.js +0 -1
  112. package/dist/acp-gateway/session-load.test.js +0 -1
  113. package/dist/acp-gateway/session-state.test.js +0 -1
  114. package/dist/acp-gateway/session-write-gate.test.js +0 -1
  115. package/dist/acp-gateway/stale-session-lock-sweeper.test.js +0 -1
  116. package/dist/acp-gateway/tool-result-payload-strategies.test.js +0 -1
  117. package/dist/acp-gateway/tool-result-projection.test.js +0 -1
  118. package/dist/acp-gateway/transport.test.js +0 -1
  119. package/dist/acp-gateway/turn-id-binding.test.js +0 -1
  120. package/dist/acp-server-agent-sync.test.js +0 -1
  121. package/dist/acp-server-bridge-shim.test.js +0 -1
  122. package/dist/acp-server-chat-abort.test.js +0 -1
  123. package/dist/auto-reply-tokens.test.js +0 -1
  124. package/dist/canvas/state.test.js +0 -1
  125. package/dist/canvas/tools.test.js +0 -1
  126. package/dist/canvas-reregister-session-race.test.js +0 -1
  127. package/dist/files/beeos-file-uri.test.js +0 -1
  128. package/dist/files/fetch-tool.test.js +0 -1
  129. package/dist/gateway-client-chat-abort.test.js +0 -1
  130. package/dist/gateway-client-handshake-retry.test.js +0 -1
  131. package/dist/gateway-client-history-page.test.js +0 -1
  132. package/dist/im-inbound-handler.test.js +0 -1
  133. package/dist/im-recovery-scanner.test.js +0 -1
  134. package/dist/im-runtime-dispatch-session-guard.test.js +0 -1
  135. package/dist/im-runtime-dispatch.test.js +0 -1
  136. package/dist/media/image-provider.test.js +0 -1
  137. package/dist/media/video-provider.test.js +0 -1
  138. package/dist/message-sanitization/message-classifier.test.js +0 -1
  139. package/dist/message-sanitization/transport-metadata-sanitizer.test.js +0 -1
  140. package/dist/runtime/agent-runtime-failure-classifier.test.js +0 -1
  141. package/dist/runtime/callback-queue.test.js +0 -1
  142. package/dist/runtime/im-reply-sink.test.js +0 -1
  143. package/dist/runtime/im-runtime-failure-policy.test.js +0 -1
  144. package/dist/runtime/lifecycle-promise-observer.test.js +0 -1
  145. package/dist/runtime/openclaw-session-write-quiescence.test.js +0 -1
  146. package/dist/runtime/prompt-run-store.test.js +0 -1
  147. package/dist/runtime/reply-router.test.js +0 -1
  148. package/dist/runtime/runtime-restart-scheduler.test.js +0 -1
  149. package/dist/runtime/session-mutation-coordinator.test.js +0 -1
  150. package/dist/test-fixtures/im-runtime.fixture.js +0 -1
  151. package/dist/test-fixtures/session-mutation.fixture.js +0 -1
  152. package/dist/upload/tool.test.js +0 -1
@@ -1 +1 @@
1
- import{randomUUID,createHash}from"node:crypto";import{mkdir,writeFile,readFile,rm,lstat,readlink,stat,readdir,rename,cp}from"node:fs/promises";import path from"node:path";import{join,resolve,relative,sep}from"node:path";import{homedir}from"node:os";import AdmZip from"adm-zip";import{sanitizePromptPayload}from"../message-sanitization/prompt-payload-sanitizer.js";import{previewSanitizedText,stripTransportMetadata}from"../message-sanitization/transport-metadata-sanitizer.js";import{isHeartbeatPromptText,isCronPromptText,extractCronJobId,classifyHeartbeatFilter}from"../message-sanitization/message-classifier.js";import{createCronOverridesStore}from"../cron-overrides.js";import{CRON_DURATION_FLOOR_MS}from"../config.js";import{CANVAS_TERM_MAPPING,CANVAS_UPDATE_HINT,buildActiveSurfacesContext}from"../canvas/tools.js";import{parseMentionsParam,buildMentionInstruction}from"../delegation/mention-resolver.js";import{EXTERNAL_PROTOCOL_TAGS}from"./external-protocol-tags.js";import{extractPromptBlocks,toGatewayPrompt,resolveSlashCommandName as resolveSlashCmd}from"./prompt-converter.js";import{extractBeeosUserTextFromHistoryContent}from"./beeos-user-envelope.js";import{loadHistory,loadHistoryPage,groupIntoTurns,splitUserReplayBlocks}from"./history-replay.js";import{classifyHistoricalAssistantTurnCompletion}from"./history-turn-completion-policy.js";import{LocalSessionHistory}from"./local-session-history.js";import{reconcileMissingArtifacts}from"./missing-artifacts-reconciler.js";import{buildResolutionPlan,resolveResources,applyResolutions}from"./prompt-resource-resolver.js";import{readEnrichmentCache,readUploadToolResult}from"../upload/result-cache.js";import{buildToolResultSessionContentBlocks}from"./tool-result-blocks.js";import{prefersChatFileArtifacts}from"./file-artifact-presenter.js";import{buildToolCallPayload,buildToolCallUpdatePayload}from"./tool-call-payload-builder.js";import{Errors}from"../errors/index.js";import{classifyAgentRuntimeFailure,INTERRUPTED_PUBLIC_MESSAGE}from"../runtime/agent-runtime-failure-classifier.js";import{SESSION_RECOVERY_REQUIRED_CODE,SESSION_RECOVERY_REQUIRED_MESSAGE}from"../runtime/session-mutation-coordinator.js";import{observeLifecyclePromise}from"../runtime/lifecycle-promise-observer.js";import{resolveChannelFromSessionKey}from"../channel-detection.js";import{resolveTurnId,recordTurnIdForRole,recordToolCallTurnForUpdate}from"./gateway-events.js";import{emitToolResultProjectionForTurn}from"./tool-result-emitter.js";import{extractPersistedToolResultEnvelope,findPersistedToolResultEnvelopeByMessageId}from"./tool-result-transcript.js";import{projectPersistedToolResultForReplay}from"./tool-result-projection.js";import{McpService}from"./mcp-service.js";import{DEFAULT_PROMPT_TIMEOUT_MS}from"./types.js";import{ACP_PROTOCOL_VERSION}from"@beeos-ai/beeos-types/acp";import{parseAndValidateTemplatePackage,TemplatePackageError,MAX_ZIP_BYTES}from"./agent-template-package.js";const AGENT_VALID_ID_RE=/^[a-z0-9][a-z0-9_-]{0,63}$/i,AGENT_INVALID_CHARS_RE=/[^a-z0-9_-]+/g,AGENT_LEADING_DASH_RE=/^-+/,AGENT_TRAILING_DASH_RE=/-+$/,DEFAULT_AGENT_ID="main";export function normalizeAgentId(e){const t=(e??"").trim();return t?AGENT_VALID_ID_RE.test(t)?t.toLowerCase():t.toLowerCase().replace(AGENT_INVALID_CHARS_RE,"-").replace(AGENT_LEADING_DASH_RE,"").replace(AGENT_TRAILING_DASH_RE,"").slice(0,64)||"main":"main"}function narrowContentBlock(e){switch(e.type){case"text":return"string"==typeof e.text?{type:"text",text:e.text}:void 0;case"resource_link":return"string"==typeof e.uri?{type:"resource_link",uri:e.uri,...void 0!==e.name?{name:e.name}:{},...void 0!==e.mimeType?{mimeType:e.mimeType}:{}}:void 0;case"file":return{type:"file",...void 0!==e.text?{text:e.text}:{},...void 0!==e.uri?{uri:e.uri}:{},...void 0!==e.mimeType?{mimeType:e.mimeType}:{}};case"image":return{type:"image",...void 0!==e.uri?{uri:e.uri}:{},...void 0!==e.mimeType?{mimeType:e.mimeType}:{}};default:return}}const DEFAULT_MODE={id:"default",name:"Default",description:"Default agent mode"},USER_MESSAGE_PREFIX="User Message From BeeOS:",USER_MESSAGE_PREFIX_WITH_NEWLINE=`${USER_MESSAGE_PREFIX}\n`,SLASH_COMMAND_NAME_RE=/^[a-z0-9_-]+$/i,HISTORY_PAGE_DEFAULT_LIMIT=100,HISTORY_PAGE_MAX_LIMIT=1e3;function withUserMessagePrefix(e){return e.startsWith(USER_MESSAGE_PREFIX)?e:`${USER_MESSAGE_PREFIX_WITH_NEWLINE}${e}`}const BACKGROUND_SESSION_PATTERNS=[":cron:",":hook:","node-",...EXTERNAL_PROTOCOL_TAGS];function isBackgroundSessionKey(e){return BACKGROUND_SESSION_PATTERNS.some(t=>e.includes(t))}function isSystemDefaultSessionKey(e){const t=e.split(":");if(t.length<3||"agent"!==t[0])return!1;const s=t.slice(2).join(":");return"main"===s||s===t[1]}function isPlaceholderTitle(e){return!e||"New chat"===e||(!!/^[0-9a-f]{6,12}\s*\(20\d{2}/i.test(e)||(!!/^Sender\s*\(untrusted/i.test(e)||!!/^Conversation\s+info\s*\(untrusted/i.test(e)))}function cleanGatewayDerivedTitle(e){if(!e)return;let t=extractBeeosUserTextFromHistoryContent(e).text;return/^\[System\b/.test(t)?void 0:(t=t.trim(),t||void 0)}function cleanHistoryUserContent(e){return extractBeeosUserTextFromHistoryContent(e).text.trim()}function mergePersistedToolResultEnvelopes(e,t){if(e||t)return{toolName:e?.toolName||t?.toolName||"tool",...e?.toolCallId||t?.toolCallId?{toolCallId:e?.toolCallId??t?.toolCallId}:{},...e?.rawText??t?.rawText?{rawText:e?.rawText??t?.rawText}:{},...void 0!==e?.rawResult||void 0!==t?.rawResult?{rawResult:e?.rawResult??t?.rawResult}:{},...e?.timestamp||t?.timestamp?{timestamp:e?.timestamp??t?.timestamp}:{}}}function resolveStandaloneSlashCommandName(e,t){if(0===e.length)return;if(e.some(e=>"text"!==e.type))return;const s=t.trim();if(!s||s.includes("\n")||!s.startsWith("/"))return;const r=s.match(/^\/([^\s:]+)(?::|\s|$)/)?.[1]?.trim();return r&&!r.includes("/")&&SLASH_COMMAND_NAME_RE.test(r)?r.toLowerCase():void 0}export const BEEOS_CHANNEL_TAG_RE=/<!-- beeos:channel=(.+?) -->/;export function encodeBeeosChannelTag(e,t){const s=`\x3c!-- beeos:channel=${t} --\x3e`,r=stripBeeosChannelTag(e);return r?`${r}\n${s}`:s}export function decodeBeeosChannelId(e){if(!e)return;const t=BEEOS_CHANNEL_TAG_RE.exec(e);return t?.[1]}export function stripBeeosChannelTag(e){return e?e.replace(/\n?<!-- beeos:channel=.+? -->/g,"").trim():""}function deriveSessionId(e,t){const s=createHash("sha256").update(`${t}:${e}`).digest("hex").substring(0,32);return`session:${s.slice(0,8)}-${s.slice(8,12)}-${s.slice(12,16)}-${s.slice(16,20)}-${s.slice(20,32)}`}function parseAgentIdFromKey(e){const t=e.split(":");return(t.length>=3&&"agent"===t[0]?t[1]:"")||"main"}function createSessionObject(e){const t=parseAgentIdFromKey(e.gatewayKey);return{sessionId:deriveSessionId(e.gatewayKey,e.instanceId),gatewaySessionKey:e.gatewayKey,agentId:t,platformAgentId:e.resolvePlatformAgentId?.(t),createdAt:Date.now(),chatReplayDedupKeys:new Set,cwd:e.cwd,title:e.title,channelType:resolveChannelFromSessionKey(e.gatewayKey,e.thirdPartyChannels)}}class HistoryUpdateRecorder{transport;updates=[];constructor(e){this.transport=e}sendSessionUpdate(e,t,s){const r=this.transport.materializeSessionUpdate(t,s);return null!==r&&this.updates.push(r),!0}}export class AcpGatewayBridgeCore{state;transport;config;logger;writeObs;getGateway;thirdPartyChannels;beeosFileResolver;agentFetch;agentGatewayUrl;triggerAgentSync;sessionMutationCoordinator;cronChannelCache=new Map;static CRON_CHANNEL_CACHE_MAX=256;localSessionHistory;mcp;initialized=!1;agentMapping=null;_syncResolvers=[];_mappingReadyCallbacks=[];_mappingUpdateCallbacks=[];cronIsolatedSessionKeys=new Set;cronOverrides;setAgentMapping(e){this.agentMapping=e;for(const t of this.state.sessions.values())!t.platformAgentId&&t.agentId&&(t.platformAgentId=e.get(t.agentId)?.id);for(const e of this._syncResolvers)e();this._syncResolvers=[];for(const e of this._mappingReadyCallbacks)e();this._mappingReadyCallbacks=[];for(const t of this._mappingUpdateCallbacks)t(e)}onceAgentMappingReady(e){this.agentMapping&&this.agentMapping.size>0?e():this._mappingReadyCallbacks.push(e)}onAgentMappingUpdated(e){this._mappingUpdateCallbacks.push(e),this.agentMapping&&this.agentMapping.size>0&&e(this.agentMapping)}markAgentSyncDone(){for(const e of this._syncResolvers)e();this._syncResolvers=[]}waitForAgentMapping(e=15e3){return this.agentMapping?Promise.resolve():new Promise(t=>{this._syncResolvers.push(t);const s=setTimeout(()=>{const e=this._syncResolvers.indexOf(t);-1!==e&&this._syncResolvers.splice(e,1),t()},e);"object"==typeof s&&"unref"in s&&s.unref()})}handleTerminalRequest=null;handleTerminalNotification=null;onRunComplete=null;onRunFailed=null;onRunEnded=null;handleSlashCommand=null;onPromptStart=null;instanceId;constructor(e){this.state=e.state,this.transport=e.transport,this.config=e.config,this.logger=e.logger,this.writeObs=e.writeObs,this.getGateway=e.getGateway,this.instanceId=e.instanceId||"unknown",this.thirdPartyChannels=e.thirdPartyChannels??new Set,this.beeosFileResolver=e.beeosFileResolver,this.agentFetch=e.agentFetch,this.agentGatewayUrl=e.agentGatewayUrl,this.triggerAgentSync=e.triggerAgentSync,this.sessionMutationCoordinator=e.sessionMutationCoordinator,this.localSessionHistory=new LocalSessionHistory({logger:this.logger}),this.cronOverrides=createCronOverridesStore(this.logger),this.mcp=new McpService({transport:this.transport,logger:this.logger,state:this.state}),this.transport.setSessionUpdateObserver((e,t,s)=>{const r=s?.turnId;if(!r)return;const n=this.state.sessions.get(e);n&&(recordTurnIdForRole(n,t,r),recordToolCallTurnForUpdate(n,t,r))})}resolvePlatformAgentId(e){return this.agentMapping?.get(e)?.id}getDefaultAgentId(){return this.agentMapping&&this.agentMapping.size>0?this.agentMapping.keys().next().value??"main":"main"}registerSession(e,t={}){const{ensureVerbose:s=!0}=t;this.state.sessions.has(e.sessionId);this.state.sessions.set(e.sessionId,e),this.state.sessionsByGatewayKey.set(e.gatewaySessionKey,e),s&&this.ensureVerboseFull(e),this.subscribeSessionMessages(e)}async ensureVerboseFull(e){if(e.verbosePatched)return;const t=this.getGateway();if(t?.isConnected){e.verbosePatched=!0;try{await t.sessionsPatch(e.gatewaySessionKey,{verboseLevel:"full"})}catch(t){e.verbosePatched=!1,this.logger.debug?.(`[bridge-core] ensureVerboseFull failed: ${t instanceof Error?t.message:t}`)}}}ensureSessionForKey(e){const t=this.state.findSessionByKey(e);if(t)return t;const s=createSessionObject({gatewayKey:e,instanceId:this.instanceId,resolvePlatformAgentId:e=>this.resolvePlatformAgentId(e),thirdPartyChannels:this.thirdPartyChannels});return this.registerSession(s),this.logger.debug?.(`[bridge-core] ensureSessionForKey: lazily created session sid=${s.sessionId} key=${e}`),s}async preloadSessionsFromGateway(){const e=this.getGateway();if(e?.isConnected)try{const t=await e.sessionsList();if(!t||"object"!=typeof t)return;const s=t.sessions;if(!Array.isArray(s))return;let r=0;for(const e of s){const t=e,s=t.sessionKey??t.key??t.id??t.name;if(!s||"string"!=typeof s)continue;if(this.state.findSessionByKey(s))continue;const n=t.label||t.derivedTitle||t.title||void 0,i=n&&!isPlaceholderTitle(n)?n:void 0,o=createSessionObject({gatewayKey:s,instanceId:this.instanceId,resolvePlatformAgentId:e=>this.resolvePlatformAgentId(e),thirdPartyChannels:this.thirdPartyChannels,title:i});this.registerSession(o,{ensureVerbose:!1}),r++}r>0&&this.logger.info?.(`[bridge-core] preloadSessions: loaded ${r} session(s) from gateway`)}catch(e){this.logger.debug?.("[bridge-core] preloadSessions failed:",e)}else this.logger.debug?.("[bridge-core] preloadSessions skipped: gateway not connected")}async handleRequest(e){this.writeObs({component:"bridge-core",domain:"forward",name:`rpc.request.${e.method}`,severity:"debug",payload:{id:e.id,method:e.method}});const t=e;try{switch(t.method){case"initialize":this.handleInitialize(t);break;case"ping":this.transport.sendResult(t.id,{pong:!0});break;case"session/new":await this.handleSessionNew(t);break;case"session/load":await this.handleSessionLoad(t);break;case"session/list":await this.handleSessionList(t);break;case"session/rename":this.handleSessionRename(t);break;case"session/delete":await this.handleSessionDelete(t);break;case"session/clear":await this.handleSessionClear(t);break;case"session/prompt":await this.handleSessionPrompt(t);break;case"session/set_mode":await this.handleSessionSetMode(t);break;case"session/set_model":await this.handleSessionSetModel(t);break;case"agent/create":await this.handleAgentCreate(t);break;case"agent/update":await this.handleAgentUpdate(t);break;case"agent/delete":await this.handleAgentDelete(t);break;case"agent/applyTemplate":await this.handleApplyTemplate(t);break;case"skills/list":await this.handleSkillsList(t);break;case"skills/install":await this.handleSkillsInstall(t);break;case"skills/uninstall":await this.handleSkillsUninstall(t);break;case"skills/update":await this.handleSkillsUpdate(t);break;case"models/list":await this.handleModelsList(t);break;case"session/cancel":await this.handleSessionCancel(t.id,t.params);break;case"cron/list":case"cron/status":case"cron/add":case"cron/update":case"cron/remove":case"cron/run":case"cron/runs":await this.handleCronRequest(t);break;default:"agent/list"===e.method?await this.handleAgentList(e):"_beeos/session/history"===e.method?await this.handleSessionHistoryPage(e):"mcp/list"===e.method?await this.mcp.handleList(e):"mcp/set"===e.method?await this.mcp.handleSet(e):"mcp/unset"===e.method?await this.mcp.handleUnset(e):e.method.startsWith("terminal/")&&this.handleTerminalRequest?await this.handleTerminalRequest(e):this.transport.sendAcpError(e.id,Errors.MethodNotFound,{message:`Method not found: ${e.method}`})}}catch(t){this.transport.sendAcpError(e.id,Errors.InternalError,{message:t instanceof Error?t.message:"Internal error"})}}handleCanvasNotification=null;getCanvasState=null;onSessionLoaded=null;onSkillInstalled=null;onSessionDeleted=null;onSessionCleared=null;handleNotification(e){switch(e.method){case"session/cancel":this.observeLifecycle(this.handleSessionCancel(void 0,e.params),"session_cancel_notification");break;case"canvas/toggle":case"canvas/clear":this.handleCanvasNotification?this.handleCanvasNotification(e.method,e.params):this.logger.debug?.(`[bridge-core] Canvas notification ignored (canvas disabled): ${e.method}`);break;case"canvas/reference":{const t=e.params,s=t?.surfaceId,r=this.state.resolveSessionFromParams(t);if(!r){this.logger.warn?.(`[bridge-core] canvas/reference ignored — no session found for sessionId=${t?.sessionId}`);break}r.referencedSurfaceId=s||void 0,this.logger.debug?.(`[bridge-core] Canvas reference ${s?"set":"cleared"}: ${s??"(none)"}`);break}default:e.method.startsWith("terminal/")&&this.handleTerminalNotification?this.handleTerminalNotification(e.method,e.params):this.logger.debug?.(`[bridge-core] Unknown notification: ${e.method}`)}}handleInitialize(e){this.initialized=!0;const t=this.transport.forwardControl;!1===e.params?.forwardThinking&&(t.forwardThinking=!1),!1===e.params?.forwardToolCalls&&(t.forwardToolCalls=!1),this.transport.sendResult(e.id,{protocolVersion:ACP_PROTOCOL_VERSION,agentCapabilities:{loadSession:!0,skills:!0,multipleSessions:!0,sessionCapabilities:{"web-ssh":!!this.config.bridge.shell.enabled,canvas:!!this.config.canvasEnabled},promptCapabilities:{image:!0,audio:!1,embeddedContext:!0},mcp:{}},agentInfo:{name:"beeos-claw",title:"BeeOS Agent Bridge",version:"0.1.0"},_meta:{},authMethods:[]})}async handleSessionNew(e){const t=`agent:${"string"==typeof e.params?.agentId&&e.params.agentId||this.config.gateway.agentId||this.getDefaultAgentId()}:${randomUUID()}`,s="string"==typeof e.params?.cwd?e.params.cwd:".",r=createSessionObject({gatewayKey:t,instanceId:this.instanceId,resolvePlatformAgentId:e=>this.resolvePlatformAgentId(e),thirdPartyChannels:this.thirdPartyChannels,cwd:s});this.registerSession(r,{ensureVerbose:!1}),this.transport.sendResult(e.id,{sessionId:r.sessionId,modes:{availableModes:[{...DEFAULT_MODE}],currentModeId:DEFAULT_MODE.id},_meta:{sessionKey:t,agentId:r.agentId,platformAgentId:r.platformAgentId??null}}),this.fetchHistoryInBackground(t)}fetchHistoryInBackground(e){const t=this.getGateway();t?.isConnected&&loadHistory(t,e,50).catch(e=>{this.logger.debug?.(`[bridge-core] Background history fetch failed: ${e instanceof Error?e.message:e}`)})}async subscribeSessionMessages(e){if(e.messageSubscriptionActive)return;const t=this.getGateway();if(t?.isConnected)try{await t.sessionsMessagesSubscribe(e.gatewaySessionKey),e.messageSubscriptionActive=!0,this.logger.debug?.(`[bridge-core] session.message subscribed sessionKey=${e.gatewaySessionKey}`)}catch(t){this.logger.debug?.(`[bridge-core] session.message subscribe failed key=${e.gatewaySessionKey}: ${t instanceof Error?t.message:t}`)}}async resubscribeAllSessionMessages(){for(const e of this.state.sessions.values())e.messageSubscriptionActive=!1,await this.subscribeSessionMessages(e)}handleSessionMessage(e){const t=e?.sessionKey,s=e?.messageId;if(!t||!s)return;const r=this.state.sessionsByGatewayKey.get(t);if(!r)return;const n=e.message?.role;if("toolResult"===n||"tool"===n)return void this.handlePersistedToolResultMessage(r,e).catch(e=>{this.logger.warn?.(`[bridge-core] persisted toolResult handling failed sessionKey=${t} messageId=${s}: ${e instanceof Error?e.message:String(e)}`)});const i="user"===n?"user":"system"===n?"system":"assistant",o=r.lastTurnIdByRole?.[i];if(!o)return void this.logger.debug?.(`[bridge-core] session.message skipped: no lastTurnId role=${i} sessionKey=${t} messageId=${s}`);const a=e.message?.timestamp,l="number"==typeof a&&a>0?new Date(a).toISOString():void 0,d={turnId:o};"number"==typeof e.messageSeq&&(d.seq=e.messageSeq),this.transport.sendSessionUpdate(r.sessionId,{sessionUpdate:"message_id_bind",role:i,messageId:s,...l?{timestamp:l}:{}},d),r.lastTurnIdByRole&&(r.lastTurnIdByRole[i]=void 0),"assistant"===i&&reconcileMissingArtifacts({localHistory:this.localSessionHistory,transport:this.transport,config:this.config,logger:this.logger,writeObs:this.writeObs,getCanvasState:()=>this.getCanvasState?.()??null,isSessionAlive:e=>this.state.sessions.has(e)},{session:r,turnId:o,messageId:s,runId:void 0})}async handlePersistedToolResultMessage(e,t){const s=t.messageId;if(!s)return;const r=extractPersistedToolResultEnvelope(t.message),n=this.extractPersistedToolResultFromHistory(e,s),i=mergePersistedToolResultEnvelopes(n,r);if(!i?.toolCallId)return void this.writeObs({component:"bridge-core",domain:"tool_result",name:"tool_result.skipped_no_tool_call_id",severity:"warn",payload:{sessionId:e.sessionId,messageId:s}});n&&this.writeObs({component:"bridge-core",domain:"tool_result",name:"tool_result.hydrated_from_history",severity:"debug",payload:{sessionId:e.sessionId,messageId:s,toolCallId:i.toolCallId,toolName:i.toolName}});const o=e.toolCallTurns?.get(i.toolCallId),a=o?.turnId??resolveTurnId(e);if(!a)return void this.writeObs({component:"bridge-core",domain:"tool_result",name:"tool_result.skipped_no_turn",severity:"warn",payload:{sessionId:e.sessionId,messageId:t.messageId,toolCallId:i.toolCallId,toolName:i.toolName}});const l=projectPersistedToolResultForReplay({toolCallId:i.toolCallId,toolName:i.toolName||o?.toolName||"tool",...i.rawText?{rawText:i.rawText}:{},...i.rawResult?{rawResult:i.rawResult}:{},...i.timestamp?{timestamp:i.timestamp}:{}});await emitToolResultProjectionForTurn({session:e,projection:l,toolCallId:i.toolCallId,turnId:a,config:this.config,logger:this.logger,writeObs:this.writeObs,getCanvasState:()=>this.getCanvasState?.()??null,sendUpdate:s=>(a&&void 0===s._turnId&&(s._turnId=a),this.transport.sendSessionUpdate(e.sessionId,s,{turnId:a,..."number"==typeof t.messageSeq?{seq:t.messageSeq}:{}}))})}extractPersistedToolResultFromHistory(e,t){if(!e.agentId)return;const s=this.localSessionHistory.readEntries({agentId:e.agentId,sessionKey:e.gatewaySessionKey,sessionId:e.sessionId});return findPersistedToolResultEnvelopeByMessageId(s,t)}async recoverSessionFromGateway(e){const t=this.getGateway();if(t?.isConnected)try{const s=await t.sessionsList();if(!s||"object"!=typeof s)return;const r=s.sessions;if(!Array.isArray(r))return;for(const t of r){const s=t,r=s.sessionKey??s.key??s.id??s.name;if(r&&"string"==typeof r&&deriveSessionId(r,this.instanceId)===e){const e=s.label||s.derivedTitle||s.title||void 0,t=e&&!isPlaceholderTitle(e)?e:void 0,n=createSessionObject({gatewayKey:r,instanceId:this.instanceId,resolvePlatformAgentId:e=>this.resolvePlatformAgentId(e),thirdPartyChannels:this.thirdPartyChannels,title:t});return this.registerSession(n),n}}}catch(e){this.logger.debug?.("[bridge-core] recoverSessionFromGateway failed:",e)}}async resolveOrCreateSession(e){let t=this.state.sessions.get(e);if(!t&&e.startsWith("agent:")&&(t=this.state.findSessionByKey(e)),t||e.startsWith("agent:")||(t=await this.recoverSessionFromGateway(e)),t)return{session:t,created:!1};const s=e.startsWith("agent:"),r=this.config.gateway.agentId||this.getDefaultAgentId();return t=createSessionObject({gatewayKey:s?e:`agent:${r}:${e}`,instanceId:this.instanceId,resolvePlatformAgentId:e=>this.resolvePlatformAgentId(e),thirdPartyChannels:this.thirdPartyChannels}),this.registerSession(t),{session:t,created:!0}}async handleSessionLoad(e){const t=e.params?.sessionId;if(!t)return void this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"sessionId"});const{session:s}=await this.resolveOrCreateSession(t),r=s.sessionId;this.state.lastActiveSessionId=r,s.chatReplayDedupKeys.clear(),s.sessionLoadInProgress=!0;try{const e=await this.emitHistoryTurns(s);this.emitCurrentStreamSnapshot(s,e)}finally{s.sessionLoadInProgress=!1}this.transport.sendResult(e.id,{sessionId:r}),this.onSessionLoaded?.(r,s.gatewaySessionKey,s.platformAgentId),this.pushSkillsList().catch(e=>{this.logger.warn?.(`[bridge-core] pushSkillsList failed: ${e instanceof Error?e.message:e}`)})}async emitHistoryTurns(e){const t=new Set,s=e.sessionId;try{const r=this.getGateway(),n=e.assistantMq&&!e.assistantMq.closed,i=n?null:r,o=await loadHistory(i,e.gatewaySessionKey,100,{localHistory:this.localSessionHistory,agentId:e.agentId,sessionId:e.sessionId,logger:this.logger,emitObs:e=>{this.writeObs({component:"history-replay",domain:"lifecycle",name:e.name,severity:e.severity,payload:e.payload})}}),a=groupIntoTurns(o),l=a.filter(e=>"user"===e.role).length,d=a.filter(e=>"assistant"===e.role).length,c=a.filter(e=>e.toolCalls?.length).length,g=a.reduce((e,t)=>e+(t.toolCalls?.length??0),0);if(this.logger.debug?.(`[bridge-core] handleSessionLoad sid=${s} messages=${o.length} turns=${a.length} userTurns=${l} asstTurns=${d} turnsWithToolCalls=${c} totalToolCalls=${g}`),l>0&&e.pendingUserTurnUpdates&&e.pendingUserTurnUpdates.length>0&&(e.pendingUserTurnUpdates=[]),a.length>0&&isPlaceholderTitle(e.title)&&"manual"!==e.titleSource){const t=a.find(e=>{if("user"!==e.role)return!1;if("internal_system"===e.provenance?.kind)return!1;if(isCronPromptText(e.content))return!1;const t=cleanHistoryUserContent(e.content);return t.length>0&&!/^Sender\s*\(untrusted/i.test(t)});if(t?.content){const r=cleanHistoryUserContent(t.content).slice(0,64).replace(/\n/g," ").trim();if(r){e.title=r,e.titleSource="truncated";const t=this.getGateway();t?.isConnected&&t.sessionsPatch(e.gatewaySessionKey,{label:r}).catch(()=>{}),this.transport.sendSessionUpdate(s,{sessionUpdate:"title_update",title:r}),this.tryGenerateTitleFromHistory(e,s,a)}}}const p=t=>`history:${e.gatewaySessionKey}:${t}`;let m=-1;if(n)for(let e=a.length-1;e>=0;e--)if("assistant"===a[e].role){m=e;break}const{replayedToolCallIds:h,streamCompleteCount:u}=await this.replayHistoryTurnsToSink(e,a,this.transport,{mqActive:Boolean(n),lastAsstIdx:m,turnIdFor:(e,t)=>p(t)});for(const e of h)t.add(e);this.logger.debug?.(`[bridge-core] emitHistoryTurns DONE sid=${s} historyStreamCompleteEmitted=${u} replayedToolCallIds=${t.size}`)}catch(e){this.logger.warn?.("[bridge-core] Failed to load session history:",e)}return t}async replayHistoryTurnsToSink(e,t,s,r){const n=e.sessionId,i=new Set;let o=0;for(let a=0;a<t.length;a++){const l=t[a],d=r.turnIdFor(l,a);if(r.mqActive&&a===r.lastAsstIdx){const e=classifyHistoricalAssistantTurnCompletion(l,{mqActive:r.mqActive,isSkippedForLiveMq:!0});this.logger.debug?.(`[bridge-core] SKIP history_turn[${a}] assistant — ${"live"===e.kind?e.reason:"mq active"}, deferring to emitCurrentStreamSnapshot`);continue}if("user"===l.role&&"internal_system"===l.provenance?.kind){this.logger.debug?.(`[bridge-core] SKIP history_turn[${a}] internal_system user message`);continue}if("user"===l.role&&/^\s*system\s*\(untrusted[^)]*\)/i.test(l.content)){this.logger.debug?.(`[bridge-core] SKIP history_turn[${a}] system-injected user message`);continue}if("user"===l.role&&isHeartbeatPromptText(l.content)){this.logger.info?.(`[bridge-core] SKIP history_turn[${a}] heartbeat cron prompt sessionId=${e.sessionId} contentLen=${l.content.length} preview="${previewSanitizedText(l.content)}"`);continue}if("user"===l.role&&isCronPromptText(l.content)){this.logger.info?.(`[bridge-core] SKIP history_turn[${a}] cron-triggered user message sessionId=${e.sessionId} contentLen=${l.content.length} preview="${previewSanitizedText(l.content)}"`);continue}if("assistant"===l.role){const t=classifyHeartbeatFilter(l.content);if(t.filtered){this.logger.info?.(`[bridge-core] SKIP history_turn[${a}] heartbeat assistant reply reason=${t.reason} sessionId=${e.sessionId} contentLen=${l.content.length} preview="${previewSanitizedText(l.content)}"`);continue}}const c="assistant"===l.role?classifyHistoricalAssistantTurnCompletion(l,{mqActive:r.mqActive,isSkippedForLiveMq:!1}):void 0;if("error"===c?.kind||"interrupted"===c?.kind&&c.failure)s.sendSessionUpdate(n,{sessionUpdate:"agent_message_chunk",content:{type:"text",text:""},...l.timestamp?{timestamp:l.timestamp}:{}},{turnId:d,...void 0!==l._seq?{seq:l._seq}:{}}),s.sendSessionUpdate(n,{sessionUpdate:"stream_complete",stopReason:c.stopReason,error:c.message,errorCode:c.errorCode},{turnId:d}),this.logger.info?.(`[bridge-core] history_runtime_failure_terminalized session_id=${n} turn_id=${d} error_code=${c.errorCode} stop_reason=${c.stopReason} source=history_replay`),o+=1;else if("system"!==l.role)if(this.logger.debug?.(`[bridge-core] SENDING history_turn[${a}] role=${l.role} contentLen=${l.content.length} content=${JSON.stringify(l.content.slice(0,80))} tcCount=${l.toolCalls?.length??0}`),"user"===l.role)this.replayUserTurnAsChunks(s,n,l.content,l.metadata,l.timestamp,d,l._seq);else{if(await this.replayAssistantTurnAsStream(s,n,l,d),l.toolCalls)for(const e of l.toolCalls)e.id&&i.add(e.id);const e=c??classifyHistoricalAssistantTurnCompletion(l,{mqActive:r.mqActive,isSkippedForLiveMq:!1});if("interrupted"===e.kind){const t=new Set(e.incompleteToolCallIds);for(const r of l.toolCalls??[])t.has(r.id)&&s.sendSessionUpdate(n,buildToolCallUpdatePayload({toolCallId:r.id,toolName:r.name,outputText:e.message,kind:r.kind,status:"failed",timestamp:l.timestamp}),{turnId:d});this.logger.info?.(`[bridge-core] history_orphan_turn_terminalized session_id=${n} turn_id=${d} tool_call_ids=${e.incompleteToolCallIds.join(",")} stop_reason=${e.stopReason} source=history_replay`),s.sendSessionUpdate(n,{sessionUpdate:"stream_complete",stopReason:e.stopReason,error:e.message,errorCode:e.errorCode},{turnId:d}),o+=1;continue}if("completed"!==e.kind)continue;s.sendSessionUpdate(n,{sessionUpdate:"stream_complete",stopReason:e.stopReason},{turnId:d}),o+=1}else this.logger.debug?.(`[bridge-core] SENDING system_message[${a}] contentLen=${l.content.length}`),s.sendSessionUpdate(n,{sessionUpdate:"system_message_chunk",content:{type:"text",text:l.content},metadata:l.metadata,...l.timestamp?{timestamp:l.timestamp}:{}},{turnId:d,...void 0!==l._seq?{seq:l._seq}:{}})}return{replayedToolCallIds:i,streamCompleteCount:o}}async handleSessionHistoryPage(e){const t=e.params??{},s="string"==typeof t.sessionId?t.sessionId:"";if(!s)return void this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"sessionId"});const r="string"==typeof t.cursor&&t.cursor.length>0?t.cursor:void 0,n="number"==typeof t.limit&&Number.isFinite(t.limit)&&t.limit>0?Math.min(1e3,Math.floor(t.limit)):100,{session:i}=await this.resolveOrCreateSession(s),o=this.getGateway(),a=await loadHistoryPage(o,i.gatewaySessionKey,{...r?{cursor:r}:{},limit:n,logger:this.logger}),l=groupIntoTurns(a.messages),d=new HistoryUpdateRecorder(this.transport);await this.replayHistoryTurnsToSink(i,l,d,{mqActive:!1,lastAsstIdx:-1,turnIdFor:(e,t)=>void 0!==e._seq?`history:${i.gatewaySessionKey}:seq:${e._seq}`:`history:${i.gatewaySessionKey}:seq:none:${r??"head"}:${t}`}),this.logger.debug?.(`[bridge-core] _beeos/session/history sid=${i.sessionId} cursor=${r??"<head>"} limit=${n} turns=${l.length} updates=${d.updates.length} hasMore=${a.hasMore} nextCursor=${a.nextCursor??"<none>"}`),this.transport.sendResult(e.id,{updates:d.updates,...a.nextCursor?{nextCursor:a.nextCursor}:{},hasMore:a.hasMore})}emitCurrentStreamSnapshot(e,t){const s=e.pendingUserTurnUpdates;if(s&&s.length>0)for(const t of s)this.transport.sendSessionUpdate(e.sessionId,t);const r=e.assistantMq;if(r&&!r.closed){const s=r.peek(0);let n=0;if(t&&t.size>0)for(let e=0;e<s.length;e++){const r=s[e],i=r.sessionUpdate,o=r.toolCallId;if("tool_call"!==i&&"tool_call_update"!==i||!o||!t.has(o)){if("agent_message_chunk"===i){if(""===(r.content?.text??"").trim()&&e<s.length-1){const r=s[e+1];if(("tool_call"===r.sessionUpdate||"tool_call_update"===r.sessionUpdate)&&r.toolCallId&&t.has(r.toolCallId)){n=e+1;continue}}break}break}n=e+1}const i=resolveTurnId(e),o=i?{turnId:i}:void 0;for(let t=n;t<s.length;t++)this.transport.sendSessionUpdate(e.sessionId,s[t],o)}}async handleSessionList(e){const t=[];for(const[,e]of this.state.sessions)isBackgroundSessionKey(e.gatewaySessionKey)||this.cronIsolatedSessionKeys.has(e.gatewaySessionKey)||t.push({sessionId:e.sessionId,title:e.title||"New chat",cwd:e.cwd,updatedAt:e.lastStreamActivityTs??e.createdAt,agentId:e.agentId,platformAgentId:e.platformAgentId,deletable:!isSystemDefaultSessionKey(e.gatewaySessionKey)});const s=this.getGateway();if(s?.isConnected)try{const e=await s.sessionsList();if(e&&"object"==typeof e&&Array.isArray(e.sessions)){const s=e.sessions;for(const e of s){const s=e,r=s.sessionKey??s.key??s.id??s.name;if(!r||"string"!=typeof r)continue;if(isBackgroundSessionKey(r))continue;if(this.cronIsolatedSessionKeys.has(r))continue;const n=deriveSessionId(r,this.instanceId),i=t.find(e=>e.sessionId===r||e.sessionId===n||this.state.sessions.get(e.sessionId)?.gatewaySessionKey===r),o=s.label||cleanGatewayDerivedTitle(s.derivedTitle)||s.title||void 0,a=o&&!isPlaceholderTitle(o)?o:void 0,l=s.model,d=s.modelProvider;if(i){if(a&&isPlaceholderTitle(i.title)){i.title=a;const e=this.state.sessions.get(i.sessionId);e&&isPlaceholderTitle(e.title)&&(e.title=a)}const e=s.updatedAt;e&&(i.updatedAt=e),l&&(i.model=l),d&&(i.modelProvider=d)}else{const e=createSessionObject({gatewayKey:r,instanceId:this.instanceId,resolvePlatformAgentId:e=>this.resolvePlatformAgentId(e),thirdPartyChannels:this.thirdPartyChannels,cwd:s.cwd,title:a});this.registerSession(e,{ensureVerbose:!1}),t.push({sessionId:e.sessionId,title:a||"",cwd:s.cwd,updatedAt:s.updatedAt,agentId:e.agentId,platformAgentId:e.platformAgentId,deletable:!isSystemDefaultSessionKey(r),model:l,modelProvider:d})}}}}catch(e){this.logger.debug?.("[bridge-core] sessions.list enrichment failed:",e)}t.sort((e,t)=>(t.updatedAt??0)-(e.updatedAt??0)||e.sessionId.localeCompare(t.sessionId)),this.transport.sendResult(e.id,{sessions:t})}handleSessionRename(e){const t=e.params?.sessionId,s=e.params?.title;if(!t||!s)return void this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"sessionId, title"});const r=s.slice(0,64),n=this.state.sessions.get(t);if(!n)return void this.transport.sendAcpError(e.id,Errors.SessionNotFound);n.title=r,n.titleSource="manual";const i=n.gatewaySessionKey,o=this.getGateway();o?.isConnected&&o.sessionsPatch(i,{label:r}).catch(e=>{this.logger.debug?.(`[bridge-core] sessionsPatch (rename) failed: ${e instanceof Error?e.message:e}`)}),this.transport.sendResult(e.id,{sessionId:t,title:r})}static TITLE_TEMPLATES=[{match:["今日日报","日报"],zh:"今日工作日报",en:"Daily Work Log"},{match:["周报","周报总结","本周周报"],zh:"本周工作周报",en:"Weekly Work Report"},{match:["月报","月度汇报","月度总结"],zh:"本月工作月报",en:"Monthly Work Report"},{match:["工作日志","日志"],zh:"工作日志整理",en:"Work Log Review"},{match:["晨会","今日晨会"],zh:"今日晨会纪要",en:"Daily Standup Notes"},{match:["复盘","今日复盘"],zh:"今日工作复盘",en:"Daily Retrospective"},{match:["新会话","new chat","new conversation"],zh:"新会话",en:"New Conversation"}];static TITLE_SYSTEM_PROMPT='You are a conversation title generator. Your task is to generate a concise, human-readable title summarizing the topic of the user\'s question together with the assistant\'s answer.\n\n### Rules:\n- Length: 3-6 words in English, or 6-20 characters in Chinese (up to 24 if needed to keep all key domain terms intact).\n- Language: Match the user\'s input language exactly. Mixed zh/en input → use the dominant language.\n- Extract the core topic (noun-centric) of the Q&A exchange, not the action verb.\n- Use the assistant\'s answer to disambiguate or enrich the topic when the question alone is vague (e.g. "帮我看下" + an answer about x402 → "x402 支付集成").\n- No punctuation, no quotation marks, no emojis, no markdown.\n- No prefix like "Title:" or "Here is". Output the title only.\n- Avoid generic words: "help", "question", "chat", "problem", "task".\n- If both the question and answer are too vague to summarize, return "新会话" (for zh) or "New Conversation" (for en).\n\n### Input Format:\nThe user message will contain both the question and the assistant\'s answer, delimited like this:\n[User Question]\n...question text...\n\n[Assistant Answer]\n...answer text (may be truncated with "..." in the middle)...\n\n### Output Format:\nRespond ONLY with valid JSON, no other text:\n{"title": "<your title here>"}\n\n### Examples:\nInput:\n[User Question]\n帮我写一个关于BeeOS设备接入的PRD\n\n[Assistant Answer]\n好的,这是 BeeOS 设备接入 PRD 的草稿:1. 目标 ...\nOutput: {"title": "BeeOS 设备接入 PRD"}\n\nInput:\n[User Question]\n帮我看下\n\n[Assistant Answer]\n你说的 x402 支付协议是一个基于 HTTP 402 的链上支付标准 ...\nOutput: {"title": "x402 支付协议解析"}\n\nInput:\n[User Question]\nWhat\'s the best way to integrate x402 payment protocol?\n\n[Assistant Answer]\nYou have three options: 1. Use the official SDK ...\nOutput: {"title": "x402 Payment Integration"}\n\nInput:\n[User Question]\nhi\n\n[Assistant Answer]\nHello! How can I help you today?\nOutput: {"title": "New Conversation"}';matchTitleTemplate(e){const t=e.trim().toLowerCase();for(const s of AcpGatewayBridgeCore.TITLE_TEMPLATES)if(s.match.some(e=>e.toLowerCase()===t))return/[\u4e00-\u9fff]/.test(e)?s.zh:s.en}isGuardMessage(e){const t=e.trim();return[...t].length<3||[...t].every(e=>/[\p{P}\p{S}\s]/u.test(e))}truncateQuestionForTitle(e){return e.length>2e3?e.slice(0,1500):e}truncateAnswerForTitle(e){return e.length<=1e3?e:`${e.slice(0,500)}\n...\n${e.slice(-500)}`}generateTitleFromQA(e,t,s,r){if(this.isGuardMessage(s))return void this.logger.info?.(`[bridge-core] title-gen SKIP guard message sid=${t}`);const n=this.matchTitleTemplate(s);if(n){e.title=n,e.titleSource="template";const s=this.getGateway();return s?.isConnected&&s.sessionsPatch(e.gatewaySessionKey,{label:n}).catch(()=>{}),void this.transport.sendSessionUpdate(t,{sessionUpdate:"title_update",title:n})}const i=(process.env.AROUTER_BASE_URL||"").replace(/\/$/,""),o=process.env.AROUTER_API_KEY||"";if(!i||!o)return void this.logger.info?.(`[bridge-core] title-gen SKIP sid=${t}: no AROUTER env vars`);const a=`[User Question]\n${this.truncateQuestionForTitle(s)}\n\n[Assistant Answer]\n${this.truncateAnswerForTitle(r)}`;this.logger.info?.(`[bridge-core] title-gen CALLING LLM sid=${t} q="${s.slice(0,60)}" aLen=${r.length}`),fetch(`${i}/v1/chat/completions`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${o}`},body:JSON.stringify({model:"openai/gpt-4o-mini",max_tokens:32,temperature:.3,messages:[{role:"system",content:AcpGatewayBridgeCore.TITLE_SYSTEM_PROMPT},{role:"user",content:a}]})}).then(async s=>{if(!s.ok){const e=await s.text().catch(()=>"");return void this.logger.info?.(`[bridge-core] title-gen FAILED sid=${t} status=${s.status} body=${e.slice(0,300)}`)}const r=await s.json(),n=r.choices?.[0]?.message?.content?.trim();if(!n)return;const i=n.replace(/^```json\s*/,"").replace(/```$/,"").trim();let o;try{o=JSON.parse(i).title?.trim()??""}catch{return void this.logger.debug?.(`[bridge-core] title-gen JSON parse failed: ${i}`)}if(!o)return;if("manual"===e.titleSource)return void this.logger.debug?.("[bridge-core] title-gen discarded: user already renamed");this.logger.info?.(`[bridge-core] title-gen OK sid=${t} title="${o}"`),e.title=o,e.titleSource="auto";const a=this.getGateway();a?.isConnected&&a.sessionsPatch(e.gatewaySessionKey,{label:o}).catch(()=>{}),this.transport.sendSessionUpdate(t,{sessionUpdate:"title_update",title:o})}).catch(e=>{this.logger.info?.(`[bridge-core] title-gen ERROR sid=${t}: ${e instanceof Error?e.message:e}`)})}async tryGenerateTitleFromHistory(e,t,s){if("manual"===e.titleSource||"auto"===e.titleSource)return;const r=this.getGateway();if(!r?.isConnected)return;let n=s;if(!n)try{const t=await loadHistory(r,e.gatewaySessionKey);n=groupIntoTurns(t)}catch(e){return void this.logger.debug?.(`[bridge-core] title-gen loadHistory failed sid=${t}: ${e instanceof Error?e.message:e}`)}const i=n.find(e=>{if("user"!==e.role)return!1;if(!e.content?.trim())return!1;if("internal_system"===e.provenance?.kind)return!1;if(isCronPromptText(e.content))return!1;const t=cleanHistoryUserContent(e.content);return!!t&&!/^Sender\s*\(untrusted/i.test(t)});if(!i)return;const o=n.indexOf(i),a=n.slice(o+1).find(e=>"assistant"===e.role&&!!e.content?.trim());if(!a)return;const l=cleanHistoryUserContent(i.content),d=a.content.trim();l&&d&&this.generateTitleFromQA(e,t,l,d)}async handleSessionDelete(e){const t=e.params?.sessionId;if(!t)return void this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"sessionId"});const s=this.state.sessions.get(t);if(!s)return this.logger.info?.(`[bridge-core] session/delete sessionId=${t} NOT FOUND in local state`),void this.transport.sendAcpError(e.id,Errors.SessionNotFound);const r=s.gatewaySessionKey,n=this.sessionMutationCoordinator.tryStartExclusive({sessionKey:r,label:"session_delete"},"acp");if(!n.ok)return void this.sendSessionBusy(s,e.id,"active_session_mutation",n.queueDepth);const i=n.token;try{if(this.hasActiveRun(s)){const t=this.sessionMutationCoordinator.gateSnapshot(r);return void this.sendSessionBusy(s,e.id,"active_session_mutation",t.queueDepth)}this.logger.info?.(`[bridge-core] session/delete sessionId=${t} gatewayKey=${r} gwConnected=${!!this.getGateway()?.isConnected}`);let n=!1;const i=this.getGateway();if(i?.isConnected&&r)try{await i.sessionsDelete(r),n=!0,this.logger.info?.(`[bridge-core] Gateway sessions.delete SUCCESS for key=${r}`)}catch(t){const s=t instanceof Error?t.message:String(t);if(this.logger.info?.(`[bridge-core] Gateway sessions.delete FAILED for key=${r}: ${s}`),!s.includes("Cannot delete the main session"))return void this.transport.sendResult(e.id,{deleted:!1});try{await i.sessionsReset(r),n=!0,this.logger.info?.(`[bridge-core] Gateway sessions.reset SUCCESS for key=${r}`)}catch(t){return this.logger.warn?.(`[bridge-core] Gateway sessions.reset also failed for key=${r}: ${t instanceof Error?t.message:t}`),void this.transport.sendResult(e.id,{deleted:!1})}}else r&&this.logger.warn?.(`[bridge-core] Gateway not connected, proceeding with local-only delete for key=${r}`);this.state.clearPromptTimeout(s),this.state.clearStreamIdleTimer(s),s.assistantMq?.close(),s.assistantMq=void 0,s.currentRunId&&this.state.promptsByRunId.delete(s.currentRunId);const o=new Set;for(const[e,s]of this.state.toolCallToSessionMap)s===t&&(this.state.toolCallToSessionMap.delete(e),o.add(e));for(const[e,t]of this.state.toolCallIdMap)o.has(t)&&this.state.toolCallIdMap.delete(e);this.state.sessions.delete(t),this.state.sessionsByGatewayKey.delete(r),this.transport.resetSessionUpdateCount(t),n&&this.onSessionDeleted?.(r),this.logger.info?.(`[bridge-core] session/delete DONE sessionId=${t} gatewayKey=${r} deleted=true`),this.transport.sendResult(e.id,{deleted:!0})}finally{this.sessionMutationCoordinator.releaseBeforeRuntime(i)}}async handleSessionClear(e){this.logger.info?.(`[bridge-core] handleSessionClear called, params=${JSON.stringify(e.params)}`);const t=e.params?.sessionId;if(!t)return this.logger.warn?.("[bridge-core] session/clear missing sessionId"),void this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"sessionId"});const s=this.state.sessions.get(t);if(!s)return this.logger.warn?.(`[bridge-core] session/clear sessionId=${t} not found in state`),void this.transport.sendAcpError(e.id,Errors.SessionNotFound);this.logger.info?.(`[bridge-core] session/clear sessionId=${t} gatewayKey=${s.gatewaySessionKey}`);const r=this.getGateway(),n=r?.isConnected;if(!s.promptDone&&(void 0!==s.pendingPromptId||void 0!==s.currentRunId)){this.logger.info?.(`[bridge-core] session/clear: cancelling active run sid=${t} runId=${s.currentRunId}`);const i=s.writeGateToken;let o=!1;if(n&&i){const e=await this.sessionMutationCoordinator.requestCancel(i,r);o="recovery_required"===await this.handleCancellationResult(s,e)}else n&&await r.chatAbort(s.gatewaySessionKey,s.currentRunId);return void this.transport.sendError(e.id,Errors.AgentError.rpcCode,o?SESSION_RECOVERY_REQUIRED_MESSAGE:"Cancellation is still converging",o?{code:SESSION_RECOVERY_REQUIRED_CODE,stopReason:"interrupted"}:{code:"session_cancellation_pending",stopReason:"cancelled"})}if(n&&s.gatewaySessionKey)try{await r.sessionsReset(s.gatewaySessionKey),this.logger.info?.(`[bridge-core] Gateway sessions.reset SUCCESS for key=${s.gatewaySessionKey}`)}catch(e){this.logger.warn?.(`[bridge-core] Gateway sessions.reset FAILED for key=${s.gatewaySessionKey}: ${e instanceof Error?e.message:e}`)}else this.logger.warn?.(`[bridge-core] session/clear skipped gateway reset: connected=${n}, key=${s.gatewaySessionKey}`);s.chatReplayDedupKeys.clear(),this.logger.info?.("[bridge-core] session/clear dedupKeys cleared, calling onSessionCleared"),this.onSessionCleared?.(t,s.gatewaySessionKey),this.transport.sendResult(e.id,{cleared:!0}),this.logger.info?.("[bridge-core] session/clear DONE, sent cleared=true")}async handleSessionSetMode(e){const t=e.params?.sessionId,s=e.params?.mode,r=t?this.state.sessions.get(t):void 0,n=this.getGateway();if(r&&n?.isConnected&&s){const e={};if("verbose"===s||"detailed"===s?e.verboseLevel="full":"concise"!==s&&"default"!==s||(e.verboseLevel="default"),"reasoning"!==s&&"thinking"!==s||(e.reasoningLevel="stream"),Object.keys(e).length>0)try{await n.sessionsPatch(r.gatewaySessionKey,e)}catch(e){this.logger.warn?.(`[bridge-core] sessions.patch failed: ${e instanceof Error?e.message:e}`)}}this.transport.sendResult(e.id,{mode:s??"default"})}async handleSessionSetModel(e){const t=e.params?.sessionId,s=e.params?.model,r=t?this.state.sessions.get(t):void 0,n=this.getGateway();let i=s??null,o=null;if(r&&n?.isConnected&&s)try{const e=await n.sessionsPatch(r.gatewaySessionKey,{model:s}),t=e?.resolved;t?.model&&(i=t.model),t?.modelProvider&&(o=t.modelProvider)}catch(e){this.logger.warn?.(`[bridge-core] sessions.patch for model failed: ${e instanceof Error?e.message:e}`)}this.transport.sendResult(e.id,{model:i,modelProvider:o})}async createAgentShell(e,t,s){const r=this.getGateway();if(!r?.isConnected)throw new Error("gateway not connected");const n=`agent-${randomUUID().replace(/-/g,"").slice(0,12)}`,i=n,o=e,a=join(this.getOpenclawHome(),`workspace-${n}`),l=await r.request("agents.create",{name:i,workspace:a,...s?{model:s}:{},...t?.emoji?{emoji:t.emoji}:{},...t?.avatar?{avatar:t.avatar}:{}}),d=l?.agentId??n,c=l?.workspace??a;if(o)try{await r.request("agents.update",{agentId:d,name:o})}catch(e){this.logger.warn?.(`[bridge-core] createAgentShell: failed to set display name for "${d}": ${e instanceof Error?e.message:e}`)}return{agentId:d,workspace:c}}async handleAgentCreate(e){const t=e.params,s=(t?.name??"").trim();if(!s)return void this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:"Missing agent name"});const r=this.getGateway();if(r?.isConnected)try{const{agentId:n,workspace:i}=await this.createAgentShell(s,t?.identity,t?.model),o=t?.files??{};for(const[e,t]of Object.entries(o))if("IDENTITY.md"!==e){if("string"==typeof t)try{await r.request("agents.files.set",{agentId:n,name:e,content:t})}catch(t){this.logger.warn?.(`[bridge-core] agent/create: failed to write "${e}" for "${n}": ${t instanceof Error?t.message:t}`)}}else this.logger.warn?.("[bridge-core] agent/create: ignoring IDENTITY.md in files (identity is set via structured fields)");this.logger.info?.(`[bridge-core] Created agent "${n}" (name="${s}") workspace=${i}`);try{await(this.triggerAgentSync?.())}catch(e){this.logger.warn?.(`[bridge-core] agent/create: agent sync after create failed: ${e instanceof Error?e.message:e}`)}const a=this.agentMapping?.get(n)?.id??"";this.transport.sendResult(e.id,{agentId:n,platformAgentId:a,workspace:i})}catch(t){this.logger.error?.(`[bridge-core] Failed to create agent "${s}": ${t instanceof Error?t.message:t}`),this.transport.sendAcpError(e.id,Errors.InternalError,{message:`Create agent failed: ${t instanceof Error?t.message:String(t)}`})}else this.transport.sendAcpError(e.id,Errors.GatewayNotConnected)}async handleApplyTemplate(e){const t=e.params,s=(t?.name??"").trim(),r=(t?.packageUrl??"").trim();if(!s)return void this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:"Missing agent name"});if(!r)return void this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"packageUrl"});const n=this.getGateway();if(n?.isConnected)try{const n=await fetch(r);if(!n.ok)throw new TemplatePackageError(`failed to download package: HTTP ${n.status}`);const i=Buffer.from(await n.arrayBuffer());if(i.length>MAX_ZIP_BYTES)throw new TemplatePackageError(`package too large: ${i.length} bytes (max ${MAX_ZIP_BYTES})`);const o=t?.sha256?.trim().toLowerCase();if(o){if(createHash("sha256").update(i).digest("hex")!==o)throw new TemplatePackageError("package sha256 mismatch")}const a=parseAndValidateTemplatePackage(i),{agentId:l,workspace:d}=await this.createAgentShell(s,t?.identity,t?.model);let c=0;for(const[e,t]of a.workspaceFiles){const s=resolve(d,e),r=relative(d,s);if(""===r||r.startsWith("..")||path.isAbsolute(r))throw new TemplatePackageError(`refusing to write outside workspace: ${e}`);await mkdir(path.dirname(s),{recursive:!0}),await writeFile(s,t),c++}const g=a.manifest?.slug??"(no manifest)",p=a.manifest?.version??"";this.logger.info?.(`[bridge-core] agent/applyTemplate: applied "${g}${p?`@${p}`:""}" to agent "${l}" (${c} files) workspace=${d}`);try{await(this.triggerAgentSync?.())}catch(e){this.logger.warn?.(`[bridge-core] agent/applyTemplate: agent sync after apply failed: ${e instanceof Error?e.message:e}`)}const m=this.agentMapping?.get(l)?.id??"";this.transport.sendResult(e.id,{agentId:l,platformAgentId:m,workspace:d})}catch(t){if(t instanceof TemplatePackageError)return this.logger.warn?.(`[bridge-core] agent/applyTemplate: invalid package: ${t.message}`),void this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:`Invalid template package: ${t.message}`});this.logger.error?.(`[bridge-core] Failed to apply template for "${s}": ${t instanceof Error?t.message:t}`),this.transport.sendAcpError(e.id,Errors.InternalError,{message:`Apply template failed: ${t instanceof Error?t.message:String(t)}`})}else this.transport.sendAcpError(e.id,Errors.GatewayNotConnected)}async handleAgentUpdate(e){const t=e.params,s=(t?.agentId??"").trim();if(!s)return void this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"agentId"});const r=t?.name?.trim(),n=t?.model?.trim(),i=t?.identity?.emoji?.trim(),o=t?.identity?.avatar?.trim();if(!(r||n||i||o))return void this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:"No updatable fields provided (name/model/identity)"});const a=this.getGateway();if(a?.isConnected)try{const t=await a.request("agents.update",{agentId:s,...r?{name:r}:{},...n?{model:n}:{},...i?{emoji:i}:{},...o?{avatar:o}:{}}),l=t?.agentId??s;this.logger.info?.(`[bridge-core] Updated agent "${l}"${r?` name="${r}"`:""}`);try{await(this.triggerAgentSync?.())}catch(e){this.logger.warn?.(`[bridge-core] agent/update: agent sync after update failed: ${e instanceof Error?e.message:e}`)}this.transport.sendResult(e.id,{agentId:l})}catch(t){this.logger.error?.(`[bridge-core] Failed to update agent "${s}": ${t instanceof Error?t.message:t}`),this.transport.sendAcpError(e.id,Errors.InternalError,{message:`Update agent failed: ${t instanceof Error?t.message:String(t)}`})}else this.transport.sendAcpError(e.id,Errors.GatewayNotConnected)}async handleAgentDelete(e){const t=e.params,s=(t?.agentId??"").trim();if(!s)return void this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"agentId"});if("main"===normalizeAgentId(s))return void this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:'"main" cannot be deleted; delete the instance instead'});const r=this.getGateway();if(!r?.isConnected)return void this.transport.sendAcpError(e.id,Errors.GatewayNotConnected);const n=t?.deleteFiles??!0;try{const t=await r.request("agents.delete",{agentId:s,deleteFiles:n}),i=t?.agentId??s;this.logger.info?.(`[bridge-core] Deleted agent "${i}" (deleteFiles=${n})`);try{await(this.triggerAgentSync?.())}catch(e){this.logger.warn?.(`[bridge-core] agent/delete: agent sync after delete failed: ${e instanceof Error?e.message:e}`)}this.transport.sendResult(e.id,{agentId:i,..."number"==typeof t?.removedBindings?{removedBindings:t.removedBindings}:{}})}catch(t){this.logger.error?.(`[bridge-core] Failed to delete agent "${s}": ${t instanceof Error?t.message:t}`),this.transport.sendAcpError(e.id,Errors.InternalError,{message:`Delete agent failed: ${t instanceof Error?t.message:String(t)}`})}}async handleAgentList(e){const t=this.getGateway();if(t?.isConnected)try{const s=await t.request("agents.list",{}),r=(s?.agents??[]).filter(e=>"string"==typeof e.id&&e.id.length>0).map(e=>{const t=e.identity?.avatarUrl||e.identity?.avatar;return{agentId:e.id,name:e.identity?.name||e.name||e.id,...e.identity?.emoji?{emoji:e.identity.emoji}:{},...t?{avatarUrl:t}:{}}}),n={...s?.defaultId?{defaultId:s.defaultId}:{},agents:r};this.transport.sendResult(e.id,n)}catch(t){this.transport.sendAcpError(e.id,Errors.InternalError,{message:`Failed to list agents: ${t instanceof Error?t.message:t}`})}else this.transport.sendAcpError(e.id,Errors.GatewayNotConnected)}async handleModelsList(e){const t=this.getGateway();if(t?.isConnected)try{const s=await t.request("models.list",{});this.transport.sendResult(e.id,s??{models:[]})}catch(t){this.transport.sendAcpError(e.id,Errors.InternalError,{message:`Failed to list models: ${t instanceof Error?t.message:t}`})}else this.transport.sendAcpError(e.id,Errors.GatewayNotConnected)}async handleSkillsList(e){const t=e.params?.agentId,s=await this.fetchSimplifiedSkills(t);this.transport.sendResult(e.id,{skills:s})}async fetchSimplifiedSkills(e){const t=this.getGateway();if(!t?.isConnected)return[];try{const s=await t.skillsStatus(e);if(!s?.skills)return[];const r=s.skills.filter(e=>!1!==e.eligible||!0===e.disabled).sort((e,t)=>e.always&&!t.always?-1:!e.always&&t.always?1:e.name.localeCompare(t.name)).map(e=>({name:e.name,description:e.description,emoji:e.emoji,always:e.always||void 0,bundled:e.bundled||void 0,disabled:e.disabled||void 0,skillKey:e.skillKey||void 0,iconUrl:void 0})),n=await this.getSkillsDir(e);for(const e of r){const t=e.skillKey??e.name;try{const s=await readFile(join(n,t,".beeos-meta.json"),"utf8"),r=JSON.parse(s);r?.iconUrl&&(e.iconUrl=r.iconUrl)}catch{}}return r}catch(e){return this.logger.warn?.(`[bridge-core] Failed to fetch skills: ${e instanceof Error?e.message:e}`),[]}}async pushSkillsList(e){const t=await this.fetchSimplifiedSkills(e);this.transport.sendNotification("session/update",{sessionId:"_system",update:{sessionUpdate:"available_commands_update",commands:t}}),this.writeObs({component:"bridge-core",domain:"lifecycle",name:"skills.pushed",severity:"debug",payload:{count:t.length}})}getOpenclawHome(){return process.env.OPENCLAW_HOME||join(homedir(),".openclaw")}async getSkillsDir(e){const t=this.getOpenclawHome();if(!e||"main"===e)return join(t,"skills");const s=await this.resolveAgentWorkspaceDir(e);return join(s??join(t,`workspace-${e}`),"skills")}async resolveAgentWorkspaceDir(e){const t=this.getGateway();if(!t?.isConnected)return null;try{const s=await t.request("agents.list",{}),r=normalizeAgentId(e),n=s?.agents?.find(e=>normalizeAgentId(e.id)===r);return n?.workspace??null}catch(t){return this.logger.warn?.(`[bridge-core] resolveAgentWorkspaceDir("${e}") failed: ${t instanceof Error?t.message:t}`),null}}async handleSkillsInstall(e){const t=e.params,s=t?.zipUrl,r=t?.files,n=t?.agentId;let i;try{i=validateSkillSlug(t?.slug||t?.name||"")}catch{return void this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:"Invalid or missing skill slug"})}if(!r?.length&&!s)return void this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:"Missing skill files or zipUrl"});const o=resolveSkillInstallDir(await this.getSkillsDir(n),i);try{if(await mkdir(o,{recursive:!0}),s){const e=await fetchWithRetry(s,{maxRetries:4,logger:this.logger});await extractZipToDir(e,o)}else if(r?.length){const e=resolve(o);for(const t of r){const s=join(o,t.path);if(!resolve(s).startsWith(e+sep))throw new Error(`File path escapes skill directory: ${t.path}`);await mkdir(join(s,".."),{recursive:!0});const r=await fetch(t.downloadUrl,{signal:AbortSignal.timeout(12e4)});if(!r.ok)throw new Error(`Failed to download ${t.path}: ${r.status}`);const n=Buffer.from(await r.arrayBuffer());await writeFile(s,n)}}if(t?.iconUrl)try{await writeFile(join(o,".beeos-meta.json"),JSON.stringify({iconUrl:t.iconUrl}),"utf8")}catch{}this.logger.info?.(`[bridge-core] Installed skill "${i}"${n?` for agent "${n}"`:""} via ${s?"zip":"files"}`),this.transport.sendResult(e.id,{success:!0}),this.onSkillInstalled?.(i,o),await this.pushSkillsList(n)}catch(t){this.logger.error?.(`[bridge-core] Failed to install skill "${i}": ${t instanceof Error?t.message:t}`),this.transport.sendAcpError(e.id,Errors.InternalError,{message:`Install failed: ${t instanceof Error?t.message:String(t)}`})}}async handleSkillsUninstall(e){const t=e.params,s=t?.agentId;let r;try{r=validateSkillSlug(t?.slug||t?.name||"")}catch{return void this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:"Invalid or missing skill name"})}const n=resolveSkillInstallDir(await this.getSkillsDir(s),r),i=resolveSkillInstallDir(join(homedir(),".agents","skills"),r);try{let t=!1;try{const e=await lstat(n);if(e.isSymbolicLink()){const e=await readlink(n);await rm(n,{force:!0}),await rm(e,{recursive:!0,force:!0}),t=!0}else e.isDirectory()&&(await rm(n,{recursive:!0,force:!0}),t=!0)}catch{}try{const e=await lstat(i);(e.isDirectory()||e.isSymbolicLink())&&(await rm(i,{recursive:!0,force:!0}),t=!0)}catch{}t||this.logger.warn?.(`[bridge-core] Skill "${r}" not found in any known location`),this.logger.info?.(`[bridge-core] Uninstalled skill "${r}"${s?` for agent "${s}"`:""}`),this.transport.sendResult(e.id,{success:!0}),await this.pushSkillsList(s)}catch(t){this.logger.error?.(`[bridge-core] Failed to uninstall skill "${r}": ${t instanceof Error?t.message:t}`),this.transport.sendAcpError(e.id,Errors.InternalError,{message:`Uninstall failed: ${t instanceof Error?t.message:String(t)}`})}}async handleSkillsUpdate(e){const t=e.params,s=t?.skillKey,r=t?.enabled,n=t?.agentId;if(!s||"boolean"!=typeof r)return void this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:"Missing skillKey or enabled"});const i=this.getGateway();if(i?.isConnected)try{const t=await i.skillsUpdate(s,r,n);this.logger.info?.(`[bridge-core] Updated skill "${s}" enabled=${r}${n?` for agent "${n}"`:""}`),this.transport.sendResult(e.id,{ok:t?.ok??!0,skillKey:s,enabled:r}),await this.pushSkillsList(n)}catch(t){this.logger.error?.(`[bridge-core] Failed to update skill "${s}": ${t instanceof Error?t.message:t}`),this.transport.sendAcpError(e.id,Errors.InternalError,{message:`Update failed: ${t instanceof Error?t.message:String(t)}`})}else this.transport.sendAcpError(e.id,Errors.InternalError,{message:"Gateway not connected"})}async handleSessionPrompt(e){const t=e.params?.sessionId,s=e.params?.prompt;if(!t||!s)return void this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"sessionId, prompt"});const{session:r,created:n}=await this.resolveOrCreateSession(t);n&&this.onSessionLoaded?.(r.sessionId,r.gatewaySessionKey,r.platformAgentId);const i=r.sessionId;this.state.lastActiveSessionId=i,this.onPromptStart?.();const o=this.getGateway();if(!o?.isConnected)return void this.transport.sendAcpError(e.id,Errors.GatewayNotConnected);if(this.hasActiveRun(r)){const t=this.sessionMutationCoordinator.gateSnapshot(r.gatewaySessionKey);return void this.sendSessionBusy(r,e.id,"active_run",t.queueDepth)}try{await this.ensureVerboseFull(r)}catch(t){return this.releaseSessionWriteGate(r),void this.transport.sendAcpError(e.id,Errors.InternalError,{message:`Failed to prepare agent context: ${t instanceof Error?t.message:String(t)}`})}let a=extractPromptBlocks(sanitizePromptPayload(s));const l=a.filter(e=>"text"===e.type&&"text"in e&&"string"==typeof e.text).map(e=>e.text).join("\n");if(r.fileArtifactDisplayMode=prefersChatFileArtifacts(l)?"chat":"auto",!r.title||isPlaceholderTitle(r.title)){const e=a.find(e=>"text"===e.type&&e.text);if(e&&"text"in e&&e.text){const t=stripTransportMetadata(e.text).trim();if(this.logger.info?.(`[bridge-core] title-gen sid=${i} existing="${r.title}" cleanText="${t.slice(0,80)}" isPlaceholder=${isPlaceholderTitle(t)}`),t&&!isPlaceholderTitle(t)){const e=t.slice(0,64).replace(/\n/g," ").trim();r.title=e,r.titleSource="truncated",o?.isConnected&&e&&o.sessionsPatch(r.gatewaySessionKey,{label:e}).catch(e=>{this.logger.debug?.(`[bridge-core] sessionsPatch (title) failed: ${e instanceof Error?e.message:e}`)}),this.transport.sendSessionUpdate(i,{sessionUpdate:"title_update",title:e})}}}const d=buildResolutionPlan(a);if(d.refs.length>0)try{const e=await resolveResources(d,this.writeObs);a=applyResolutions(a,e)}catch(e){this.logger.warn?.(`[bridge-core] Resource pre-resolution failed: ${e instanceof Error?e.message:e}`)}let c;if(this.beeosFileResolver){const e=this.beeosFileResolver.buildResolutionPlan(a);if(e.refs.length>0)try{c=await this.beeosFileResolver.resolveMetadataForPrompt(e)}catch(e){this.logger.warn?.(`[bridge-core] BeeOS file resolution failed: ${e instanceof Error?e.message:e}`)}}const g=toGatewayPrompt(a,{beeosFileResolutionsByUri:c});if("error"in g)return this.releaseSessionWriteGate(r),void this.transport.sendAcpError(e.id,Errors.PromptConvertFailed,{message:g.error});const{message:p,attachments:m}=g,h=resolveStandaloneSlashCommandName(a,p)??resolveSlashCmd(a,p),u="string"==typeof h;if(h&&this.handleSlashCommand){const t=this;if(await(async()=>{try{return await t.handleSlashCommand(h)}catch(e){return t.logger.warn?.(`[bridge-core] /${h} handler failed: ${e instanceof Error?e.message:e}`),!1}})())return this.emitPromptAsUserSessionUpdates(r,a,resolveTurnId(r)),this.transport.sendSessionUpdate(i,{sessionUpdate:"agent_message_chunk",content:{type:"text",text:`Loading ${h}...`},timestamp:(new Date).toISOString()},{turnId:resolveTurnId(r)}),this.transport.sendResult(e.id,{stopReason:"end_turn"}),void this.releaseSessionWriteGate(r)}const y="chat.send";let f=u?p:withUserMessagePrefix(p);const w=this.config.canvasEnabled&&!u&&!0===r.canvasActive&&!r.channelType;if(w){const e=r.promptsSinceCanvasInjection??0;!r.canvasPromptInjected||e>=10?(f=`${CANVAS_TERM_MAPPING}\n\n${f}`,r.canvasPromptInjected=!0,r.promptsSinceCanvasInjection=0,this.logger.debug?.("[bridge-core] Canvas term mapping injected (re-inject cycle)")):r.promptsSinceCanvasInjection=e+1}if(w){const e=this.getCanvasState?.();if(e){const t=e.resolveCanvasForSession(r.gatewaySessionKey);f=`${buildActiveSurfacesContext(e,t)??"[Canvas] Canvas panel is available. Use render_ui for structured/visual content."}\n\n${f}`}const t=r.referencedSurfaceId;t&&(f=`${CANVAS_UPDATE_HINT}\n[IMPORTANT: The user selected canvas surface "${t}" for editing. You MUST call render_ui with id="${t}" to UPDATE this existing surface. Do NOT create a new surface with a different id.]\n${f}`,r.referencedSurfaceId=void 0)}if(!u){const t=parseMentionsParam(e.params?.mentions);if(t.length>0){r.activeMentions=t;f=`${buildMentionInstruction(t)}\n${f}`,this.logger.debug?.(`[bridge-core] delegation mention instruction injected for ${t.length} agent(s)`)}else r.activeMentions=void 0}if(u&&this.logger.info?.(`[bridge-core] slash command passthrough sessionId=${i} command=/${h}`),this.hasActiveRun(r)){const t=this.sessionMutationCoordinator.gateSnapshot(r.gatewaySessionKey);return void this.sendSessionBusy(r,e.id,"active_run_after_prepare",t.queueDepth)}const S=this.sessionMutationCoordinator.tryStartInteractive({sessionKey:r.gatewaySessionKey,label:"session/prompt"},"acp");if(!S.ok)return void this.sendSessionBusy(r,e.id,S.reason,S.queueDepth);r.writeGateToken=S.token,this.state.resetForNewRun(r,i,this.transport),r.pendingPromptId=e.id,r.gatewayMethod=y;const I=resolveTurnId(r);this.emitPromptAsUserSessionUpdates(r,a,I),this.transport.sendSessionUpdate(i,{sessionUpdate:"agent_message_chunk",content:{type:"text",text:""},timestamp:(new Date).toISOString()},I?{turnId:I}:void 0);const v=extractCronJobId(p);v&&(r.cronJobId=v),this.schedulePromptTimeout(r);const E=m.length>0?m.map(e=>({type:e.type,content:e.content,...e.mimeType?{mimeType:e.mimeType}:{},...e.fileName?{fileName:e.fileName}:{}})):void 0,_=`req_${randomUUID().replace(/-/g,"")}`,b={sessionKey:r.gatewaySessionKey,message:f,deliver:!1,idempotencyKey:randomUUID()};E?.length&&(b.attachments=E);if(!o.sendFrame({type:"req",id:_,method:y,params:b}))return this.state.clearPromptTimeout(r),r.pendingPromptId=void 0,r.assistantMq?.close(),r.assistantMq=void 0,r.pendingUserTurnUpdates=void 0,this.releaseSessionWriteGate(r),void this.transport.sendAcpError(e.id,Errors.SendFailed,{message:"failed to send prompt to gateway"});r.gatewayRequestId=_,this.state.promptsByGatewayRequestId.set(_,r)}replayUserTurnAsChunks(e,t,s,r,n,i,o){const a=splitUserReplayBlocks(s),l=i||void 0!==o?{...i?{turnId:i}:{},...void 0!==o?{seq:o}:{}}:void 0;for(const s of a){let i;if("text"===s.type){if(!s.text)continue;i={type:"text",text:s.text}}else i="resource_link"===s.type?{type:"resource_link",uri:s.uri??"",...s.fileName?{name:s.fileName}:{},...s.mimeType?{mimeType:s.mimeType}:{}}:{type:s.type,...s.mimeType?{mimeType:s.mimeType}:{},...s.fileName?{fileName:s.fileName}:{}};e.sendSessionUpdate(t,{sessionUpdate:"user_message_chunk",content:i,...r?{metadata:r}:{},...n?{timestamp:n}:{}},l)}}async replayAssistantTurnAsStream(e,t,s,r){const n=e=>{if(r||void 0!==e)return{...r?{turnId:r}:{},...void 0!==e?{seq:e}:{}}},i=new Set,o=(s,r,o)=>{if(!s?.length)return;const a=n(o);for(const n of s)n.uri&&!i.has(n.uri)&&(i.add(n.uri),e.sendSessionUpdate(t,{sessionUpdate:"agent_message_chunk",content:{type:"resource_link",uri:n.uri,name:n.name??"file",...n.mimeType?{mimeType:n.mimeType}:{}},...r?{timestamp:r}:{}},a))},a=n(s._seq);if(!s.sourceMessages?.length){const r={sessionUpdate:"history_turn",role:s.role,content:s.content,reasoning:s.reasoning,toolCalls:s.toolCalls,...s.timestamp?{timestamp:s.timestamp}:{}};return s.metadata&&(r.metadata=s.metadata),e.sendSessionUpdate(t,r,a),void o(s.files,s.timestamp,s._seq)}for(const r of s.sourceMessages){const i=r.updateType??"agent_message_chunk",a=n(r._seq);switch(i){case"agent_message_chunk":e.sendSessionUpdate(t,{sessionUpdate:"agent_message_chunk",content:{type:"text",text:r.content},...r.timestamp?{timestamp:r.timestamp}:{},...r.metadata?{metadata:r.metadata}:{}},a);break;case"agent_thought_chunk":e.sendSessionUpdate(t,{sessionUpdate:"agent_thought_chunk",content:{type:"text",text:r.content},...r.timestamp?{timestamp:r.timestamp}:{}},a);break;case"tool_call":{const n=r.toolCallId??"",i=s.toolCalls?.find(e=>e.id===n),o=r.toolName??"tool",l=r.content??"";let d={};if(l)try{d=JSON.parse(l)}catch{}e.sendSessionUpdate(t,buildToolCallPayload({toolCallId:n,toolName:o,args:d,kind:i?.kind,status:i?.status??"in_progress",timestamp:r.timestamp}),a);break}case"tool_call_update":{const n=r.toolCallId??"",i=s.toolCalls?.find(e=>e.id===n),l=r.toolName??i?.name??"tool",d=r.content??i?.output??"",c=await this.resolveReplayResourceLinks(n,l,r.files);e.sendSessionUpdate(t,buildToolCallUpdatePayload({toolCallId:n,toolName:l,outputText:d,kind:i?.kind,status:i?.status??"completed",resourceLinks:c,timestamp:r.timestamp}),a),o(c,r.timestamp,r._seq);break}}}o(s.files,s.timestamp,s._seq)}async resolveReplayResourceLinks(e,t,s){let r=s?.map(e=>({uri:e.uri,name:e.name??"file",...e.mimeType?{mimeType:e.mimeType}:{}}));if(r?.length)return r;if(r=await readEnrichmentCache(e).catch(()=>{}),r?.length)return r;if("beeos_upload_file"!==t)return;const n=await readUploadToolResult(e).catch(()=>{});if(!n)return;const i=buildToolResultSessionContentBlocks(t,n).filter(e=>"resource_link"===e.content?.type&&e.content.uri).map(e=>({uri:e.content.uri,name:e.content.name??"file",...e.content.mimeType?{mimeType:e.content.mimeType}:{}}));return i.length>0?i:void 0}emitPromptAsUserSessionUpdates(e,t,s){const r=(new Date).toISOString(),n=s?{turnId:s}:void 0;for(const i of t){const t=this.toUserSessionUpdateContent(i);if(!t)continue;const o={sessionUpdate:"user_message_chunk",content:t,timestamp:r};s&&void 0===o._turnId&&(o._turnId=s),this.transport.sendSessionUpdate(e.sessionId,o,n),e.pendingUserTurnUpdates?.push(o)}}toUserSessionUpdateContent(e){if("text"===e.type){if(!e.text)return;return{type:"text",text:e.text}}if("image"===e.type){const t=e.mimeType??e.mime_type??"application/octet-stream",s=e.fileName??e.file_name??e.name;if(!e.data&&!e.uri)return;return{type:"image",...e.data?{data:e.data}:{},...e.uri?{uri:e.uri}:{},...t?{mimeType:t}:{},...s?{fileName:s}:{}}}if("resource_link"===e.type){if(!e.uri)return;return{type:"resource_link",uri:e.uri,...e.title?{title:e.title}:{},...e.name?{name:e.name}:{},...e.mimeType??e.mime_type?{mimeType:e.mimeType??e.mime_type}:{}}}if("file"===e.type){const t=e.mimeType??e.mime_type??"application/octet-stream",s=e.fileName??e.file_name??e.filename??e.name;if(!e.data&&!e.text&&!e.uri)return;return{type:"file",...e.data?{data:e.data}:{},...e.text?{text:e.text}:{},...e.uri?{uri:e.uri}:{},...t?{mimeType:t}:{},...s?{fileName:s}:{}}}if("resource"===e.type){const t=e.resource??{},s=t.uri??e.uri,r=t.mimeType??t.mime_type??e.mimeType??e.mime_type,n=t.text??e.text,i=t.data??e.data,o=t.fileName??t.file_name??t.filename??t.name??e.fileName??e.file_name??e.filename??e.name;if(!s&&!n&&!i)return;return{type:"resource",resource:{...s?{uri:s}:{},...r?{mimeType:r}:{},...n?{text:n}:{},...i?{data:i}:{},...o?{fileName:o}:{}}}}}async handleSessionCancel(e,t){const s=t&&"object"==typeof t&&"sessionId"in t?t.sessionId:void 0;if(!s)return void(void 0!==e&&this.transport.sendAcpError(e,Errors.MissingParam,{param:"sessionId"}));const r=this.state.sessions.get(s);if(!r)return void(void 0!==e&&this.transport.sendAcpError(e,Errors.SessionNotFound));const n=this.getGateway(),i=r.writeGateToken;if(!n?.isConnected)return i&&(this.sessionMutationCoordinator.requireRecovery(i,"gateway_disconnected","disconnect"),await this.endRun(r,{kind:"interrupted",stopReason:"interrupted",message:SESSION_RECOVERY_REQUIRED_MESSAGE,errorCode:SESSION_RECOVERY_REQUIRED_CODE})),void(void 0!==e&&this.transport.sendAcpError(e,Errors.GatewayNotConnected));const o=i?await this.sessionMutationCoordinator.requestCancel(i,n):await this.sessionMutationCoordinator.requestCancelForSession(r.gatewaySessionKey,n);if(!o.ok&&"reason"in o&&"no_active_mutation"===o.reason)return void(void 0!==e&&this.transport.sendResult(e,{}));if(!o.ok&&"reason"in o&&"not_cancellable"===o.reason){if(void 0!==e){const t=this.sessionMutationCoordinator.gateSnapshot(r.gatewaySessionKey);this.sendSessionBusy(r,e,"exclusive_session_mutation",t.queueDepth)}return}const a=await this.handleCancellationResult(r,o);void 0!==e&&("recovery_required"===a?this.transport.sendError(e,Errors.AgentError.rpcCode,SESSION_RECOVERY_REQUIRED_MESSAGE,{code:SESSION_RECOVERY_REQUIRED_CODE,stopReason:"interrupted"}):"terminal_already_observed"===a?this.transport.sendError(e,Errors.AgentError.rpcCode,"The run is already committing its terminal state",{code:"terminal_committing",stopReason:"interrupted"}):this.transport.sendResult(e,{}))}hasActiveRun(e){return!e.promptDone&&(void 0!==e.pendingPromptId||void 0!==e.currentRunId||void 0!==e.gatewayRequestId)}observeLifecycle(e,t,s){observeLifecyclePromise(e,this.logger,{component:"bridge-core",action:t,...s?{sessionId:s.sessionId,gatewaySessionKey:s.gatewaySessionKey}:{}})}async handleCancellationResult(e,t){return t.ok?(this.observeLifecycle(t.terminalObservation.then(t=>{if(!t.ok)return this.publishCancellationRecovery(e)}),"cancel_terminal_observation",e),"accepted"):"recovery"in t?(await this.publishCancellationRecovery(e),"recovery_required"):"terminal_already_observed"}async publishCancellationRecovery(e){await this.endRun(e,{kind:"interrupted",stopReason:"interrupted",message:SESSION_RECOVERY_REQUIRED_MESSAGE,errorCode:SESSION_RECOVERY_REQUIRED_CODE})}releaseSessionWriteGate(e){if(!e.writeGateToken)return;const t=e.writeGateToken;e.writeGateToken=void 0,this.sessionMutationCoordinator.releaseBeforeRuntime(t)}logRuntimeFailure(e,t){this.logger.warn?.(`[bridge-core] runtime_failure_classified session_id=${e.sessionId} gateway_session_key=${e.gatewaySessionKey} run_id=${e.currentRunId??""} gateway_request_id=${e.gatewayRequestId??""} error_code=${t.errorCode} stop_reason=${t.stopReason} source=${t.source} runtime=${t.runtimeKind}`)}sendSessionBusy(e,t,s,r){this.logger.warn?.(`[bridge-core] session_write_gate_busy session_id=${e.sessionId} gateway_session_key=${e.gatewaySessionKey} reason=${s} queue_depth=${r}`),this.transport.sendError(t,Errors.AgentError.rpcCode,"Agent is busy",{code:"session_busy",reason:s,queueDepth:r}),this.transport.sendSessionUpdate(e.sessionId,{sessionUpdate:"stream_complete",stopReason:"interrupted",error:INTERRUPTED_PUBLIC_MESSAGE,errorCode:"session_busy"},{turnId:`req:${String(t)}`})}async endRun(e,t){if(e.promptDone||e.mutationTerminalCommitting)return this.logger.debug?.(`[bridge-core] endRun skipped (promptDone=true) sessionId=${e.sessionId} outcome=${t.kind}`),!1;if(void 0===e.pendingPromptId&&void 0===e.currentRunId&&void 0===e.gatewayRequestId)return this.logger.debug?.(`[bridge-core] endRun skipped (no pendingPromptId and no currentRunId) sessionId=${e.sessionId} outcome=${t.kind}`),!1;e.mutationTerminalCommitting=!0,this.state.clearPromptTimeout(e),this.state.clearStreamIdleTimer(e);const s=e.writeGateToken;let r,n,i,o,a="timeout"===e.mutationTerminalOverride?{kind:"interrupted",stopReason:"timeout",message:"The agent did not finish before the session timeout.",errorCode:"gateway_timeout"}:t;if(s){const e="complete"===a.kind?"cancelled"===a.stopReason?"cancelled":"completed":"interrupted"===a.kind&&"timeout"===a.stopReason?"timeout":"error",t=await this.sessionMutationCoordinator.converge(s,e);t.ok?r=t.permit:a={kind:"interrupted",stopReason:"interrupted",message:SESSION_RECOVERY_REQUIRED_MESSAGE,errorCode:SESSION_RECOVERY_REQUIRED_CODE}}try{const t=resolveTurnId(e),s=t?{turnId:t}:void 0;e.promptDone=!0;const r=e.currentRunId;if("complete"===a.kind){if(r&&this.onRunComplete){const t=e.agentAssistantTextSoFar??e.chatAssistantTextSoFar??"";this.onRunComplete(r,t,a.stopReason)}void 0!==e.pendingPromptId&&this.transport.sendResult(e.pendingPromptId,{stopReason:a.stopReason}),this.transport.sendSessionUpdate(e.sessionId,{sessionUpdate:"stream_complete",stopReason:a.stopReason},s),this.tryGenerateTitleFromHistory(e,e.sessionId)}else"interrupted"===a.kind?(r&&this.onRunFailed&&this.onRunFailed(r,a.message),void 0!==e.pendingPromptId&&this.transport.sendError(e.pendingPromptId,Errors.AgentError.rpcCode,a.message,{code:a.errorCode??"interrupted",stopReason:a.stopReason}),this.transport.sendSessionUpdate(e.sessionId,{sessionUpdate:"stream_complete",stopReason:a.stopReason,error:a.message,...a.errorCode?{errorCode:a.errorCode}:{}},s)):(r&&this.onRunFailed&&this.onRunFailed(r,a.message),void 0!==e.pendingPromptId&&this.transport.sendError(e.pendingPromptId,a.code,a.message),this.transport.sendSessionUpdate(e.sessionId,{sessionUpdate:"stream_complete",stopReason:"error",error:a.message},s))}catch(e){n=e}try{this.cleanupPrompt(e)}catch(e){i=e}finally{e.writeGateToken=void 0,r&&this.sessionMutationCoordinator.release(r)}try{this.onRunEnded?.(e,a)}catch(e){o=e}if(n)throw n;if(i)throw i;if(o)throw o;return!0}schedulePromptTimeout(e){let t;if(this.state.clearPromptTimeout(e),e.cronJobId){const s=this.cronOverrides.get(e.cronJobId)??this.config.cronMaxDurationDefaultMs;t=Math.max(CRON_DURATION_FLOOR_MS,Math.min(this.config.cronMaxDurationCapMs,s))}else t=this.config.promptTimeoutMs??DEFAULT_PROMPT_TIMEOUT_MS;t<=0||(e.promptTimeoutTimer=setTimeout(()=>{if(e.mutationTerminalCommitting)return;if(void 0===e.pendingPromptId&&void 0===e.currentRunId)return;this.logger.warn?.(`[bridge-core] Prompt timed out sessionId=${e.sessionId} after ${t}ms`),e.mutationTerminalOverride="timeout";const s=this.getGateway(),r=e.writeGateToken;s?.isConnected&&r?this.observeLifecycle(this.sessionMutationCoordinator.requestCancel(r,s).then(t=>this.handleCancellationResult(e,t)),"prompt_timeout_cancel",e):s?.isConnected?this.observeLifecycle(s.chatAbort(e.gatewaySessionKey,e.currentRunId).then(()=>this.interruptPrompt(e,classifyAgentRuntimeFailure({raw:`Prompt timed out after ${t}ms`,source:"live_gateway",runtimeKind:"openclaw"}))),"prompt_timeout_unowned_cancel",e):r&&(this.sessionMutationCoordinator.requireRecovery(r,"gateway_disconnected","disconnect"),this.observeLifecycle(this.endRun(e,{kind:"interrupted",stopReason:"interrupted",message:SESSION_RECOVERY_REQUIRED_MESSAGE,errorCode:SESSION_RECOVERY_REQUIRED_CODE}),"prompt_timeout_disconnected",e))},t),"object"==typeof e.promptTimeoutTimer&&"unref"in e.promptTimeoutTimer&&e.promptTimeoutTimer.unref())}adoptBackgroundMutation(e,t){const s=this.sessionMutationCoordinator.attachOrStartExternal({sessionKey:e.gatewaySessionKey,label:"background_run"},"acp",t);return s.ok?("owner"===s.ownership&&(e.writeGateToken=s.token),s):(this.logger.error?.(`[bridge-core] background mutation rejected sessionKey=${e.gatewaySessionKey} runId=${t} reason=${s.reason}`),s)}cleanupPrompt(e){this.state.clearPromptTimeout(e),this.state.clearStreamIdleTimer(e);const t=e.currentRunId;if(t){this.state.promptsByRunId.delete(t),this.state.runIdToCronJobId.delete(t),this.state.markRunCompleted(t);const e=`tc_${t}_`;for(const[t,s]of this.state.toolCallIdMap)s.startsWith(e)&&this.state.toolCallIdMap.delete(t)}e.gatewayRequestId&&this.state.promptsByGatewayRequestId.delete(e.gatewayRequestId),e.pendingPromptId=void 0,e.promptDone=void 0,e.currentRunId=void 0,e.gatewayRequestId=void 0,e.assistantStreamStartedAt=void 0,e.gatewayMethod=void 0,e.mutationTerminalOverride=void 0,e.mutationTerminalCommitting=void 0,e.assistantMq?.close(),e.assistantMq=void 0,e.pendingUserTurnUpdates=void 0}async completePrompt(e,t){await this.endRun(e,{kind:"complete",stopReason:t})}async interruptPrompt(e,t){this.logRuntimeFailure(e,t),await this.endRun(e,{kind:"interrupted",stopReason:t.stopReason,message:t.publicMessage,errorCode:t.errorCode})}async failPrompt(e,t,s){await this.endRun(e,{kind:"error",code:t,message:s})}cleanupAllPendingPrompts(){for(const e of this.state.sessions.values())e.writeGateToken?(this.sessionMutationCoordinator.requireRecovery(e.writeGateToken,"gateway_disconnected","disconnect"),this.observeLifecycle(this.endRun(e,{kind:"interrupted",stopReason:"interrupted",message:SESSION_RECOVERY_REQUIRED_MESSAGE,errorCode:SESSION_RECOVERY_REQUIRED_CODE}),"gateway_disconnect_recovery",e)):this.observeLifecycle(this.interruptPrompt(e,classifyAgentRuntimeFailure({raw:Errors.GatewayDisconnected.message,source:"live_gateway",runtimeKind:"openclaw"})),"gateway_disconnect_interrupt",e)}handleGatewayResponse(e){const t=e.id;if(!t)return;const s=this.state.promptsByGatewayRequestId.get(t);if(!s||s.promptDone)return;if(!e.ok){const r=e.error,n=r?.message??"gateway returned error";this.logger.warn?.(`[bridge-core] handleGatewayResponse: gateway rejected prompt gatewayRequestId=${t} error=${n}`);const i=classifyAgentRuntimeFailure({raw:n,source:"live_gateway",runtimeKind:"openclaw"});return void("interrupted"===i.kind?this.observeLifecycle(this.interruptPrompt(s,i),"gateway_response_interrupt",s):(this.logRuntimeFailure(s,i),this.observeLifecycle(this.failPrompt(s,Errors.SendFailed.rpcCode,i.publicMessage),"gateway_response_failure",s)))}const r=e.payload,n=r?.runId;n?(s.currentRunId=n,this.sessionMutationCoordinator.bindRun(s.writeGateToken,n),s.assistantStreamStartedAt=Date.now(),this.state.promptsByRunId.set(n,s),this.logger.debug?.(`[bridge-core] handleGatewayResponse: bound runId=${n} gatewayRequestId=${t} sessionId=${s.sessionId}`)):this.logger.debug?.(`[bridge-core] handleGatewayResponse: no runId in payload gatewayRequestId=${t} sessionId=${s.sessionId}`)}maybeCompletePromptFromChatState(e,t){if(e.promptDone||"chat.send"!==e.gatewayMethod)return;const s="string"==typeof t.state?t.state.toLowerCase():void 0;if(s)if("final"===s){const s="string"==typeof t.stopReason?t.stopReason:void 0;this.observeLifecycle(this.completePrompt(e,s||"end_turn"),"chat_state_final",e)}else if("aborted"===s||"cancelled"===s||"cancel"===s)this.observeLifecycle(this.completePrompt(e,"cancelled"),"chat_state_cancelled",e);else if("error"===s){const s=("string"==typeof t.summary?t.summary:void 0)??("string"==typeof t.message?t.message:void 0)??"gateway chat error",r=classifyAgentRuntimeFailure({raw:s,source:"chat_state",runtimeKind:"openclaw"});"interrupted"===r.kind?this.observeLifecycle(this.interruptPrompt(e,r),"chat_state_interrupt",e):(this.logRuntimeFailure(e,r),this.observeLifecycle(this.failPrompt(e,Errors.AgentError.rpcCode,r.publicMessage),"chat_state_failure",e))}}async handleCronRequest(e){const t=this.getGateway();if(!t?.isConnected)return void this.transport.sendAcpError(e.id,Errors.GatewayNotConnected);const s=e.method.replace("/","."),r={...e.params??{}},n="cron.update"===s;!n||r.patch&&"object"==typeof r.patch||(r.patch={});const i=n?r.patch:r,o=i._beeos_meta;let a;if(delete i._beeos_meta,"cron.add"!==s&&"cron.update"!==s||!0!==i._beeos_internal)void 0!==i._beeos_internal&&delete i._beeos_internal;else if(delete i._beeos_internal,i.delivery={mode:"none"},"cron.add"===s){const e=await this.allocateCronChannel(i.name??"cron");e&&(i.description=encodeBeeosChannelTag(i.description,e))}else{const e=r.id,t="description"in i,s=i.description;let n,o=decodeBeeosChannelId(s);!o&&e&&(o=this.cronChannelCache.get(e)),!o&&e&&(n=await this.lookupCronJobDescription(e),o=decodeBeeosChannelId(n));let l=!1;o||(o=await this.allocateCronChannel(i.name??"cron"),l=!0),o&&(t?i.description=encodeBeeosChannelTag(s,o):l&&(i.description=encodeBeeosChannelTag(n,o)),a=o)}try{const n=await t.request(s,r),l="cron.list"===s||"cron.status"===s?this.enrichCronResponseWithOverrides(n):n;if(this.transport.sendResult(e.id,l??{}),("cron.add"===s||"cron.update"===s)&&n&&"object"==typeof n){const e=n.id;if(e&&void 0!==o?.maxDurationMs){const t=Math.max(CRON_DURATION_FLOOR_MS,Math.min(this.config.cronMaxDurationCapMs,o.maxDurationMs));this.cronOverrides.set(e,t)}}if("cron.remove"===s){const t=e.params?.id;t&&(this.cronOverrides.delete(t),this.cronChannelCache.delete(t))}if(("cron.add"===s||"cron.update"===s)&&n&&"object"==typeof n){const e=n.id,t=decodeBeeosChannelId(i.description)??a;e&&t&&this.setCronChannelCache(e,t)}"cron.add"!==s&&"cron.update"!==s&&"cron.remove"!==s||this.refreshCronIsolatedSessionKeys()}catch(t){const r=t instanceof Error?t.message:"cron request failed";this.logger.warn?.(`[bridge-core] ${s} failed: ${r}`),this.transport.sendAcpError(e.id,Errors.SendFailed,{message:r})}}enrichCronResponseWithOverrides(e){if(!e||"object"!=typeof e)return e;const t=e,s=e=>{if(!e||"object"!=typeof e)return e;const t=e,s=t.id;if("string"!=typeof s)return e;const r=this.cronOverrides.get(s);return"number"!=typeof r?e:{...t,_beeos_meta:{maxDurationMs:r}}};if(Array.isArray(t.jobs))return{...t,jobs:t.jobs.map(s)};if(t.jobs&&"object"==typeof t.jobs){const e=t.jobs,r={};for(const[t,n]of Object.entries(e))r[t]=s(n);return{...t,jobs:r}}return e}async allocateCronChannel(e){if(this.agentFetch&&this.agentGatewayUrl)try{const t=`${this.agentGatewayUrl}/api/v1/channels`,s=await this.agentFetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({participants:[this.instanceId,"service:cron"],metadata:{source:"cron",job_name:e}})});if(!s.ok)return void this.logger.warn?.(`[bridge-core] channel alloc failed: ${s.status}`);return(await s.json()).id}catch(e){return void this.logger.warn?.(`[bridge-core] channel alloc error: ${e instanceof Error?e.message:e}`)}else this.logger.debug?.("[bridge-core] agentFetch/agentGatewayUrl unavailable, skip channel alloc")}async lookupCronJobDescription(e){const t=this.getGateway();if(t?.isConnected)try{const s=await t.request("cron.list",{}),r=s?.jobs;if(!Array.isArray(r))return;for(const t of r){if(!t||"object"!=typeof t)continue;const s=t;if(s.id===e)return"string"==typeof s.description?s.description:void 0}}catch(e){this.logger.debug?.(`[bridge-core] cron.list description lookup failed: ${e instanceof Error?e.message:e}`)}}setCronChannelCache(e,t){if(this.cronChannelCache.size>=AcpGatewayBridgeCore.CRON_CHANNEL_CACHE_MAX){const e=this.cronChannelCache.keys().next().value;void 0!==e&&this.cronChannelCache.delete(e)}this.cronChannelCache.set(e,t)}async refreshCronIsolatedSessionKeys(){const e=this.getGateway();if(e?.isConnected)try{const t=await e.request("cron.list",{}),s=t?.jobs;if(!Array.isArray(s))return;const r=new Set,n=[];for(const e of s){if(!e||"object"!=typeof e)continue;const t=e;if("string"==typeof t.id&&n.push(t.id),"isolated"!==t.sessionTarget)continue;const s=t.sessionKey;"string"==typeof s&&s.length>0&&r.add(s)}this.cronIsolatedSessionKeys=r,this.cronOverrides.reconcile(n)}catch(e){this.logger.debug?.(`[bridge-core] refreshCronIsolatedSessionKeys failed: ${e instanceof Error?e.message:e}`)}}}async function fetchWithRetry(e,t={}){const s=t.maxRetries??3;let r;for(let n=0;n<=s;n++){const i=await fetch(e,{signal:AbortSignal.timeout(12e4),redirect:"follow"});if(i.ok)return Buffer.from(await i.arrayBuffer());if(429===i.status||i.status>=500){const o=Number(i.headers.get("retry-after"))||0,a=1e3*Math.max(o,2**n);if(r=new Error(`HTTP ${i.status} from ${e}`),n<s){t.logger?.info?.(`[bridge-core] Download ${i.status}, retry ${n+1}/${s} in ${(a/1e3).toFixed(0)}s`),await new Promise(e=>setTimeout(e,a));continue}}throw new Error(`Failed to download zip: ${i.status}`)}throw r??new Error("Download failed after retries")}const SKILL_ROOT_MARKERS=["SKILL.md","skill.md","SKILL.MD"];async function resolveSkillRoot(e){for(const t of SKILL_ROOT_MARKERS)try{return await stat(join(e,t)),e}catch{}const t=(await readdir(e,{withFileTypes:!0})).filter(e=>e.isDirectory()&&!e.name.startsWith(".")&&"__MACOSX"!==e.name);if(1===t.length){const s=join(e,t[0].name);for(const e of SKILL_ROOT_MARKERS)try{return await stat(join(s,e)),s}catch{}return s}return e}async function extractZipToDir(e,t){const s=new AdmZip(e),r=join(t,".beeos-extract-tmp-"+Date.now());try{await mkdir(r,{recursive:!0}),s.extractAllTo(r,!0);const e=await resolveSkillRoot(r),n=await readdir(e,{withFileTypes:!0});let i=0;for(const s of n){if(s.name.startsWith(".beeos-extract-tmp")||"__MACOSX"===s.name)continue;const r=join(e,s.name),n=join(t,s.name);await rename(r,n).catch(async()=>{await cp(r,n,{recursive:!0})}),i++}if(0===i)throw new Error("Zip archive contained no extractable files")}finally{await rm(r,{recursive:!0,force:!0}).catch(()=>{})}}const VALID_SLUG_PATTERN=/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i,NON_ASCII_PATTERN=/[^\x00-\x7F]/;function validateSkillSlug(e){const t=e.trim();if(!t||t.includes("/")||t.includes("\\")||t.includes(".."))throw new Error(`Invalid skill slug: ${e}`);if(NON_ASCII_PATTERN.test(t)||!VALID_SLUG_PATTERN.test(t))throw new Error(`Invalid skill slug: ${e}`);return t}function safeDirName(e){const t=e.trim();return t?t.replaceAll("/","__").replaceAll("\\","__"):t}function resolveSkillInstallDir(e,t){const s=safeDirName(t),r=join(e,s),n=resolve(e),i=resolve(r),o=relative(n,i);if(!o||".."===o||o.startsWith(`..${sep}`)||path.isAbsolute(o))throw new Error("Invalid skill target path");return r}
1
+ import{randomUUID,createHash}from"node:crypto";import{mkdir,readFile,rm,stat,readdir,rename,cp}from"node:fs/promises";import path from"node:path";import{join,resolve,relative,sep}from"node:path";import{homedir}from"node:os";import AdmZip from"adm-zip";import{sanitizePromptPayload}from"../message-sanitization/prompt-payload-sanitizer.js";import{previewSanitizedText,stripTransportMetadata}from"../message-sanitization/transport-metadata-sanitizer.js";import{isHeartbeatPromptText,isCronPromptText,extractCronJobId,classifyHeartbeatFilter}from"../message-sanitization/message-classifier.js";import{createCronOverridesStore,createInMemoryCronOverridesStore}from"../cron-overrides.js";import{CRON_DURATION_FLOOR_MS}from"../config.js";import{CANVAS_TERM_MAPPING,CANVAS_UPDATE_HINT,buildActiveSurfacesContext}from"../canvas/tools.js";import{parseMentionsParam,buildMentionInstruction}from"../delegation/mention-resolver.js";import{EXTERNAL_PROTOCOL_TAGS}from"./external-protocol-tags.js";import{extractPromptBlocks,toGatewayPrompt,resolveSlashCommandName as resolveSlashCmd}from"./prompt-converter.js";import{extractBeeosUserTextFromHistoryContent}from"./beeos-user-envelope.js";import{loadHistory,loadHistoryPage,groupIntoTurns,splitUserReplayBlocks}from"./history-replay.js";import{classifyHistoricalAssistantTurnCompletion}from"./history-turn-completion-policy.js";import{LocalSessionHistory}from"./local-session-history.js";import{reconcileMissingArtifacts}from"./missing-artifacts-reconciler.js";import{buildResolutionPlan,resolveResources,applyResolutions}from"./prompt-resource-resolver.js";import{readEnrichmentCache,readUploadToolResult}from"../upload/result-cache.js";import{buildToolResultSessionContentBlocks}from"./tool-result-blocks.js";import{prefersChatFileArtifacts}from"./file-artifact-presenter.js";import{buildToolCallPayload,buildToolCallUpdatePayload}from"./tool-call-payload-builder.js";import{Errors}from"../errors/index.js";import{classifyAgentRuntimeFailure,INTERRUPTED_PUBLIC_MESSAGE}from"../runtime/agent-runtime-failure-classifier.js";import{SESSION_RECOVERY_REQUIRED_CODE,SESSION_RECOVERY_REQUIRED_MESSAGE}from"../runtime/session-mutation-coordinator.js";import{observeLifecyclePromise}from"../runtime/lifecycle-promise-observer.js";import{normalizeRuntimeAgentLocalKey,OpenClawRuntimeCommandDispatcher,RuntimeCommandError}from"../runtime/runtime-command-dispatcher.js";import{FileRuntimeAgentIdentityRegistry}from"../runtime/runtime-agent-identity-registry.js";import{FileRuntimeLeasePort}from"../runtime/runtime-operation-lease.js";import{FileRuntimeCommandJournal,InMemoryRuntimeCommandJournal}from"../runtime/runtime-command-journal.js";import{OpenClawRuntimeCapabilities}from"../runtime/openclaw-runtime-capabilities.js";import{RuntimeCanvasService}from"../canvas/runtime-canvas-service.js";import{RuntimeSkillsService}from"../runtime/runtime-skills-service.js";import{RuntimeCronService,encodeBeeosChannelTag}from"../runtime/runtime-cron-service.js";export{BEEOS_CHANNEL_TAG_RE,decodeBeeosChannelId,encodeBeeosChannelTag,stripBeeosChannelTag}from"../runtime/runtime-cron-service.js";import{LocalConversationHistoryBoundary,OpenClawGatewaySessionMetadataPort,OpenClawSessionMetadataService,RuntimeSessionControlService}from"../runtime/runtime-session-control-service.js";import{pageRuntimeSessions,parseRuntimeSessionListPage}from"../runtime/runtime-session-list-policy.js";import{createAcpCoreExecutionAttempt}from"../runtime/core-execution-attempt-context.js";import{CompositeActiveRunPort}from"../runtime/composite-active-run-port.js";import{resolveChannelFromSessionKey}from"../channel-detection.js";import{resolveTurnId,recordTurnIdForRole,recordToolCallTurnForUpdate}from"./gateway-events.js";import{emitToolResultProjectionForTurn}from"./tool-result-emitter.js";import{extractPersistedToolResultEnvelope,findPersistedToolResultEnvelopeByMessageId}from"./tool-result-transcript.js";import{projectPersistedToolResultForReplay}from"./tool-result-projection.js";import{McpService}from"./mcp-service.js";import{McpDesiredStateStore,normalizeMcpPublicServerConfig}from"./mcp-desired-state-store.js";import{requireSecretFreeMcpServerName}from"./mcp-public-config-security.js";import{McpDesiredStateController}from"./mcp-desired-state-controller.js";import{CredentialReferenceRuntimeResolver}from"./mcp-credential-reference-port.js";import{DEFAULT_PROMPT_TIMEOUT_MS}from"./types.js";import{ACP_PROTOCOL_VERSION}from"@beeos-ai/beeos-types/acp";import{canonicalizeJcs}from"@beeos-ai/beeos-types/runtime";import{MAX_ZIP_BYTES,parseAndValidateTemplatePackage,TemplatePackageError}from"./agent-template-package.js";const DEFAULT_AGENT_ID="main";export function normalizeAgentId(e){return normalizeRuntimeAgentLocalKey(e)}function narrowContentBlock(e){switch(e.type){case"text":return"string"==typeof e.text?{type:"text",text:e.text}:void 0;case"resource_link":return"string"==typeof e.uri?{type:"resource_link",uri:e.uri,...void 0!==e.name?{name:e.name}:{},...void 0!==e.mimeType?{mimeType:e.mimeType}:{}}:void 0;case"file":return{type:"file",...void 0!==e.text?{text:e.text}:{},...void 0!==e.uri?{uri:e.uri}:{},...void 0!==e.mimeType?{mimeType:e.mimeType}:{}};case"image":return{type:"image",...void 0!==e.uri?{uri:e.uri}:{},...void 0!==e.mimeType?{mimeType:e.mimeType}:{}};default:return}}const DEFAULT_MODE={id:"default",name:"Default",description:"Default agent mode"},USER_MESSAGE_PREFIX="User Message From BeeOS:",USER_MESSAGE_PREFIX_WITH_NEWLINE=`${USER_MESSAGE_PREFIX}\n`,SLASH_COMMAND_NAME_RE=/^[a-z0-9_-]+$/i,HISTORY_PAGE_DEFAULT_LIMIT=100,HISTORY_PAGE_MAX_LIMIT=1e3;function withUserMessagePrefix(e){return e.startsWith(USER_MESSAGE_PREFIX)?e:`${USER_MESSAGE_PREFIX_WITH_NEWLINE}${e}`}const BACKGROUND_SESSION_PATTERNS=[":cron:",":hook:","node-",...EXTERNAL_PROTOCOL_TAGS];function isBackgroundSessionKey(e){return BACKGROUND_SESSION_PATTERNS.some(t=>e.includes(t))}function isSystemDefaultSessionKey(e){const t=e.split(":");if(t.length<3||"agent"!==t[0])return!1;const s=t.slice(2).join(":");return"main"===s||s===t[1]}function isPlaceholderTitle(e){return!e||"New chat"===e||(!!/^[0-9a-f]{6,12}\s*\(20\d{2}/i.test(e)||(!!/^Sender\s*\(untrusted/i.test(e)||!!/^Conversation\s+info\s*\(untrusted/i.test(e)))}function cleanGatewayDerivedTitle(e){if(!e)return;let t=extractBeeosUserTextFromHistoryContent(e).text;return/^\[System\b/.test(t)?void 0:(t=t.trim(),t||void 0)}function cleanHistoryUserContent(e){return extractBeeosUserTextFromHistoryContent(e).text.trim()}function mergePersistedToolResultEnvelopes(e,t){if(e||t)return{toolName:e?.toolName||t?.toolName||"tool",...e?.toolCallId||t?.toolCallId?{toolCallId:e?.toolCallId??t?.toolCallId}:{},...e?.rawText??t?.rawText?{rawText:e?.rawText??t?.rawText}:{},...void 0!==e?.rawResult||void 0!==t?.rawResult?{rawResult:e?.rawResult??t?.rawResult}:{},...e?.timestamp||t?.timestamp?{timestamp:e?.timestamp??t?.timestamp}:{}}}function resolveStandaloneSlashCommandName(e,t){if(0===e.length)return;if(e.some(e=>"text"!==e.type))return;const s=t.trim();if(!s||s.includes("\n")||!s.startsWith("/"))return;const n=s.match(/^\/([^\s:]+)(?::|\s|$)/)?.[1]?.trim();return n&&!n.includes("/")&&SLASH_COMMAND_NAME_RE.test(n)?n.toLowerCase():void 0}class AcpRuntimeCommandResourceStore{fetchPackage;parsePackage;isValidationError;content=new Map;artifacts=new Map;mcpDefinitions=new Map;constructor(e,t,s){this.fetchPackage=e,this.parsePackage=t,this.isValidationError=s}registerContent(e){const t=`acp-content:sha256:${createHash("sha256").update(e).digest("hex")}`;return this.content.set(t,e),t}registerMcpDefinition(e,t){this.mcpDefinitions.set(JSON.stringify(e),structuredClone(t))}async resolveMcpDefinition(e){const t=this.mcpDefinitions.get(JSON.stringify(e));if(!t)throw new Error("ACP MCP definition was not verified for this request");return structuredClone(t)}async registerArtifact(e,t){try{const s=await this.fetchPackage(e);if(!s.ok)throw new TemplatePackageError(`failed to download package: HTTP ${s.status}`);const n=Buffer.from(await s.arrayBuffer());if(n.length>MAX_ZIP_BYTES)throw new TemplatePackageError(`package too large: ${n.length} bytes (max ${MAX_ZIP_BYTES})`);const i=createHash("sha256").update(n).digest("hex");if(t?.trim()&&t.trim().toLowerCase()!==i)throw new TemplatePackageError("package sha256 mismatch");const r=this.parsePackage(n),o=`acp-artifact:sha256:${i}`;return this.artifacts.set(o,r),{artifactRef:o,version:r.manifest?.version??"0.0.0",sha256:i,sizeBytes:String(n.length),source:"acp-verified"}}catch(e){if(e instanceof TemplatePackageError||this.isValidationError(e))throw new RuntimeCommandError("INVALID_PARAMS",`Invalid template package: ${e instanceof Error?e.message:String(e)}`);throw new RuntimeCommandError("EXECUTION_FAILED",`Apply template failed: ${e instanceof Error?e.message:String(e)}`)}}registerParsedArtifact(e){const t=createHash("sha256");for(const[s,n]of[...e.workspaceFiles].sort(([e],[t])=>e.localeCompare(t)))t.update(s).update("\0").update(n).update("\0");const s=t.digest("hex"),n=`acp-artifact:sha256:${s}`;this.artifacts.set(n,e);const i=[...e.workspaceFiles.values()].reduce((e,t)=>e+t.byteLength,0);return{artifactRef:n,version:e.manifest?.version??"0.0.0",sha256:s,sizeBytes:String(i),source:"acp-verified"}}async readContent(e){const t=this.content.get(e);if(void 0===t)throw new Error(`unknown contentRef: ${e}`);return t}async readVerifiedArtifact(e){const t=this.artifacts.get(e.artifactRef);if(!t)throw new Error(`unknown verified artifact: ${e.artifactRef}`);return t}}export const lateBoundRuntimeAgentProjectionPort=e=>({prepareCommittedMutation:async t=>{const s=e();if(!s)throw new Error("agent projection production is unavailable");return await s.prepareCommittedMutation(t)},applyCommittedMutation:async t=>{const s=e();if(!s)throw new Error("agent projection production is unavailable");return await s.applyCommittedMutation(t)},compactCommittedMutation:async t=>{const s=e();if(!s)throw new Error("agent projection production is unavailable");await s.compactCommittedMutation(t)}});function deriveSessionId(e,t){const s=createHash("sha256").update(`${t}:${e}`).digest("hex").substring(0,32);return`session:${s.slice(0,8)}-${s.slice(8,12)}-${s.slice(12,16)}-${s.slice(16,20)}-${s.slice(20,32)}`}function parseAgentIdFromKey(e){const t=e.split(":");return(t.length>=3&&"agent"===t[0]?t[1]:"")||"main"}function createSessionObject(e){const t=parseAgentIdFromKey(e.gatewayKey);return{sessionId:deriveSessionId(e.gatewayKey,e.instanceId),gatewaySessionKey:e.gatewayKey,agentId:t,platformAgentId:e.resolvePlatformAgentId?.(t),createdAt:Date.now(),chatReplayDedupKeys:new Set,cwd:e.cwd,title:e.title,channelType:resolveChannelFromSessionKey(e.gatewayKey,e.thirdPartyChannels)}}class HistoryUpdateRecorder{transport;updates=[];constructor(e){this.transport=e}sendSessionUpdate(e,t,s){const n=this.transport.materializeSessionUpdate(t,s);return null!==n&&this.updates.push(n),!0}}export class AcpGatewayBridgeCore{state;transport;config;logger;writeObs;getGateway;thirdPartyChannels;beeosFileResolver;agentFetch;agentGatewayUrl;triggerAgentSync;sessionMutationCoordinator;runtimeCommands;runtimeAgentIdentities;runtimeLeases;runtimeCommandResources;localHistoryBoundary;activeRuns;markAgentProjectionDirty;projectionObservationTimeoutMs;cronChannelCache=new Map;static CRON_CHANNEL_CACHE_MAX=256;localSessionHistory;mcp;runtimeTerminalService;initialized=!1;agentMapping=null;_syncResolvers=[];_mappingReadyCallbacks=[];_mappingUpdateCallbacks=[];cronIsolatedSessionKeys=new Set;cronOverrides;cronReady;setAgentMapping(e){this.agentMapping=e;for(const t of this.state.sessions.values())!t.platformAgentId&&t.agentId&&(t.platformAgentId=e.get(t.agentId)?.id);for(const e of this._syncResolvers)e();this._syncResolvers=[];for(const e of this._mappingReadyCallbacks)e();this._mappingReadyCallbacks=[];for(const t of this._mappingUpdateCallbacks)t(e)}onceAgentMappingReady(e){this.agentMapping&&this.agentMapping.size>0?e():this._mappingReadyCallbacks.push(e)}onAgentMappingUpdated(e){this._mappingUpdateCallbacks.push(e),this.agentMapping&&this.agentMapping.size>0&&e(this.agentMapping)}markAgentSyncDone(){for(const e of this._syncResolvers)e();this._syncResolvers=[]}waitForAgentMapping(e=15e3){return this.agentMapping?Promise.resolve():new Promise(t=>{this._syncResolvers.push(t);const s=setTimeout(()=>{const e=this._syncResolvers.indexOf(t);-1!==e&&this._syncResolvers.splice(e,1),t()},e);"object"==typeof s&&"unref"in s&&s.unref()})}handleTerminalRequest=null;handleTerminalNotification=null;onRunComplete=null;onRunFailed=null;onRunEnded=null;handleSlashCommand=null;onPromptStart=null;instanceId;mcpCredentialReferences;constructor(e){this.state=e.state,this.transport=e.transport,this.config=e.config,this.logger=e.logger,this.writeObs=e.writeObs,this.getGateway=e.getGateway,this.instanceId=e.instanceId||"unknown",this.mcpCredentialReferences=e.mcpCredentialReferences,this.thirdPartyChannels=e.thirdPartyChannels??new Set,this.beeosFileResolver=e.beeosFileResolver,this.agentFetch=e.agentFetch,this.agentGatewayUrl=e.agentGatewayUrl,this.triggerAgentSync=e.triggerAgentSync,this.markAgentProjectionDirty=e.markAgentProjectionDirty,this.projectionObservationTimeoutMs=e.projectionObservationTimeoutMs??5e3,this.sessionMutationCoordinator=e.sessionMutationCoordinator,this.runtimeAgentIdentities=e.runtimeAgentIdentities??new FileRuntimeAgentIdentityRegistry(join(process.env.OPENCLAW_HOME||join(homedir(),".openclaw"),"beeos","runtime-agent-identities.json")),this.runtimeLeases=e.runtimeLeases??new FileRuntimeLeasePort(join(process.env.OPENCLAW_HOME||join(homedir(),".openclaw"),"beeos","runtime-operation-leases.json")),this.runtimeCommandResources=e.runtimeCommandResources??new AcpRuntimeCommandResourceStore(e.agentFetch??((e,t)=>fetch(e,t)),e.runtimeTemplatePackageParser?.parse??parseAndValidateTemplatePackage,e.runtimeTemplatePackageParser?.isValidationError??(e=>e instanceof TemplatePackageError));const t=e.runtimeCommandJournal??(e.runtimeLeases||process.env.VITEST?new InMemoryRuntimeCommandJournal:new FileRuntimeCommandJournal(join(process.env.OPENCLAW_HOME||join(homedir(),".openclaw"),"beeos","runtime-command-journal.json")));this.localSessionHistory=new LocalSessionHistory({logger:this.logger}),this.cronOverrides=e.cronOverrides??(process.env.VITEST?createInMemoryCronOverridesStore():createCronOverridesStore(this.logger)),this.cronReady=this.cronOverrides.initialize(),this.cronReady.catch(()=>{});const s=e.mcpCredentialReferences?new CredentialReferenceRuntimeResolver(e.mcpCredentialReferences):void 0,n=e.mcpToolBridge?new McpDesiredStateController(new McpDesiredStateStore(e.mcpDesiredStatePath??join(process.env.OPENCLAW_HOME||join(homedir(),".openclaw"),"beeos","mcp-desired-state.json")),e.mcpToolBridge,s,e.mcpCredentialReferences,t,e.runtimeReceiptHistory):void 0;this.mcp=new McpService({transport:this.transport,logger:this.logger,state:this.state,...n?{desiredState:n}:{}}),this.localHistoryBoundary=new LocalConversationHistoryBoundary(e=>{const t=this.state.sessions.get(e);t&&(t.chatReplayDedupKeys.clear(),this.onSessionCleared?.(t.sessionId,t.gatewaySessionKey))}),this.activeRuns=e.activeRuns??new CompositeActiveRunPort;const i=new RuntimeCronService({getGateway:e.getGateway,overrides:this.cronOverrides,durationFloorMs:CRON_DURATION_FLOOR_MS,durationCapMs:this.config.cronMaxDurationCapMs,channels:{allocate:async e=>await this.allocateCronChannel(e),lookup:e=>this.cronChannelCache.get(e),remember:(e,t)=>this.setCronChannelCache(e,t),forget:e=>{this.cronChannelCache.delete(e)}}}),r=new RuntimeSessionControlService({convergeRun:async({runtimeSessionKey:e})=>{const t=this.state.sessionsByGatewayKey.get(e),s=(new Date).toISOString();if(!t||t.promptDone&&void 0===t.pendingPromptId&&void 0===t.currentRunId)return{state:"no_active_run",quiescence:{state:"verified",observedAt:s}};const n=this.getGateway();if(!n?.isConnected)return{state:"recovery_required",reason:"gateway_disconnected"};if(!t.writeGateToken||!t.currentRunId)return{state:"recovery_required",reason:"run_conflict",runtimeRunId:t.currentRunId??"unknown"};const i=t.currentRunId,r=await this.sessionMutationCoordinator.requestCancel(t.writeGateToken,n);if(!r.ok)return{state:"recovery_required",reason:"terminal_not_observed",runtimeRunId:i};const o=await r.terminalObservation;return o.ok?{state:"terminal_observed",runtimeRunId:i,abortObservation:r.acknowledged?{state:"target_acknowledged",aborted:!0,runIds:[i],targetRunAcknowledged:!0}:{state:"already_stopped",aborted:!1,runIds:[]},terminalState:"cancelled",quiescence:{state:"verified",observedAt:(new Date).toISOString()}}:{state:"recovery_required",reason:"quiescence_failed"===o.recovery.reason?"quiescence_failed":"quiescence_timeout"===o.recovery.reason?"quiescence_timeout":"terminal_not_observed",runtimeRunId:i}},reset:async e=>{const t=this.getGateway();if(!t?.isConnected)throw new RuntimeCommandError("GATEWAY_NOT_CONNECTED","Gateway not connected");await t.sessionsReset(e)}},new OpenClawSessionMetadataService(new OpenClawGatewaySessionMetadataPort(e.getGateway,(e,t)=>{const s=this.state.sessionsByGatewayKey.get(e);if(!s)throw new Error("session model projection target is unavailable");void 0===t.modelOverrideId?delete s.modelOverrideId:s.modelOverrideId=t.modelOverrideId,s.effectiveModelId=t.effectiveModelId,void 0===t.modelProvider?delete s.modelProvider:s.modelProvider=t.modelProvider})),this.activeRuns),o=e.getAgentProjection?lateBoundRuntimeAgentProjectionPort(e.getAgentProjection):void 0,a=e.runtimeCommandDispatcher??new OpenClawRuntimeCommandDispatcher({getGateway:e.getGateway,resources:this.runtimeCommandResources,identities:this.runtimeAgentIdentities,leases:this.runtimeLeases,journal:t,logger:e.logger,...o?{projection:o}:{}});this.runtimeCommands=a,a instanceof OpenClawRuntimeCommandDispatcher&&a.setCapabilities(new OpenClawRuntimeCapabilities({getGateway:e.getGateway,identities:this.runtimeAgentIdentities,sessions:r,skills:new RuntimeSkillsService({getGateway:e.getGateway,resources:this.runtimeCommandResources,getOpenclawHome:()=>this.getOpenclawHome()}),mcp:this.mcp,terminal:{execute:async(e,t)=>{if(!this.runtimeTerminalService)throw new RuntimeCommandError("INVALID_PARAMS","terminal canonical delegate is not configured");return await this.runtimeTerminalService.execute(e,t)}},canvas:new RuntimeCanvasService({apply:async(e,t)=>{await this.applyCanonicalCanvasControl(e,t)}}),cron:i})),this.transport.setSessionUpdateObserver((e,t,s)=>{const n=s?.turnId;if(!n)return;const i=this.state.sessions.get(e);i&&(recordTurnIdForRole(i,t,n),recordToolCallTurnForUpdate(i,t,n))})}setRuntimeTerminalService(e){this.runtimeTerminalService=e}setRuntimeReceiptHistoryPort(e){this.mcp.setRuntimeReceiptHistoryPort(e)}getRuntimeCommandDispatcher(){return this.runtimeCommands}async stopMcp(){await this.mcp.stop()}observeActiveRun(e){const t=e.currentRunId,s=e.writeGateToken,n=this.sessionMutationCoordinator.snapshot(e.gatewaySessionKey),i=void 0===e.pendingPromptId?e.gatewayRequestId??t:String(e.pendingPromptId);if(!t||!n||!i)return;const r={runtimeConversationUid:e.sessionId,runtimeSessionKey:e.gatewaySessionKey,historyGeneration:this.localHistoryBoundary.currentGeneration(e.sessionId),targetMessageId:i,runtimeRunId:t,activeRunRegistrationId:`acp:${n.operationId}`};return this.activeRuns.register(r,async()=>{const i=this.getGateway();if(!i?.isConnected||e.currentRunId!==t)return"recovery_required";const r=s?.operationId===n.operationId?await this.sessionMutationCoordinator.requestCancel(s,i):await this.sessionMutationCoordinator.requestCancelForSession(e.gatewaySessionKey,i);return r.ok?"accepted":!("reason"in r)||"terminal_already_observed"!==r.reason&&"no_active_mutation"!==r.reason?"recovery_required":"already_terminal"}),e.activeRunBinding=r,r}resolvePlatformAgentId(e){return this.agentMapping?.get(e)?.id}getDefaultAgentId(){return this.agentMapping&&this.agentMapping.size>0?this.agentMapping.keys().next().value??"main":"main"}registerSession(e,t={}){const{ensureVerbose:s=!0}=t;this.state.sessions.has(e.sessionId);this.state.sessions.set(e.sessionId,e),this.state.sessionsByGatewayKey.set(e.gatewaySessionKey,e),s&&this.ensureVerboseFull(e),this.subscribeSessionMessages(e)}async ensureVerboseFull(e){if(e.verbosePatched)return;const t=this.getGateway();if(t?.isConnected){e.verbosePatched=!0;try{await t.sessionsPatch(e.gatewaySessionKey,{verboseLevel:"full"})}catch(t){e.verbosePatched=!1,this.logger.debug?.(`[bridge-core] ensureVerboseFull failed: ${t instanceof Error?t.message:t}`)}}}ensureSessionForKey(e){const t=this.state.findSessionByKey(e);if(t)return t;const s=createSessionObject({gatewayKey:e,instanceId:this.instanceId,resolvePlatformAgentId:e=>this.resolvePlatformAgentId(e),thirdPartyChannels:this.thirdPartyChannels});return this.registerSession(s),this.logger.debug?.(`[bridge-core] ensureSessionForKey: lazily created session sid=${s.sessionId} key=${e}`),s}async preloadSessionsFromGateway(){const e=this.getGateway();if(e?.isConnected)try{const t=await e.sessionsList();if(!t||"object"!=typeof t)return;const s=t.sessions;if(!Array.isArray(s))return;let n=0;for(const e of s){const t=e,s=t.sessionKey??t.key??t.id??t.name;if(!s||"string"!=typeof s)continue;if(this.state.findSessionByKey(s))continue;const i=t.label||t.derivedTitle||t.title||void 0,r=i&&!isPlaceholderTitle(i)?i:void 0,o=createSessionObject({gatewayKey:s,instanceId:this.instanceId,resolvePlatformAgentId:e=>this.resolvePlatformAgentId(e),thirdPartyChannels:this.thirdPartyChannels,title:r});this.registerSession(o,{ensureVerbose:!1}),n++}n>0&&this.logger.info?.(`[bridge-core] preloadSessions: loaded ${n} session(s) from gateway`)}catch(e){this.logger.debug?.("[bridge-core] preloadSessions failed:",e)}else this.logger.debug?.("[bridge-core] preloadSessions skipped: gateway not connected")}async handleRequest(e){this.writeObs({component:"bridge-core",domain:"forward",name:`rpc.request.${e.method}`,severity:"debug",payload:{id:e.id,method:e.method}});const t=e;try{switch(t.method){case"initialize":await this.handleInitialize(t);break;case"ping":this.transport.sendResult(t.id,{pong:!0});break;case"session/new":await this.handleSessionNew(t);break;case"session/load":await this.handleSessionLoad(t);break;case"session/list":await this.handleSessionList(t);break;case"session/rename":await this.handleSessionRename(t);break;case"session/delete":await this.handleSessionDelete(t);break;case"session/clear":await this.handleSessionClear(t);break;case"session/prompt":await this.handleSessionPrompt(t);break;case"session/set_mode":await this.handleSessionSetMode(t);break;case"session/set_model":await this.handleSessionSetModel(t);break;case"agent/create":await this.handleAgentCreate(t);break;case"agent/update":await this.handleAgentUpdate(t);break;case"agent/delete":await this.handleAgentDelete(t);break;case"agent/applyTemplate":await this.handleApplyTemplate(t);break;case"skills/list":await this.handleSkillsList(t);break;case"skills/install":await this.handleSkillsInstall(t);break;case"skills/uninstall":await this.handleSkillsUninstall(t);break;case"skills/update":await this.handleSkillsUpdate(t);break;case"models/list":await this.handleModelsList(t);break;case"session/cancel":await this.handleSessionCancel(t.id,t.params);break;case"cron/list":case"cron/status":case"cron/add":case"cron/update":case"cron/remove":case"cron/run":case"cron/runs":await this.handleCronRequest(t);break;default:"agent/list"===e.method?await this.handleAgentList(e):"_beeos/session/history"===e.method?await this.handleSessionHistoryPage(e):"mcp/list"===e.method?await this.handleMcpList(e):"mcp/set"===e.method?await this.handleMcpSet(e):"mcp/unset"===e.method?await this.handleMcpUnset(e):e.method.startsWith("terminal/")&&this.handleTerminalRequest?await this.handleTerminalRequest(e):this.transport.sendAcpError(e.id,Errors.MethodNotFound,{message:`Method not found: ${e.method}`})}}catch(t){this.transport.sendAcpError(e.id,Errors.InternalError,{message:t instanceof Error?t.message:"Internal error"})}}handleCanvasNotification=null;getCanvasState=null;onSessionLoaded=null;onSkillInstalled=null;onSessionDeleted=null;onSessionCleared=null;handleNotification(e){switch(e.method){case"session/cancel":this.observeLifecycle(this.handleSessionCancel(void 0,e.params),"session_cancel_notification");break;case"canvas/toggle":case"canvas/clear":case"canvas/reference":case"canvas/dimensions":this.observeLifecycle(this.dispatchLegacyCanvasNotification(e.method,e.params),`canvas_notification_${e.method.replace("/","_")}`);break;default:e.method.startsWith("terminal/")&&this.handleTerminalNotification?this.handleTerminalNotification(e.method,e.params):this.logger.debug?.(`[bridge-core] Unknown notification: ${e.method}`)}}async handleInitialize(e){await this.cronReady,this.initialized=!0;const t=this.transport.forwardControl;!1===e.params?.forwardThinking&&(t.forwardThinking=!1),!1===e.params?.forwardToolCalls&&(t.forwardToolCalls=!1),this.transport.sendResult(e.id,{protocolVersion:ACP_PROTOCOL_VERSION,agentCapabilities:{loadSession:!0,skills:!0,multipleSessions:!0,sessionCapabilities:{"web-ssh":!!this.config.bridge.shell.enabled,canvas:!!this.config.canvasEnabled},promptCapabilities:{image:!0,audio:!1,embeddedContext:!0},mcp:{}},agentInfo:{name:"beeos-claw",title:"BeeOS Agent Bridge",version:"0.1.0"},_meta:{},authMethods:[]})}async handleSessionNew(e){const t=`agent:${"string"==typeof e.params?.agentId&&e.params.agentId||this.config.gateway.agentId||this.getDefaultAgentId()}:${randomUUID()}`,s="string"==typeof e.params?.cwd?e.params.cwd:".",n=createSessionObject({gatewayKey:t,instanceId:this.instanceId,resolvePlatformAgentId:e=>this.resolvePlatformAgentId(e),thirdPartyChannels:this.thirdPartyChannels,cwd:s});this.registerSession(n,{ensureVerbose:!1}),this.transport.sendResult(e.id,{sessionId:n.sessionId,modes:{availableModes:[{...DEFAULT_MODE}],currentModeId:DEFAULT_MODE.id},_meta:{sessionKey:t,agentId:n.agentId,platformAgentId:n.platformAgentId??null}}),this.fetchHistoryInBackground(t)}fetchHistoryInBackground(e){const t=this.getGateway();t?.isConnected&&loadHistory(t,e,50).catch(e=>{this.logger.debug?.(`[bridge-core] Background history fetch failed: ${e instanceof Error?e.message:e}`)})}async subscribeSessionMessages(e){if(e.messageSubscriptionActive)return;const t=this.getGateway();if(t?.isConnected)try{await t.sessionsMessagesSubscribe(e.gatewaySessionKey),e.messageSubscriptionActive=!0,this.logger.debug?.(`[bridge-core] session.message subscribed sessionKey=${e.gatewaySessionKey}`)}catch(t){this.logger.debug?.(`[bridge-core] session.message subscribe failed key=${e.gatewaySessionKey}: ${t instanceof Error?t.message:t}`)}}async resubscribeAllSessionMessages(){for(const e of this.state.sessions.values())e.messageSubscriptionActive=!1,await this.subscribeSessionMessages(e)}handleSessionMessage(e){const t=e?.sessionKey,s=e?.messageId;if(!t||!s)return;const n=this.state.sessionsByGatewayKey.get(t);if(!n)return;const i=e.message?.role;if("toolResult"===i||"tool"===i)return void this.handlePersistedToolResultMessage(n,e).catch(e=>{this.logger.warn?.(`[bridge-core] persisted toolResult handling failed sessionKey=${t} messageId=${s}: ${e instanceof Error?e.message:String(e)}`)});const r="user"===i?"user":"system"===i?"system":"assistant",o=n.lastTurnIdByRole?.[r];if(!o)return void this.logger.debug?.(`[bridge-core] session.message skipped: no lastTurnId role=${r} sessionKey=${t} messageId=${s}`);const a=e.message?.timestamp,d="number"==typeof a&&a>0?new Date(a).toISOString():void 0,c={turnId:o};"number"==typeof e.messageSeq&&(c.seq=e.messageSeq),this.transport.sendSessionUpdate(n.sessionId,{sessionUpdate:"message_id_bind",role:r,messageId:s,...d?{timestamp:d}:{}},c),n.lastTurnIdByRole&&(n.lastTurnIdByRole[r]=void 0),"assistant"===r&&reconcileMissingArtifacts({localHistory:this.localSessionHistory,transport:this.transport,config:this.config,logger:this.logger,writeObs:this.writeObs,getCanvasState:()=>this.getCanvasState?.()??null,isSessionAlive:e=>this.state.sessions.has(e)},{session:n,turnId:o,messageId:s,runId:void 0})}async handlePersistedToolResultMessage(e,t){const s=t.messageId;if(!s)return;const n=extractPersistedToolResultEnvelope(t.message),i=this.extractPersistedToolResultFromHistory(e,s),r=mergePersistedToolResultEnvelopes(i,n);if(!r?.toolCallId)return void this.writeObs({component:"bridge-core",domain:"tool_result",name:"tool_result.skipped_no_tool_call_id",severity:"warn",payload:{sessionId:e.sessionId,messageId:s}});i&&this.writeObs({component:"bridge-core",domain:"tool_result",name:"tool_result.hydrated_from_history",severity:"debug",payload:{sessionId:e.sessionId,messageId:s,toolCallId:r.toolCallId,toolName:r.toolName}});const o=e.toolCallTurns?.get(r.toolCallId),a=o?.turnId??resolveTurnId(e);if(!a)return void this.writeObs({component:"bridge-core",domain:"tool_result",name:"tool_result.skipped_no_turn",severity:"warn",payload:{sessionId:e.sessionId,messageId:t.messageId,toolCallId:r.toolCallId,toolName:r.toolName}});const d=projectPersistedToolResultForReplay({toolCallId:r.toolCallId,toolName:r.toolName||o?.toolName||"tool",...r.rawText?{rawText:r.rawText}:{},...r.rawResult?{rawResult:r.rawResult}:{},...r.timestamp?{timestamp:r.timestamp}:{}});await emitToolResultProjectionForTurn({session:e,projection:d,toolCallId:r.toolCallId,turnId:a,config:this.config,logger:this.logger,writeObs:this.writeObs,getCanvasState:()=>this.getCanvasState?.()??null,sendUpdate:s=>(a&&void 0===s._turnId&&(s._turnId=a),this.transport.sendSessionUpdate(e.sessionId,s,{turnId:a,..."number"==typeof t.messageSeq?{seq:t.messageSeq}:{}}))})}extractPersistedToolResultFromHistory(e,t){if(!e.agentId)return;const s=this.localSessionHistory.readEntries({agentId:e.agentId,sessionKey:e.gatewaySessionKey,sessionId:e.sessionId});return findPersistedToolResultEnvelopeByMessageId(s,t)}async recoverSessionFromGateway(e){const t=this.getGateway();if(t?.isConnected)try{const s=await t.sessionsList();if(!s||"object"!=typeof s)return;const n=s.sessions;if(!Array.isArray(n))return;for(const t of n){const s=t,n=s.sessionKey??s.key??s.id??s.name;if(n&&"string"==typeof n&&deriveSessionId(n,this.instanceId)===e){const e=s.label||s.derivedTitle||s.title||void 0,t=e&&!isPlaceholderTitle(e)?e:void 0,i=createSessionObject({gatewayKey:n,instanceId:this.instanceId,resolvePlatformAgentId:e=>this.resolvePlatformAgentId(e),thirdPartyChannels:this.thirdPartyChannels,title:t});return this.registerSession(i),i}}}catch(e){this.logger.debug?.("[bridge-core] recoverSessionFromGateway failed:",e)}}async resolveOrCreateSession(e){let t=this.state.sessions.get(e);if(!t&&e.startsWith("agent:")&&(t=this.state.findSessionByKey(e)),t||e.startsWith("agent:")||(t=await this.recoverSessionFromGateway(e)),t)return{session:t,created:!1};const s=e.startsWith("agent:"),n=this.config.gateway.agentId||this.getDefaultAgentId();return t=createSessionObject({gatewayKey:s?e:`agent:${n}:${e}`,instanceId:this.instanceId,resolvePlatformAgentId:e=>this.resolvePlatformAgentId(e),thirdPartyChannels:this.thirdPartyChannels}),this.registerSession(t),{session:t,created:!0}}async handleSessionLoad(e){const t=e.params?.sessionId;if(!t)return void this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"sessionId"});const{session:s}=await this.resolveOrCreateSession(t),n=s.sessionId;this.state.lastActiveSessionId=n,s.chatReplayDedupKeys.clear(),s.sessionLoadInProgress=!0;try{const e=await this.emitHistoryTurns(s);this.emitCurrentStreamSnapshot(s,e)}finally{s.sessionLoadInProgress=!1}this.transport.sendResult(e.id,{sessionId:n}),this.onSessionLoaded?.(n,s.gatewaySessionKey,s.platformAgentId),this.pushSkillsList().catch(e=>{this.logger.warn?.(`[bridge-core] pushSkillsList failed: ${e instanceof Error?e.message:e}`)})}async emitHistoryTurns(e){const t=new Set,s=e.sessionId;try{const n=this.getGateway(),i=e.assistantMq&&!e.assistantMq.closed,r=i?null:n,o=await loadHistory(r,e.gatewaySessionKey,100,{localHistory:this.localSessionHistory,agentId:e.agentId,sessionId:e.sessionId,logger:this.logger,emitObs:e=>{this.writeObs({component:"history-replay",domain:"lifecycle",name:e.name,severity:e.severity,payload:e.payload})}}),a=groupIntoTurns(o),d=a.filter(e=>"user"===e.role).length,c=a.filter(e=>"assistant"===e.role).length,l=a.filter(e=>e.toolCalls?.length).length,m=a.reduce((e,t)=>e+(t.toolCalls?.length??0),0);if(this.logger.debug?.(`[bridge-core] handleSessionLoad sid=${s} messages=${o.length} turns=${a.length} userTurns=${d} asstTurns=${c} turnsWithToolCalls=${l} totalToolCalls=${m}`),d>0&&e.pendingUserTurnUpdates&&e.pendingUserTurnUpdates.length>0&&(e.pendingUserTurnUpdates=[]),a.length>0&&isPlaceholderTitle(e.title)&&"manual"!==e.titleSource){const t=a.find(e=>{if("user"!==e.role)return!1;if("internal_system"===e.provenance?.kind)return!1;if(isCronPromptText(e.content))return!1;const t=cleanHistoryUserContent(e.content);return t.length>0&&!/^Sender\s*\(untrusted/i.test(t)});if(t?.content){const n=cleanHistoryUserContent(t.content).slice(0,64).replace(/\n/g," ").trim();if(n){e.title=n,e.titleSource="truncated";const t=this.getGateway();t?.isConnected&&t.sessionsPatch(e.gatewaySessionKey,{label:n}).catch(()=>{}),this.transport.sendSessionUpdate(s,{sessionUpdate:"title_update",title:n}),this.tryGenerateTitleFromHistory(e,s,a)}}}const u=t=>`history:${e.gatewaySessionKey}:${t}`;let p=-1;if(i)for(let e=a.length-1;e>=0;e--)if("assistant"===a[e].role){p=e;break}const{replayedToolCallIds:h,streamCompleteCount:g}=await this.replayHistoryTurnsToSink(e,a,this.transport,{mqActive:Boolean(i),lastAsstIdx:p,turnIdFor:(e,t)=>u(t)});for(const e of h)t.add(e);this.logger.debug?.(`[bridge-core] emitHistoryTurns DONE sid=${s} historyStreamCompleteEmitted=${g} replayedToolCallIds=${t.size}`)}catch(e){this.logger.warn?.("[bridge-core] Failed to load session history:",e)}return t}async replayHistoryTurnsToSink(e,t,s,n){const i=e.sessionId,r=new Set;let o=0;for(let a=0;a<t.length;a++){const d=t[a],c=n.turnIdFor(d,a);if(n.mqActive&&a===n.lastAsstIdx){const e=classifyHistoricalAssistantTurnCompletion(d,{mqActive:n.mqActive,isSkippedForLiveMq:!0});this.logger.debug?.(`[bridge-core] SKIP history_turn[${a}] assistant — ${"live"===e.kind?e.reason:"mq active"}, deferring to emitCurrentStreamSnapshot`);continue}if("user"===d.role&&"internal_system"===d.provenance?.kind){this.logger.debug?.(`[bridge-core] SKIP history_turn[${a}] internal_system user message`);continue}if("user"===d.role&&/^\s*system\s*\(untrusted[^)]*\)/i.test(d.content)){this.logger.debug?.(`[bridge-core] SKIP history_turn[${a}] system-injected user message`);continue}if("user"===d.role&&isHeartbeatPromptText(d.content)){this.logger.info?.(`[bridge-core] SKIP history_turn[${a}] heartbeat cron prompt sessionId=${e.sessionId} contentLen=${d.content.length} preview="${previewSanitizedText(d.content)}"`);continue}if("user"===d.role&&isCronPromptText(d.content)){this.logger.info?.(`[bridge-core] SKIP history_turn[${a}] cron-triggered user message sessionId=${e.sessionId} contentLen=${d.content.length} preview="${previewSanitizedText(d.content)}"`);continue}if("assistant"===d.role){const t=classifyHeartbeatFilter(d.content);if(t.filtered){this.logger.info?.(`[bridge-core] SKIP history_turn[${a}] heartbeat assistant reply reason=${t.reason} sessionId=${e.sessionId} contentLen=${d.content.length} preview="${previewSanitizedText(d.content)}"`);continue}}const l="assistant"===d.role?classifyHistoricalAssistantTurnCompletion(d,{mqActive:n.mqActive,isSkippedForLiveMq:!1}):void 0;if("error"===l?.kind||"interrupted"===l?.kind&&l.failure)s.sendSessionUpdate(i,{sessionUpdate:"agent_message_chunk",content:{type:"text",text:""},...d.timestamp?{timestamp:d.timestamp}:{}},{turnId:c,...void 0!==d._seq?{seq:d._seq}:{}}),s.sendSessionUpdate(i,{sessionUpdate:"stream_complete",stopReason:l.stopReason,error:l.message,errorCode:l.errorCode},{turnId:c}),this.logger.info?.(`[bridge-core] history_runtime_failure_terminalized session_id=${i} turn_id=${c} error_code=${l.errorCode} stop_reason=${l.stopReason} source=history_replay`),o+=1;else if("system"!==d.role)if(this.logger.debug?.(`[bridge-core] SENDING history_turn[${a}] role=${d.role} contentLen=${d.content.length} content=${JSON.stringify(d.content.slice(0,80))} tcCount=${d.toolCalls?.length??0}`),"user"===d.role)this.replayUserTurnAsChunks(s,i,d.content,d.metadata,d.timestamp,c,d._seq);else{if(await this.replayAssistantTurnAsStream(s,i,d,c),d.toolCalls)for(const e of d.toolCalls)e.id&&r.add(e.id);const e=l??classifyHistoricalAssistantTurnCompletion(d,{mqActive:n.mqActive,isSkippedForLiveMq:!1});if("interrupted"===e.kind){const t=new Set(e.incompleteToolCallIds);for(const n of d.toolCalls??[])t.has(n.id)&&s.sendSessionUpdate(i,buildToolCallUpdatePayload({toolCallId:n.id,toolName:n.name,outputText:e.message,kind:n.kind,status:"failed",timestamp:d.timestamp}),{turnId:c});this.logger.info?.(`[bridge-core] history_orphan_turn_terminalized session_id=${i} turn_id=${c} tool_call_ids=${e.incompleteToolCallIds.join(",")} stop_reason=${e.stopReason} source=history_replay`),s.sendSessionUpdate(i,{sessionUpdate:"stream_complete",stopReason:e.stopReason,error:e.message,errorCode:e.errorCode},{turnId:c}),o+=1;continue}if("completed"!==e.kind)continue;s.sendSessionUpdate(i,{sessionUpdate:"stream_complete",stopReason:e.stopReason},{turnId:c}),o+=1}else this.logger.debug?.(`[bridge-core] SENDING system_message[${a}] contentLen=${d.content.length}`),s.sendSessionUpdate(i,{sessionUpdate:"system_message_chunk",content:{type:"text",text:d.content},metadata:d.metadata,...d.timestamp?{timestamp:d.timestamp}:{}},{turnId:c,...void 0!==d._seq?{seq:d._seq}:{}})}return{replayedToolCallIds:r,streamCompleteCount:o}}async handleSessionHistoryPage(e){const t=e.params??{},s="string"==typeof t.sessionId?t.sessionId:"";if(!s)return void this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"sessionId"});const n="string"==typeof t.cursor&&t.cursor.length>0?t.cursor:void 0,i="number"==typeof t.limit&&Number.isFinite(t.limit)&&t.limit>0?Math.min(1e3,Math.floor(t.limit)):100,{session:r}=await this.resolveOrCreateSession(s),o=this.getGateway(),a=await loadHistoryPage(o,r.gatewaySessionKey,{...n?{cursor:n}:{},limit:i,logger:this.logger}),d=groupIntoTurns(a.messages),c=new HistoryUpdateRecorder(this.transport);await this.replayHistoryTurnsToSink(r,d,c,{mqActive:!1,lastAsstIdx:-1,turnIdFor:(e,t)=>void 0!==e._seq?`history:${r.gatewaySessionKey}:seq:${e._seq}`:`history:${r.gatewaySessionKey}:seq:none:${n??"head"}:${t}`}),this.logger.debug?.(`[bridge-core] _beeos/session/history sid=${r.sessionId} cursor=${n??"<head>"} limit=${i} turns=${d.length} updates=${c.updates.length} hasMore=${a.hasMore} nextCursor=${a.nextCursor??"<none>"}`),this.transport.sendResult(e.id,{updates:c.updates,...a.nextCursor?{nextCursor:a.nextCursor}:{},hasMore:a.hasMore})}emitCurrentStreamSnapshot(e,t){const s=e.pendingUserTurnUpdates;if(s&&s.length>0)for(const t of s)this.transport.sendSessionUpdate(e.sessionId,t);const n=e.assistantMq;if(n&&!n.closed){const s=n.peek(0);let i=0;if(t&&t.size>0)for(let e=0;e<s.length;e++){const n=s[e],r=n.sessionUpdate,o=n.toolCallId;if("tool_call"!==r&&"tool_call_update"!==r||!o||!t.has(o)){if("agent_message_chunk"===r){if(""===(n.content?.text??"").trim()&&e<s.length-1){const n=s[e+1];if(("tool_call"===n.sessionUpdate||"tool_call_update"===n.sessionUpdate)&&n.toolCallId&&t.has(n.toolCallId)){i=e+1;continue}}break}break}i=e+1}const r=resolveTurnId(e),o=r?{turnId:r}:void 0;for(let t=i;t<s.length;t++)this.transport.sendSessionUpdate(e.sessionId,s[t],o)}}async handleSessionList(e){let t;try{t=parseRuntimeSessionListPage(e.params)}catch(t){return void this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:t instanceof Error?t.message:"Invalid session/list params"})}const s=[];for(const[,e]of this.state.sessions)isBackgroundSessionKey(e.gatewaySessionKey)||this.cronIsolatedSessionKeys.has(e.gatewaySessionKey)||!t.includeDeleted&&this.state.deletedGatewaySessionKeys.has(e.gatewaySessionKey)||s.push({sessionId:e.sessionId,title:e.title||"New chat",cwd:e.cwd,updatedAt:e.lastStreamActivityTs??e.createdAt,agentId:e.agentId,platformAgentId:e.platformAgentId,deletable:!isSystemDefaultSessionKey(e.gatewaySessionKey)});const n=this.getGateway();if(n?.isConnected)try{const e=await n.sessionsList();if(e&&"object"==typeof e&&Array.isArray(e.sessions)){const n=e.sessions;for(const e of n){const n=e,i=n.sessionKey??n.key??n.id??n.name;if(!i||"string"!=typeof i)continue;if(isBackgroundSessionKey(i))continue;if(this.cronIsolatedSessionKeys.has(i))continue;const r=deriveSessionId(i,this.instanceId),o=s.find(e=>e.sessionId===i||e.sessionId===r||this.state.sessions.get(e.sessionId)?.gatewaySessionKey===i);if(!0===n.deleted){if(this.state.deletedGatewaySessionKeys.add(i),!t.includeDeleted)continue}else this.state.deletedGatewaySessionKeys.delete(i);const a=n.label||cleanGatewayDerivedTitle(n.derivedTitle)||n.title||void 0,d=a&&!isPlaceholderTitle(a)?a:void 0,c=n.model,l=n.modelProvider;if(o){if(d&&isPlaceholderTitle(o.title)){o.title=d;const e=this.state.sessions.get(o.sessionId);e&&isPlaceholderTitle(e.title)&&(e.title=d)}const e=n.updatedAt;e&&(o.updatedAt=e),c&&(o.model=c),l&&(o.modelProvider=l)}else{const e=createSessionObject({gatewayKey:i,instanceId:this.instanceId,resolvePlatformAgentId:e=>this.resolvePlatformAgentId(e),thirdPartyChannels:this.thirdPartyChannels,cwd:n.cwd,title:d});this.registerSession(e,{ensureVerbose:!1}),s.push({sessionId:e.sessionId,title:d||"",cwd:n.cwd,updatedAt:n.updatedAt,agentId:e.agentId,platformAgentId:e.platformAgentId,deletable:!isSystemDefaultSessionKey(i),model:c,modelProvider:l})}}}}catch(e){this.logger.debug?.("[bridge-core] sessions.list enrichment failed:",e)}const i=t.includeDeleted?s:s.filter(e=>{const t=this.state.sessions.get(e.sessionId)?.gatewaySessionKey;return!t||!this.state.deletedGatewaySessionKeys.has(t)});i.sort((e,t)=>(t.updatedAt??0)-(e.updatedAt??0)||e.sessionId.localeCompare(t.sessionId)),this.transport.sendResult(e.id,{sessions:pageRuntimeSessions(i,t)})}async handleSessionRename(e){const t=e.params?.sessionId,s=e.params?.title;if(!t||!s)return void this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"sessionId, title"});const n=s.slice(0,64),i=this.state.sessions.get(t);if(i)try{const s=await this.dispatchLegacyConversationCapability("session.rename",i,{title:n});i.title=s.title,i.titleSource="manual",this.transport.sendResult(e.id,{sessionId:t,title:s.title})}catch(s){this.logger.warn?.(`[bridge-core] ACP rename projection deferred: ${s instanceof Error?s.message:String(s)}`),i.title=n,i.titleSource="manual",this.transport.sendResult(e.id,{sessionId:t,title:n})}else this.transport.sendAcpError(e.id,Errors.SessionNotFound)}static TITLE_TEMPLATES=[{match:["今日日报","日报"],zh:"今日工作日报",en:"Daily Work Log"},{match:["周报","周报总结","本周周报"],zh:"本周工作周报",en:"Weekly Work Report"},{match:["月报","月度汇报","月度总结"],zh:"本月工作月报",en:"Monthly Work Report"},{match:["工作日志","日志"],zh:"工作日志整理",en:"Work Log Review"},{match:["晨会","今日晨会"],zh:"今日晨会纪要",en:"Daily Standup Notes"},{match:["复盘","今日复盘"],zh:"今日工作复盘",en:"Daily Retrospective"},{match:["新会话","new chat","new conversation"],zh:"新会话",en:"New Conversation"}];static TITLE_SYSTEM_PROMPT='You are a conversation title generator. Your task is to generate a concise, human-readable title summarizing the topic of the user\'s question together with the assistant\'s answer.\n\n### Rules:\n- Length: 3-6 words in English, or 6-20 characters in Chinese (up to 24 if needed to keep all key domain terms intact).\n- Language: Match the user\'s input language exactly. Mixed zh/en input → use the dominant language.\n- Extract the core topic (noun-centric) of the Q&A exchange, not the action verb.\n- Use the assistant\'s answer to disambiguate or enrich the topic when the question alone is vague (e.g. "帮我看下" + an answer about x402 → "x402 支付集成").\n- No punctuation, no quotation marks, no emojis, no markdown.\n- No prefix like "Title:" or "Here is". Output the title only.\n- Avoid generic words: "help", "question", "chat", "problem", "task".\n- If both the question and answer are too vague to summarize, return "新会话" (for zh) or "New Conversation" (for en).\n\n### Input Format:\nThe user message will contain both the question and the assistant\'s answer, delimited like this:\n[User Question]\n...question text...\n\n[Assistant Answer]\n...answer text (may be truncated with "..." in the middle)...\n\n### Output Format:\nRespond ONLY with valid JSON, no other text:\n{"title": "<your title here>"}\n\n### Examples:\nInput:\n[User Question]\n帮我写一个关于BeeOS设备接入的PRD\n\n[Assistant Answer]\n好的,这是 BeeOS 设备接入 PRD 的草稿:1. 目标 ...\nOutput: {"title": "BeeOS 设备接入 PRD"}\n\nInput:\n[User Question]\n帮我看下\n\n[Assistant Answer]\n你说的 x402 支付协议是一个基于 HTTP 402 的链上支付标准 ...\nOutput: {"title": "x402 支付协议解析"}\n\nInput:\n[User Question]\nWhat\'s the best way to integrate x402 payment protocol?\n\n[Assistant Answer]\nYou have three options: 1. Use the official SDK ...\nOutput: {"title": "x402 Payment Integration"}\n\nInput:\n[User Question]\nhi\n\n[Assistant Answer]\nHello! How can I help you today?\nOutput: {"title": "New Conversation"}';matchTitleTemplate(e){const t=e.trim().toLowerCase();for(const s of AcpGatewayBridgeCore.TITLE_TEMPLATES)if(s.match.some(e=>e.toLowerCase()===t))return/[\u4e00-\u9fff]/.test(e)?s.zh:s.en}isGuardMessage(e){const t=e.trim();return[...t].length<3||[...t].every(e=>/[\p{P}\p{S}\s]/u.test(e))}truncateQuestionForTitle(e){return e.length>2e3?e.slice(0,1500):e}truncateAnswerForTitle(e){return e.length<=1e3?e:`${e.slice(0,500)}\n...\n${e.slice(-500)}`}generateTitleFromQA(e,t,s,n){if(this.isGuardMessage(s))return void this.logger.info?.(`[bridge-core] title-gen SKIP guard message sid=${t}`);const i=this.matchTitleTemplate(s);if(i){e.title=i,e.titleSource="template";const s=this.getGateway();return s?.isConnected&&s.sessionsPatch(e.gatewaySessionKey,{label:i}).catch(()=>{}),void this.transport.sendSessionUpdate(t,{sessionUpdate:"title_update",title:i})}const r=(process.env.AROUTER_BASE_URL||"").replace(/\/$/,""),o=process.env.AROUTER_API_KEY||"";if(!r||!o)return void this.logger.info?.(`[bridge-core] title-gen SKIP sid=${t}: no AROUTER env vars`);const a=`[User Question]\n${this.truncateQuestionForTitle(s)}\n\n[Assistant Answer]\n${this.truncateAnswerForTitle(n)}`;this.logger.info?.(`[bridge-core] title-gen CALLING LLM sid=${t} q="${s.slice(0,60)}" aLen=${n.length}`),fetch(`${r}/v1/chat/completions`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${o}`},body:JSON.stringify({model:"openai/gpt-4o-mini",max_tokens:32,temperature:.3,messages:[{role:"system",content:AcpGatewayBridgeCore.TITLE_SYSTEM_PROMPT},{role:"user",content:a}]})}).then(async s=>{if(!s.ok){const e=await s.text().catch(()=>"");return void this.logger.info?.(`[bridge-core] title-gen FAILED sid=${t} status=${s.status} body=${e.slice(0,300)}`)}const n=await s.json(),i=n.choices?.[0]?.message?.content?.trim();if(!i)return;const r=i.replace(/^```json\s*/,"").replace(/```$/,"").trim();let o;try{o=JSON.parse(r).title?.trim()??""}catch{return void this.logger.debug?.(`[bridge-core] title-gen JSON parse failed: ${r}`)}if(!o)return;if("manual"===e.titleSource)return void this.logger.debug?.("[bridge-core] title-gen discarded: user already renamed");this.logger.info?.(`[bridge-core] title-gen OK sid=${t} title="${o}"`),e.title=o,e.titleSource="auto";const a=this.getGateway();a?.isConnected&&a.sessionsPatch(e.gatewaySessionKey,{label:o}).catch(()=>{}),this.transport.sendSessionUpdate(t,{sessionUpdate:"title_update",title:o})}).catch(e=>{this.logger.info?.(`[bridge-core] title-gen ERROR sid=${t}: ${e instanceof Error?e.message:e}`)})}async tryGenerateTitleFromHistory(e,t,s){if("manual"===e.titleSource||"auto"===e.titleSource)return;const n=this.getGateway();if(!n?.isConnected)return;let i=s;if(!i)try{const t=await loadHistory(n,e.gatewaySessionKey);i=groupIntoTurns(t)}catch(e){return void this.logger.debug?.(`[bridge-core] title-gen loadHistory failed sid=${t}: ${e instanceof Error?e.message:e}`)}const r=i.find(e=>{if("user"!==e.role)return!1;if(!e.content?.trim())return!1;if("internal_system"===e.provenance?.kind)return!1;if(isCronPromptText(e.content))return!1;const t=cleanHistoryUserContent(e.content);return!!t&&!/^Sender\s*\(untrusted/i.test(t)});if(!r)return;const o=i.indexOf(r),a=i.slice(o+1).find(e=>"assistant"===e.role&&!!e.content?.trim());if(!a)return;const d=cleanHistoryUserContent(r.content),c=a.content.trim();d&&c&&this.generateTitleFromQA(e,t,d,c)}async handleSessionDelete(e){const t=e.params?.sessionId;if(!t)return void this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"sessionId"});const s=this.state.sessions.get(t);if(!s)return this.logger.info?.(`[bridge-core] session/delete sessionId=${t} NOT FOUND in local state`),void this.transport.sendAcpError(e.id,Errors.SessionNotFound);const n=s.gatewaySessionKey,i=this.sessionMutationCoordinator.tryStartExclusive({sessionKey:n,label:"session_delete"},"acp");if(!i.ok)return void this.sendSessionBusy(s,e.id,"active_session_mutation",i.queueDepth);const r=i.token;try{if(this.hasActiveRun(s)){const t=this.sessionMutationCoordinator.gateSnapshot(n);return void this.sendSessionBusy(s,e.id,"active_session_mutation",t.queueDepth)}this.logger.info?.(`[bridge-core] session/delete sessionId=${t} gatewayKey=${n} gwConnected=${!!this.getGateway()?.isConnected}`);let i=!1;const r=this.getGateway();if(r?.isConnected&&n)try{await r.sessionsDelete(n),i=!0,this.logger.info?.(`[bridge-core] Gateway sessions.delete SUCCESS for key=${n}`)}catch(t){const s=t instanceof Error?t.message:String(t);if(this.logger.info?.(`[bridge-core] Gateway sessions.delete FAILED for key=${n}: ${s}`),!s.includes("Cannot delete the main session"))return void this.transport.sendResult(e.id,{deleted:!1});try{await r.sessionsReset(n),i=!0,this.logger.info?.(`[bridge-core] Gateway sessions.reset SUCCESS for key=${n}`)}catch(t){return this.logger.warn?.(`[bridge-core] Gateway sessions.reset also failed for key=${n}: ${t instanceof Error?t.message:t}`),void this.transport.sendResult(e.id,{deleted:!1})}}else n&&this.logger.warn?.(`[bridge-core] Gateway not connected, proceeding with local-only delete for key=${n}`);this.state.clearPromptTimeout(s),this.state.clearStreamIdleTimer(s),s.assistantMq?.close(),s.assistantMq=void 0,s.currentRunId&&this.state.promptsByRunId.delete(s.currentRunId);const o=new Set;for(const[e,s]of this.state.toolCallToSessionMap)s===t&&(this.state.toolCallToSessionMap.delete(e),o.add(e));for(const[e,t]of this.state.toolCallIdMap)o.has(t)&&this.state.toolCallIdMap.delete(e);this.state.sessions.delete(t),this.state.sessionsByGatewayKey.delete(n),this.state.deletedGatewaySessionKeys.add(n),this.transport.resetSessionUpdateCount(t),i&&this.onSessionDeleted?.(n),this.logger.info?.(`[bridge-core] session/delete DONE sessionId=${t} gatewayKey=${n} deleted=true`),this.transport.sendResult(e.id,{deleted:!0})}finally{this.sessionMutationCoordinator.releaseBeforeRuntime(r)}}async handleSessionClear(e){this.logger.info?.(`[bridge-core] handleSessionClear called, params=${JSON.stringify(e.params)}`);const t=e.params?.sessionId;if(!t)return this.logger.warn?.("[bridge-core] session/clear missing sessionId"),void this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"sessionId"});const s=this.state.sessions.get(t);if(!s)return this.logger.warn?.(`[bridge-core] session/clear sessionId=${t} not found in state`),void this.transport.sendAcpError(e.id,Errors.SessionNotFound);this.logger.info?.(`[bridge-core] session/clear sessionId=${t} gatewayKey=${s.gatewaySessionKey}`);const n=this.getGateway(),i=n?.isConnected;if(!s.promptDone&&(void 0!==s.pendingPromptId||void 0!==s.currentRunId)){this.logger.info?.(`[bridge-core] session/clear: cancelling active run sid=${t} runId=${s.currentRunId}`);const r=s.writeGateToken;let o=!1;if(i&&r){const e=await this.sessionMutationCoordinator.requestCancel(r,n);o="recovery_required"===await this.handleCancellationResult(s,e)}else i&&await n.chatAbort(s.gatewaySessionKey,s.currentRunId);return void this.transport.sendError(e.id,Errors.AgentError.rpcCode,o?SESSION_RECOVERY_REQUIRED_MESSAGE:"Cancellation is still converging",o?{code:SESSION_RECOVERY_REQUIRED_CODE,stopReason:"interrupted"}:{code:"session_cancellation_pending",stopReason:"cancelled"})}try{await this.dispatchLegacyConversationCapability("session.clear",s,{}),this.transport.sendResult(e.id,{cleared:!0}),this.logger.info?.("[bridge-core] session/clear DONE, sent cleared=true")}catch(t){this.sendRuntimeCommandError(e.id,t)}}async handleSessionSetMode(e){const t=e.params?.sessionId,s=e.params?.mode,n=t?this.state.sessions.get(t):void 0;if(t&&s)if(n)try{const t=await this.dispatchLegacyConversationCapability("session.setMode",n,{mode:s});this.transport.sendResult(e.id,t)}catch(t){this.sendRuntimeCommandError(e.id,t)}else this.transport.sendAcpError(e.id,Errors.SessionNotFound);else this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"sessionId, mode"})}async handleSessionSetModel(e){const t=e.params,s=null===t||"object"!=typeof t||Array.isArray(t)?void 0:t,n="string"==typeof s?.sessionId?s.sessionId:void 0;if(!n||!s||!Object.prototype.hasOwnProperty.call(s,"model"))return void this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"sessionId, model"});const i=s.model;if(null!==i&&("string"!=typeof i||0===i.length||i.trim()!==i))return void this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:"model must be a non-empty string or null"});const r=n?this.state.sessions.get(n):void 0;if(r)try{const t=await this.dispatchLegacyConversationCapability("session.setModel",r,{modelOverrideId:i});this.transport.sendResult(e.id,{model:t.effectiveModelId,modelProvider:t.modelProvider??null})}catch(t){this.sendRuntimeCommandError(e.id,t)}else this.transport.sendAcpError(e.id,Errors.SessionNotFound)}sendRuntimeCommandError(e,t){if(t instanceof RuntimeCommandError)switch(t.code){case"MISSING_PARAM":return void this.transport.sendAcpError(e,Errors.MissingParam,{...t.param?{param:t.param}:{}});case"INVALID_PARAMS":return void this.transport.sendAcpError(e,Errors.InvalidParams,{message:t.message});case"GATEWAY_NOT_CONNECTED":return void this.transport.sendAcpError(e,Errors.GatewayNotConnected);case"EXECUTION_FAILED":case"AGENT_DEFAULT_MODEL_NOT_CONFIGURED":case"OUTCOME_UNKNOWN":case"INSTANCE_EPOCH_MISMATCH":case"COMMAND_EXPIRED":case"IDEMPOTENCY_CONFLICT":case"UNAUTHORIZED_TARGET":return void this.transport.sendAcpError(e,Errors.InternalError,{message:t.message})}else this.transport.sendAcpError(e,Errors.InternalError,{message:t instanceof Error?t.message:"Internal error"})}createOperationId(){const e=randomUUID();return`${e.slice(0,14)}7${e.slice(15)}`}canonicalContextBase(e,t=this.createOperationId(),s="0"){const n=Date.now(),i=createHash("sha256").update(JSON.stringify(e)).digest("hex");return{operationId:t,runtimeInstanceUid:this.instanceId,dispatchEpoch:s,paramsHash:i,idempotencyKeyHash:createHash("sha256").update(t).digest("hex"),claimDeadline:new Date(n+3e4).toISOString(),executionDeadline:new Date(n+3e5).toISOString()}}acpExecutionAttempt(e){return createAcpCoreExecutionAttempt({operationId:e.context.operationId,runtimeEpoch:e.context.dispatchEpoch,resources:this.runtimeCommandResources,historyBoundary:this.localHistoryBoundary})}instanceCommand(e,t,s){return{name:e,params:t,context:{...this.canonicalContextBase(t,s.operationId,s.runtimeEpoch),scope:"instance"}}}async agentCommand(e,t,s,n){return{name:e,params:s,context:{...this.canonicalContextBase(s,n.operationId,n.runtimeEpoch),scope:"agent",agent:{runtimeLocalKey:t,runtimeAgentUid:await this.runtimeAgentIdentities.resolveOrCreate(t)}}}}async dispatchLegacyAgentCapability(e,t,s){const n=normalizeRuntimeAgentLocalKey(t),i=await this.runtimeLeases.claim(this.createOperationId());let r=!1;try{const t={name:e,params:s,context:{...this.canonicalContextBase(s,i.operationId,i.runtimeEpoch),scope:"agent",agent:{runtimeLocalKey:n,runtimeAgentUid:await this.runtimeAgentIdentities.resolveOrCreate(n)}}},o=this.runtimeCommands.dispatch(t,this.acpExecutionAttempt(t));return r=!0,await o.completion}finally{r||await this.runtimeLeases.release(i)}}async dispatchLegacyInstanceCapability(e,t){const s=await this.runtimeLeases.claim(this.createOperationId());let n=!1;try{const i={name:e,params:t,context:{...this.canonicalContextBase(t,s.operationId,s.runtimeEpoch),scope:"instance"}},r=this.runtimeCommands.dispatch(i,this.acpExecutionAttempt(i));return n=!0,await r.completion}finally{n||await this.runtimeLeases.release(s)}}async handleMcpList(e){const t=await this.dispatchLegacyInstanceCapability("mcp.list",{});this.transport.sendResult(e.id,{servers:Object.fromEntries(t.servers.map(e=>[e.name,{transport:e.transport,...e.url?{url:e.url}:{},...e.command?{command:e.command}:{},...e.args?{args:[...e.args]}:{},...e.cwd?{cwd:e.cwd}:{},...e.connectionTimeoutMs?{connectionTimeoutMs:e.connectionTimeoutMs}:{}}]))})}async handleMcpSet(e){const t=e.params&&"object"==typeof e.params?e.params:{},s="string"==typeof t.name?t.name:"";let n;try{if(requireSecretFreeMcpServerName(s),!t.server||"object"!=typeof t.server||Array.isArray(t.server))throw new Error("mcp/set requires name and server");const e=t.server,i=extractMcpCredentialMaterial(e),r={...e};delete r.headers,delete r.env;const o=e.transport??("string"==typeof e.command?"stdio":"string"==typeof e.url?"streamable-http":void 0),a=normalizeMcpPublicServerConfig({...r,transport:o}),d=createHash("sha256").update(canonicalizeJcs(a)).digest("hex");n={definition:{definitionRef:`acp-mcp:sha256:${d}`,version:"local-v1",sha256:d,...i?{credentialRef:await requireCredentialReferencePort(this.mcpCredentialReferences).put({serverName:s,material:i})}:{}},config:structuredClone(a)}}catch(t){return void this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:t instanceof Error?t.message:"Invalid MCP server config"})}this.runtimeCommandResources.registerMcpDefinition?.(n.definition,n.config);try{await this.dispatchLegacyInstanceCapability("mcp.set",{name:s,definition:n.definition})}catch(e){if(n.definition.credentialRef){!await this.mcp.removeCredentialIfUnreferenced(n.definition.credentialRef)&&this.mcpCredentialReferences?.owns(n.definition.credentialRef)&&await this.mcpCredentialReferences.remove(n.definition.credentialRef)}throw e}this.transport.sendResult(e.id,{ok:!0,restarting:!0})}async handleMcpUnset(e){const t=e.params&&"object"==typeof e.params?e.params:{};let s;try{s=requireSecretFreeMcpServerName(t.name)}catch{return void this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:"mcp/unset requires name"})}await this.dispatchLegacyInstanceCapability("mcp.unset",{name:s}),this.transport.sendResult(e.id,{ok:!0,restarting:!0})}async dispatchLegacyConversationCapability(e,t,s){const n=await this.runtimeLeases.claim(this.createOperationId());let i=!1;try{const r={name:e,params:s,context:{...this.canonicalContextBase(s,n.operationId,n.runtimeEpoch),scope:"conversation",conversation:{runtimeConversationUid:t.sessionId,runtimeSessionKey:t.gatewaySessionKey}}},o=this.runtimeCommands.dispatch(r,this.acpExecutionAttempt(r));return i=!0,await o.completion}finally{i||await this.runtimeLeases.release(n)}}async dispatchLegacyTerminalCapability(e,t,s,n){const i=await this.runtimeLeases.claim(this.createOperationId());let r=!1;try{const o={name:e,params:n,context:{...this.canonicalContextBase(n,i.operationId,i.runtimeEpoch),scope:"terminal",terminal:{terminalId:t,clientId:s}}},a=this.runtimeCommands.dispatch(o,this.acpExecutionAttempt(o));return r=!0,await a.completion}finally{r||await this.runtimeLeases.release(i)}}async dispatchLegacyCanvasNotification(e,t){const s=t&&"object"==typeof t?t:{},n=e.replace("/","."),i="canvas.toggle"===n?{visible:!0===s.visible||!0===s.enabled}:"canvas.reference"===n?{surfaceId:"string"==typeof s.surfaceId?s.surfaceId:""}:"canvas.dimensions"===n?{width:Number(s.width),height:Number(s.height),..."number"==typeof s.devicePixelRatio?{devicePixelRatio:s.devicePixelRatio}:{}}:{},r="string"==typeof s.sessionId?s.sessionId:"acp",o="string"==typeof s.canvasId?s.canvasId:r,a=await this.runtimeLeases.claim(this.createOperationId());let d=!1;try{const e={name:n,params:i,context:{...this.canonicalContextBase(i,a.operationId,a.runtimeEpoch),scope:"canvas",canvas:{canvasId:o,sessionId:r}}},t=this.runtimeCommands.dispatch(e,this.acpExecutionAttempt(e));d=!0,await t.completion}finally{d||await this.runtimeLeases.release(a)}}async applyCanonicalCanvasControl(e,t){if("canvas/reference"===e){const e=this.state.resolveSessionFromParams(t);if(!e)throw new RuntimeCommandError("INVALID_PARAMS","canvas session not found");const s="string"==typeof t.surfaceId?t.surfaceId:void 0;return void(e.referencedSurfaceId=s||void 0)}if(!this.handleCanvasNotification)throw new RuntimeCommandError("INVALID_PARAMS","canvas control delegate is not configured");await this.handleCanvasNotification(e,t)}async dispatchLegacyCronCapability(e,t){const s=normalizeRuntimeAgentLocalKey(this.config.gateway.agentId),n=await this.runtimeLeases.claim(this.createOperationId());let i=!1;try{const r="cron.add"===e?{...t,runtimeCronJobUid:`runtime-cron:${n.operationId}`}:"cron.run"===e?{...t,runToken:`runtime-run:${n.operationId}`}:t,o={name:e,params:r,context:{...this.canonicalContextBase(r,n.operationId,n.runtimeEpoch),scope:"agent",agent:{runtimeLocalKey:s,runtimeAgentUid:await this.runtimeAgentIdentities.resolveOrCreate(s)}}},a=this.runtimeCommands.dispatch(o,this.acpExecutionAttempt(o));return i=!0,await a.completion}finally{i||await this.runtimeLeases.release(n)}}validateConnectedGateway(){if(!this.getGateway()?.isConnected)throw new RuntimeCommandError("GATEWAY_NOT_CONNECTED","Gateway not connected")}async finishClaimedLegacyAgentMutation(e,t){let s=!1;try{const e=await t(),n=this.runtimeCommands.dispatch(e,this.acpExecutionAttempt(e));s=!0;const i=await n.completion;return queueMicrotask(()=>{this.observeAgentProjection()}),i}finally{s||await this.runtimeLeases.release(e)}}async observeAgentProjection(){await(async(e,t)=>{if(!t)return;let s;try{await Promise.race([Promise.resolve().then(t),new Promise((t,n)=>{s=setTimeout(()=>n(new Error(`${e} timed out`)),this.projectionObservationTimeoutMs)})])}catch(t){this.logger.warn?.(`[acp] ${e} failed: ${t instanceof Error?t.message:String(t)}`)}finally{s&&clearTimeout(s)}})("agent projection sync",this.triggerAgentSync)}async handleAgentCreate(e){try{const t=e.params?.name?.trim()??"";if(!t)throw new RuntimeCommandError("INVALID_PARAMS","Missing agent name");this.validateConnectedGateway();const s=this.createOperationId(),n=await this.runtimeLeases.claim(s),i=await this.finishClaimedLegacyAgentMutation(n,async()=>{const s=await this.runtimeAgentIdentities.allocateCreate(n,"agent.create"),i=Object.entries(e.params?.files??{}).filter(e=>"string"==typeof e[1]).map(([e,t])=>({name:e,contentRef:this.runtimeCommandResources.registerContent(t)}));return this.instanceCommand("agent.create",{runtimeAgentUid:s.runtimeAgentUid,name:t,...e.params?.identity?{identity:e.params.identity}:{},...e.params?.model?{defaultModelId:e.params.model}:{},...i.length?{bootstrapFiles:i}:{}},n)});this.transport.sendResult(e.id,{agentId:i.runtimeLocalKey,platformAgentId:this.resolvePlatformAgentId(i.runtimeLocalKey)??"",workspace:i.workspacePath})}catch(t){this.sendRuntimeCommandError(e.id,t)}}async handleApplyTemplate(e){try{const t=e.params?.name?.trim()??"";if(!t)throw new RuntimeCommandError("INVALID_PARAMS","Missing agent name");const s=e.params?.packageUrl?.trim()??"";if(!s)throw new RuntimeCommandError("MISSING_PARAM","Missing packageUrl","packageUrl");this.validateConnectedGateway();const n=await this.runtimeCommandResources.registerArtifact(s,e.params?.sha256),i=this.createOperationId(),r=await this.runtimeLeases.claim(i),o=await this.finishClaimedLegacyAgentMutation(r,async()=>{const s=await this.runtimeAgentIdentities.allocateCreate(r,"agent.applyTemplate");return this.instanceCommand("agent.applyTemplate",{runtimeAgentUid:s.runtimeAgentUid,name:t,artifact:n,...e.params?.identity?{identity:e.params.identity}:{},...e.params?.model?{defaultModelId:e.params.model}:{}},r)});this.transport.sendResult(e.id,{agentId:o.runtimeLocalKey,platformAgentId:this.resolvePlatformAgentId(o.runtimeLocalKey)??"",workspace:o.workspacePath})}catch(t){this.sendRuntimeCommandError(e.id,t)}}async handleAgentUpdate(e){try{const t=e.params?.agentId?.trim()??"";if(!t)throw new RuntimeCommandError("MISSING_PARAM","Missing agentId","agentId");const s={...void 0!==e.params?.name?{name:e.params.name}:{},...void 0!==e.params?.model?{defaultModelId:e.params.model}:{},...void 0!==e.params?.identity?{identity:e.params.identity}:{}};if(0===Object.keys(s).length)throw new RuntimeCommandError("INVALID_PARAMS","No updatable fields provided (name/model/identity)");this.validateConnectedGateway();const n=await this.runtimeLeases.claim(this.createOperationId()),i=await this.finishClaimedLegacyAgentMutation(n,()=>this.agentCommand("agent.update",t,s,n));this.transport.sendResult(e.id,{agentId:i.runtimeLocalKey})}catch(t){this.sendRuntimeCommandError(e.id,t)}}async handleAgentDelete(e){try{const t=e.params?.agentId?.trim()??"";if(!t)throw new RuntimeCommandError("MISSING_PARAM","Missing agentId","agentId");if("main"===normalizeRuntimeAgentLocalKey(t))throw new RuntimeCommandError("INVALID_PARAMS",'"main" cannot be deleted; delete the instance instead');this.validateConnectedGateway();const s=await this.runtimeLeases.claim(this.createOperationId()),n=await this.finishClaimedLegacyAgentMutation(s,()=>this.agentCommand("agent.delete",t,void 0===e.params?.deleteFiles?{}:{deleteFiles:e.params.deleteFiles},s));this.transport.sendResult(e.id,{agentId:n.runtimeLocalKey,...void 0===n.removedBindings?{}:{removedBindings:n.removedBindings}})}catch(t){this.sendRuntimeCommandError(e.id,t)}}async handleAgentList(e){try{const t=await this.dispatchLegacyInstanceCapability("agent.listProjection",{}),s=t.agents.map(e=>({agentId:e.runtimeLocalKey,name:e.name,...e.emoji?{emoji:e.emoji}:{},...e.avatarUrl?{avatarUrl:e.avatarUrl}:{}})),n={...t.defaultRuntimeLocalKey?{defaultId:t.defaultRuntimeLocalKey}:{},agents:s};this.transport.sendResult(e.id,n)}catch(t){this.sendRuntimeCommandError(e.id,t)}}async handleModelsList(e){try{this.transport.sendResult(e.id,await this.dispatchLegacyInstanceCapability("models.list",{}))}catch(t){this.sendRuntimeCommandError(e.id,t)}}async handleSkillsList(e){try{const t=await this.dispatchLegacyAgentCapability("skills.list",e.params?.agentId,{});this.transport.sendResult(e.id,{skills:t.skills.map(e=>({name:e.name,description:e.description??"",...e.emoji?{emoji:e.emoji}:{},...e.always?{always:!0}:{},...e.bundled?{bundled:!0}:{},...e.enabled?{}:{disabled:!0},skillKey:e.skillKey,...e.iconUrl?{iconUrl:e.iconUrl}:{}}))})}catch(t){this.sendRuntimeCommandError(e.id,t)}}async fetchSimplifiedSkills(e){const t=this.getGateway();if(!t?.isConnected)return[];try{const s=await t.skillsStatus(e);if(!s?.skills)return[];const n=s.skills.filter(e=>!1!==e.eligible||!0===e.disabled).sort((e,t)=>e.always&&!t.always?-1:!e.always&&t.always?1:e.name.localeCompare(t.name)).map(e=>({name:e.name,description:e.description,emoji:e.emoji,always:e.always||void 0,bundled:e.bundled||void 0,disabled:e.disabled||void 0,skillKey:e.skillKey||void 0,iconUrl:void 0})),i=await this.getSkillsDir(e);for(const e of n){const t=e.skillKey??e.name;try{const s=await readFile(join(i,t,".beeos-meta.json"),"utf8"),n=JSON.parse(s);n?.iconUrl&&(e.iconUrl=n.iconUrl)}catch{}}return n}catch(e){return this.logger.warn?.(`[bridge-core] Failed to fetch skills: ${e instanceof Error?e.message:e}`),[]}}async pushSkillsList(e){const t=await this.fetchSimplifiedSkills(e);this.transport.sendNotification("session/update",{sessionId:"_system",update:{sessionUpdate:"available_commands_update",commands:t}}),this.writeObs({component:"bridge-core",domain:"lifecycle",name:"skills.pushed",severity:"debug",payload:{count:t.length}})}getOpenclawHome(){return process.env.OPENCLAW_HOME||join(homedir(),".openclaw")}async getSkillsDir(e){const t=this.getOpenclawHome();if(!e||"main"===e)return join(t,"skills");const s=await this.resolveAgentWorkspaceDir(e);return join(s??join(t,`workspace-${e}`),"skills")}async resolveAgentWorkspaceDir(e){const t=this.getGateway();if(!t?.isConnected)return null;try{const s=await t.request("agents.list",{}),n=normalizeAgentId(e),i=s?.agents?.find(e=>normalizeAgentId(e.id)===n);return i?.workspace??null}catch(t){return this.logger.warn?.(`[bridge-core] resolveAgentWorkspaceDir("${e}") failed: ${t instanceof Error?t.message:t}`),null}}async handleSkillsInstall(e){const t=e.params,s=t?.zipUrl,n=t?.files,i=t?.agentId;let r;try{r=validateSkillSlug(t?.slug||t?.name||"")}catch{return void this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:"Invalid or missing skill slug"})}if(n?.length||s)try{let o;if(s){o=parseSkillZip(await fetchWithRetry(s,{maxRetries:4,logger:this.logger}))}else{if(!n?.length)throw new RuntimeCommandError("INVALID_PARAMS","Missing skill files or zipUrl");o=new Map;for(const e of n){const t=normalizeSkillArtifactPath(e.path),s=await fetch(e.downloadUrl,{signal:AbortSignal.timeout(12e4)});if(!s.ok)throw new Error(`Failed to download ${e.path}: ${s.status}`);o.set(t,new Uint8Array(await s.arrayBuffer()))}}if(t?.iconUrl&&o.set(".beeos-meta.json",(new TextEncoder).encode(JSON.stringify({iconUrl:t.iconUrl}))),!this.runtimeCommandResources.registerParsedArtifact)throw new RuntimeCommandError("EXECUTION_FAILED","ACP skill artifact ingress is not configured");const a=this.runtimeCommandResources.registerParsedArtifact({manifest:{slug:r,version:"0.0.0"},workspaceFiles:o});await this.dispatchLegacyAgentCapability("skills.install",i,{artifact:a}),this.transport.sendResult(e.id,{success:!0}),this.onSkillInstalled?.(r,resolveSkillInstallDir(await this.getSkillsDir(i),r)),await this.pushSkillsList(i)}catch(t){this.logger.error?.(`[bridge-core] Failed to install skill "${r}": ${t instanceof Error?t.message:t}`),this.transport.sendAcpError(e.id,Errors.InternalError,{message:`Install failed: ${t instanceof Error?t.message:String(t)}`})}else this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:"Missing skill files or zipUrl"})}async handleSkillsUninstall(e){const t=e.params,s=t?.agentId;let n;try{n=validateSkillSlug(t?.slug||t?.name||"")}catch{return void this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:"Invalid or missing skill name"})}try{await this.dispatchLegacyAgentCapability("skills.uninstall",s,{skillKey:n}),this.transport.sendResult(e.id,{success:!0}),await this.pushSkillsList(s)}catch(t){this.logger.error?.(`[bridge-core] Failed to uninstall skill "${n}": ${t instanceof Error?t.message:t}`),this.transport.sendAcpError(e.id,Errors.InternalError,{message:`Uninstall failed: ${t instanceof Error?t.message:String(t)}`})}}async handleSkillsUpdate(e){const t=e.params,s=t?.skillKey,n=t?.enabled,i=t?.agentId;if(s&&"boolean"==typeof n)try{await this.dispatchLegacyAgentCapability("skills.setEnabled",i,{skillKey:s,enabled:n}),this.logger.info?.(`[bridge-core] Updated skill "${s}" enabled=${n}${i?` for agent "${i}"`:""}`),this.transport.sendResult(e.id,{ok:!0,skillKey:s,enabled:n}),await this.pushSkillsList(i)}catch(t){this.logger.error?.(`[bridge-core] Failed to update skill "${s}": ${t instanceof Error?t.message:t}`),this.transport.sendAcpError(e.id,Errors.InternalError,{message:`Update failed: ${t instanceof Error?t.message:String(t)}`})}else this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:"Missing skillKey or enabled"})}async handleSessionPrompt(e){const t=e.params?.sessionId,s=e.params?.prompt;if(!t||!s)return void this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"sessionId, prompt"});const{session:n,created:i}=await this.resolveOrCreateSession(t);i&&this.onSessionLoaded?.(n.sessionId,n.gatewaySessionKey,n.platformAgentId);const r=n.sessionId;this.state.lastActiveSessionId=r,this.onPromptStart?.();const o=this.getGateway();if(!o?.isConnected)return void this.transport.sendAcpError(e.id,Errors.GatewayNotConnected);if(this.hasActiveRun(n)){const t=this.sessionMutationCoordinator.gateSnapshot(n.gatewaySessionKey);return void this.sendSessionBusy(n,e.id,"active_run",t.queueDepth)}try{await this.ensureVerboseFull(n)}catch(t){return this.releaseSessionWriteGate(n),void this.transport.sendAcpError(e.id,Errors.InternalError,{message:`Failed to prepare agent context: ${t instanceof Error?t.message:String(t)}`})}let a=extractPromptBlocks(sanitizePromptPayload(s));const d=a.filter(e=>"text"===e.type&&"text"in e&&"string"==typeof e.text).map(e=>e.text).join("\n");if(n.fileArtifactDisplayMode=prefersChatFileArtifacts(d)?"chat":"auto",!n.title||isPlaceholderTitle(n.title)){const e=a.find(e=>"text"===e.type&&e.text);if(e&&"text"in e&&e.text){const t=stripTransportMetadata(e.text).trim();if(this.logger.info?.(`[bridge-core] title-gen sid=${r} existing="${n.title}" cleanText="${t.slice(0,80)}" isPlaceholder=${isPlaceholderTitle(t)}`),t&&!isPlaceholderTitle(t)){const e=t.slice(0,64).replace(/\n/g," ").trim();n.title=e,n.titleSource="truncated",o?.isConnected&&e&&o.sessionsPatch(n.gatewaySessionKey,{label:e}).catch(e=>{this.logger.debug?.(`[bridge-core] sessionsPatch (title) failed: ${e instanceof Error?e.message:e}`)}),this.transport.sendSessionUpdate(r,{sessionUpdate:"title_update",title:e})}}}const c=buildResolutionPlan(a);if(c.refs.length>0)try{const e=await resolveResources(c,this.writeObs);a=applyResolutions(a,e)}catch(e){this.logger.warn?.(`[bridge-core] Resource pre-resolution failed: ${e instanceof Error?e.message:e}`)}let l;if(this.beeosFileResolver){const e=this.beeosFileResolver.buildResolutionPlan(a);if(e.refs.length>0)try{l=await this.beeosFileResolver.resolveMetadataForPrompt(e)}catch(e){this.logger.warn?.(`[bridge-core] BeeOS file resolution failed: ${e instanceof Error?e.message:e}`)}}const m=toGatewayPrompt(a,{beeosFileResolutionsByUri:l});if("error"in m)return this.releaseSessionWriteGate(n),void this.transport.sendAcpError(e.id,Errors.PromptConvertFailed,{message:m.error});const{message:u,attachments:p}=m,h=resolveStandaloneSlashCommandName(a,u)??resolveSlashCmd(a,u),g="string"==typeof h;if(h&&this.handleSlashCommand){const t=this;if(await(async()=>{try{return await t.handleSlashCommand(h)}catch(e){return t.logger.warn?.(`[bridge-core] /${h} handler failed: ${e instanceof Error?e.message:e}`),!1}})())return this.emitPromptAsUserSessionUpdates(n,a,resolveTurnId(n)),this.transport.sendSessionUpdate(r,{sessionUpdate:"agent_message_chunk",content:{type:"text",text:`Loading ${h}...`},timestamp:(new Date).toISOString()},{turnId:resolveTurnId(n)}),this.transport.sendResult(e.id,{stopReason:"end_turn"}),void this.releaseSessionWriteGate(n)}const y="chat.send";let f=g?u:withUserMessagePrefix(u);const w=this.config.canvasEnabled&&!g&&!0===n.canvasActive&&!n.channelType;if(w){const e=n.promptsSinceCanvasInjection??0;!n.canvasPromptInjected||e>=10?(f=`${CANVAS_TERM_MAPPING}\n\n${f}`,n.canvasPromptInjected=!0,n.promptsSinceCanvasInjection=0,this.logger.debug?.("[bridge-core] Canvas term mapping injected (re-inject cycle)")):n.promptsSinceCanvasInjection=e+1}if(w){const e=this.getCanvasState?.();if(e){const t=e.resolveCanvasForSession(n.gatewaySessionKey);f=`${buildActiveSurfacesContext(e,t)??"[Canvas] Canvas panel is available. Use render_ui for structured/visual content."}\n\n${f}`}const t=n.referencedSurfaceId;t&&(f=`${CANVAS_UPDATE_HINT}\n[IMPORTANT: The user selected canvas surface "${t}" for editing. You MUST call render_ui with id="${t}" to UPDATE this existing surface. Do NOT create a new surface with a different id.]\n${f}`,n.referencedSurfaceId=void 0)}if(!g){const t=parseMentionsParam(e.params?.mentions);if(t.length>0){n.activeMentions=t;f=`${buildMentionInstruction(t)}\n${f}`,this.logger.debug?.(`[bridge-core] delegation mention instruction injected for ${t.length} agent(s)`)}else n.activeMentions=void 0}if(g&&this.logger.info?.(`[bridge-core] slash command passthrough sessionId=${r} command=/${h}`),this.hasActiveRun(n)){const t=this.sessionMutationCoordinator.gateSnapshot(n.gatewaySessionKey);return void this.sendSessionBusy(n,e.id,"active_run_after_prepare",t.queueDepth)}const I=this.sessionMutationCoordinator.tryStartInteractive({sessionKey:n.gatewaySessionKey,label:"session/prompt"},"acp");if(!I.ok)return void this.sendSessionBusy(n,e.id,I.reason,I.queueDepth);n.writeGateToken=I.token,this.state.resetForNewRun(n,r,this.transport),n.pendingPromptId=e.id,n.gatewayMethod=y;const v=resolveTurnId(n);this.emitPromptAsUserSessionUpdates(n,a,v),this.transport.sendSessionUpdate(r,{sessionUpdate:"agent_message_chunk",content:{type:"text",text:""},timestamp:(new Date).toISOString()},v?{turnId:v}:void 0);const S=extractCronJobId(u);S&&(n.cronJobId=S),this.schedulePromptTimeout(n);const C=p.length>0?p.map(e=>({type:e.type,content:e.content,...e.mimeType?{mimeType:e.mimeType}:{},...e.fileName?{fileName:e.fileName}:{}})):void 0,R=`req_${randomUUID().replace(/-/g,"")}`,b={sessionKey:n.gatewaySessionKey,message:f,deliver:!1,idempotencyKey:randomUUID()};C?.length&&(b.attachments=C);if(!o.sendFrame({type:"req",id:R,method:y,params:b}))return this.state.clearPromptTimeout(n),n.pendingPromptId=void 0,n.assistantMq?.close(),n.assistantMq=void 0,n.pendingUserTurnUpdates=void 0,this.releaseSessionWriteGate(n),void this.transport.sendAcpError(e.id,Errors.SendFailed,{message:"failed to send prompt to gateway"});n.gatewayRequestId=R,this.state.promptsByGatewayRequestId.set(R,n)}replayUserTurnAsChunks(e,t,s,n,i,r,o){const a=splitUserReplayBlocks(s),d=r||void 0!==o?{...r?{turnId:r}:{},...void 0!==o?{seq:o}:{}}:void 0;for(const s of a){let r;if("text"===s.type){if(!s.text)continue;r={type:"text",text:s.text}}else r="resource_link"===s.type?{type:"resource_link",uri:s.uri??"",...s.fileName?{name:s.fileName}:{},...s.mimeType?{mimeType:s.mimeType}:{}}:{type:s.type,...s.mimeType?{mimeType:s.mimeType}:{},...s.fileName?{fileName:s.fileName}:{}};e.sendSessionUpdate(t,{sessionUpdate:"user_message_chunk",content:r,...n?{metadata:n}:{},...i?{timestamp:i}:{}},d)}}async replayAssistantTurnAsStream(e,t,s,n){const i=e=>{if(n||void 0!==e)return{...n?{turnId:n}:{},...void 0!==e?{seq:e}:{}}},r=new Set,o=(s,n,o)=>{if(!s?.length)return;const a=i(o);for(const i of s)i.uri&&!r.has(i.uri)&&(r.add(i.uri),e.sendSessionUpdate(t,{sessionUpdate:"agent_message_chunk",content:{type:"resource_link",uri:i.uri,name:i.name??"file",...i.mimeType?{mimeType:i.mimeType}:{}},...n?{timestamp:n}:{}},a))},a=i(s._seq);if(!s.sourceMessages?.length){const n={sessionUpdate:"history_turn",role:s.role,content:s.content,reasoning:s.reasoning,toolCalls:s.toolCalls,...s.timestamp?{timestamp:s.timestamp}:{}};return s.metadata&&(n.metadata=s.metadata),e.sendSessionUpdate(t,n,a),void o(s.files,s.timestamp,s._seq)}for(const n of s.sourceMessages){const r=n.updateType??"agent_message_chunk",a=i(n._seq);switch(r){case"agent_message_chunk":e.sendSessionUpdate(t,{sessionUpdate:"agent_message_chunk",content:{type:"text",text:n.content},...n.timestamp?{timestamp:n.timestamp}:{},...n.metadata?{metadata:n.metadata}:{}},a);break;case"agent_thought_chunk":e.sendSessionUpdate(t,{sessionUpdate:"agent_thought_chunk",content:{type:"text",text:n.content},...n.timestamp?{timestamp:n.timestamp}:{}},a);break;case"tool_call":{const i=n.toolCallId??"",r=s.toolCalls?.find(e=>e.id===i),o=n.toolName??"tool",d=n.content??"";let c={};if(d)try{c=JSON.parse(d)}catch{}e.sendSessionUpdate(t,buildToolCallPayload({toolCallId:i,toolName:o,args:c,kind:r?.kind,status:r?.status??"in_progress",timestamp:n.timestamp}),a);break}case"tool_call_update":{const i=n.toolCallId??"",r=s.toolCalls?.find(e=>e.id===i),d=n.toolName??r?.name??"tool",c=n.content??r?.output??"",l=await this.resolveReplayResourceLinks(i,d,n.files);e.sendSessionUpdate(t,buildToolCallUpdatePayload({toolCallId:i,toolName:d,outputText:c,kind:r?.kind,status:r?.status??"completed",resourceLinks:l,timestamp:n.timestamp}),a),o(l,n.timestamp,n._seq);break}}}o(s.files,s.timestamp,s._seq)}async resolveReplayResourceLinks(e,t,s){let n=s?.map(e=>({uri:e.uri,name:e.name??"file",...e.mimeType?{mimeType:e.mimeType}:{}}));if(n?.length)return n;if(n=await readEnrichmentCache(e).catch(()=>{}),n?.length)return n;if("beeos_upload_file"!==t)return;const i=await readUploadToolResult(e).catch(()=>{});if(!i)return;const r=buildToolResultSessionContentBlocks(t,i).filter(e=>"resource_link"===e.content?.type&&e.content.uri).map(e=>({uri:e.content.uri,name:e.content.name??"file",...e.content.mimeType?{mimeType:e.content.mimeType}:{}}));return r.length>0?r:void 0}emitPromptAsUserSessionUpdates(e,t,s){const n=(new Date).toISOString(),i=s?{turnId:s}:void 0;for(const r of t){const t=this.toUserSessionUpdateContent(r);if(!t)continue;const o={sessionUpdate:"user_message_chunk",content:t,timestamp:n};s&&void 0===o._turnId&&(o._turnId=s),this.transport.sendSessionUpdate(e.sessionId,o,i),e.pendingUserTurnUpdates?.push(o)}}toUserSessionUpdateContent(e){if("text"===e.type){if(!e.text)return;return{type:"text",text:e.text}}if("image"===e.type){const t=e.mimeType??e.mime_type??"application/octet-stream",s=e.fileName??e.file_name??e.name;if(!e.data&&!e.uri)return;return{type:"image",...e.data?{data:e.data}:{},...e.uri?{uri:e.uri}:{},...t?{mimeType:t}:{},...s?{fileName:s}:{}}}if("resource_link"===e.type){if(!e.uri)return;return{type:"resource_link",uri:e.uri,...e.title?{title:e.title}:{},...e.name?{name:e.name}:{},...e.mimeType??e.mime_type?{mimeType:e.mimeType??e.mime_type}:{}}}if("file"===e.type){const t=e.mimeType??e.mime_type??"application/octet-stream",s=e.fileName??e.file_name??e.filename??e.name;if(!e.data&&!e.text&&!e.uri)return;return{type:"file",...e.data?{data:e.data}:{},...e.text?{text:e.text}:{},...e.uri?{uri:e.uri}:{},...t?{mimeType:t}:{},...s?{fileName:s}:{}}}if("resource"===e.type){const t=e.resource??{},s=t.uri??e.uri,n=t.mimeType??t.mime_type??e.mimeType??e.mime_type,i=t.text??e.text,r=t.data??e.data,o=t.fileName??t.file_name??t.filename??t.name??e.fileName??e.file_name??e.filename??e.name;if(!s&&!i&&!r)return;return{type:"resource",resource:{...s?{uri:s}:{},...n?{mimeType:n}:{},...i?{text:i}:{},...r?{data:r}:{},...o?{fileName:o}:{}}}}}async handleSessionCancel(e,t){const s=t&&"object"==typeof t&&"sessionId"in t?t.sessionId:void 0;if(!s)return void(void 0!==e&&this.transport.sendAcpError(e,Errors.MissingParam,{param:"sessionId"}));const n=this.state.sessions.get(s);if(!n)return void(void 0!==e&&this.transport.sendAcpError(e,Errors.SessionNotFound));const i=n.activeRunBinding??this.observeActiveRun(n);if(!i){const t=this.sessionMutationCoordinator.snapshot(n.gatewaySessionKey);if("exclusive"===t?.attachmentPolicy&&void 0!==e){const t=this.sessionMutationCoordinator.gateSnapshot(n.gatewaySessionKey);return void this.sendSessionBusy(n,e,"exclusive_session_mutation",t.queueDepth)}return void(void 0!==e&&this.transport.sendResult(e,{}))}const r=await this.runtimeLeases.claim(this.createOperationId());let o=!1;try{const s=t&&"object"==typeof t&&"reason"in t&&"string"==typeof t.reason?t.reason:void 0,n={targetMessageId:i.targetMessageId,...s?{reason:s}:{}},a={name:"session.cancel",params:n,context:{...this.canonicalContextBase(n,r.operationId,r.runtimeEpoch),scope:"conversation",historyGeneration:i.historyGeneration,conversation:{runtimeConversationUid:i.runtimeConversationUid,runtimeSessionKey:i.runtimeSessionKey}}},d=this.runtimeCommands.dispatch(a,this.acpExecutionAttempt(a));o=!0,await d.completion,void 0!==e&&this.transport.sendResult(e,{})}catch(t){void 0!==e&&this.sendRuntimeCommandError(e,t)}finally{o||await this.runtimeLeases.release(r)}}hasActiveRun(e){return!e.promptDone&&(void 0!==e.pendingPromptId||void 0!==e.currentRunId||void 0!==e.gatewayRequestId)}observeLifecycle(e,t,s){observeLifecyclePromise(e,this.logger,{component:"bridge-core",action:t,...s?{sessionId:s.sessionId,gatewaySessionKey:s.gatewaySessionKey}:{}})}async handleCancellationResult(e,t){return t.ok?(this.observeLifecycle(t.terminalObservation.then(t=>{if(!t.ok)return this.publishCancellationRecovery(e)}),"cancel_terminal_observation",e),"accepted"):"recovery"in t?(await this.publishCancellationRecovery(e),"recovery_required"):"terminal_already_observed"}async publishCancellationRecovery(e){await this.endRun(e,{kind:"interrupted",stopReason:"interrupted",message:SESSION_RECOVERY_REQUIRED_MESSAGE,errorCode:SESSION_RECOVERY_REQUIRED_CODE})}releaseSessionWriteGate(e){if(!e.writeGateToken)return;const t=e.writeGateToken;e.writeGateToken=void 0,this.sessionMutationCoordinator.releaseBeforeRuntime(t)}logRuntimeFailure(e,t){this.logger.warn?.(`[bridge-core] runtime_failure_classified session_id=${e.sessionId} gateway_session_key=${e.gatewaySessionKey} run_id=${e.currentRunId??""} gateway_request_id=${e.gatewayRequestId??""} error_code=${t.errorCode} stop_reason=${t.stopReason} source=${t.source} runtime=${t.runtimeKind}`)}sendSessionBusy(e,t,s,n){this.logger.warn?.(`[bridge-core] session_write_gate_busy session_id=${e.sessionId} gateway_session_key=${e.gatewaySessionKey} reason=${s} queue_depth=${n}`),this.transport.sendError(t,Errors.AgentError.rpcCode,"Agent is busy",{code:"session_busy",reason:s,queueDepth:n}),this.transport.sendSessionUpdate(e.sessionId,{sessionUpdate:"stream_complete",stopReason:"interrupted",error:INTERRUPTED_PUBLIC_MESSAGE,errorCode:"session_busy"},{turnId:`req:${String(t)}`})}async endRun(e,t){if(e.promptDone||e.mutationTerminalCommitting)return this.logger.debug?.(`[bridge-core] endRun skipped (promptDone=true) sessionId=${e.sessionId} outcome=${t.kind}`),!1;if(void 0===e.pendingPromptId&&void 0===e.currentRunId&&void 0===e.gatewayRequestId)return this.logger.debug?.(`[bridge-core] endRun skipped (no pendingPromptId and no currentRunId) sessionId=${e.sessionId} outcome=${t.kind}`),!1;e.mutationTerminalCommitting=!0,this.state.clearPromptTimeout(e),this.state.clearStreamIdleTimer(e);const s=e.writeGateToken,n=e.activeRunBinding;let i,r,o,a,d="timeout"===e.mutationTerminalOverride?{kind:"interrupted",stopReason:"timeout",message:"The agent did not finish before the session timeout.",errorCode:"gateway_timeout"}:t;if(s){const e="complete"===d.kind?"cancelled"===d.stopReason?"cancelled":"completed":"interrupted"===d.kind&&"timeout"===d.stopReason?"timeout":"error",t=await this.sessionMutationCoordinator.converge(s,e);t.ok?i=t.permit:d={kind:"interrupted",stopReason:"interrupted",message:SESSION_RECOVERY_REQUIRED_MESSAGE,errorCode:SESSION_RECOVERY_REQUIRED_CODE}}try{const t=resolveTurnId(e),s=t?{turnId:t}:void 0;e.promptDone=!0;const n=e.currentRunId;if("complete"===d.kind){if(n&&this.onRunComplete){const t=e.agentAssistantTextSoFar??e.chatAssistantTextSoFar??"";this.onRunComplete(n,t,d.stopReason)}void 0!==e.pendingPromptId&&this.transport.sendResult(e.pendingPromptId,{stopReason:d.stopReason}),this.transport.sendSessionUpdate(e.sessionId,{sessionUpdate:"stream_complete",stopReason:d.stopReason},s),this.tryGenerateTitleFromHistory(e,e.sessionId)}else"interrupted"===d.kind?(n&&this.onRunFailed&&this.onRunFailed(n,d.message),void 0!==e.pendingPromptId&&this.transport.sendError(e.pendingPromptId,Errors.AgentError.rpcCode,d.message,{code:d.errorCode??"interrupted",stopReason:d.stopReason}),this.transport.sendSessionUpdate(e.sessionId,{sessionUpdate:"stream_complete",stopReason:d.stopReason,error:d.message,...d.errorCode?{errorCode:d.errorCode}:{}},s)):(n&&this.onRunFailed&&this.onRunFailed(n,d.message),void 0!==e.pendingPromptId&&this.transport.sendError(e.pendingPromptId,d.code,d.message),this.transport.sendSessionUpdate(e.sessionId,{sessionUpdate:"stream_complete",stopReason:"error",error:d.message},s))}catch(e){r=e}try{this.cleanupPrompt(e)}catch(e){o=e}finally{e.writeGateToken=void 0,e.activeRunBinding=void 0,i&&this.sessionMutationCoordinator.release(i)}if(n){const e="complete"===d.kind?"cancelled"===d.stopReason?"cancelled":"completed":"interrupted"===d.kind&&"cancelled"===d.stopReason?"cancelled":"failed";this.activeRuns.markTerminal(n,e)}try{this.onRunEnded?.(e,d)}catch(e){a=e}if(r)throw r;if(o)throw o;if(a)throw a;return!0}schedulePromptTimeout(e){let t;if(this.state.clearPromptTimeout(e),e.cronJobId){if(!this.cronOverrides.isInitialized())return void this.cronReady.then(()=>{!e.cronJobId||void 0===e.pendingPromptId&&void 0===e.currentRunId||this.schedulePromptTimeout(e)}).catch(e=>this.logger.error?.(`[bridge-core] cron overrides readiness failed: ${e instanceof Error?e.message:e}`));const s=this.cronOverrides.get(e.cronJobId)??this.config.cronMaxDurationDefaultMs;t=Math.max(CRON_DURATION_FLOOR_MS,Math.min(this.config.cronMaxDurationCapMs,s))}else t=this.config.promptTimeoutMs??DEFAULT_PROMPT_TIMEOUT_MS;t<=0||(e.promptTimeoutTimer=setTimeout(()=>{if(e.mutationTerminalCommitting)return;if(void 0===e.pendingPromptId&&void 0===e.currentRunId)return;this.logger.warn?.(`[bridge-core] Prompt timed out sessionId=${e.sessionId} after ${t}ms`),e.mutationTerminalOverride="timeout";const s=this.getGateway(),n=e.writeGateToken;s?.isConnected&&n?this.observeLifecycle(this.sessionMutationCoordinator.requestCancel(n,s).then(t=>this.handleCancellationResult(e,t)),"prompt_timeout_cancel",e):s?.isConnected?this.observeLifecycle(s.chatAbort(e.gatewaySessionKey,e.currentRunId).then(()=>this.interruptPrompt(e,classifyAgentRuntimeFailure({raw:`Prompt timed out after ${t}ms`,source:"live_gateway",runtimeKind:"openclaw"}))),"prompt_timeout_unowned_cancel",e):n&&(this.sessionMutationCoordinator.requireRecovery(n,"gateway_disconnected","disconnect"),this.observeLifecycle(this.endRun(e,{kind:"interrupted",stopReason:"interrupted",message:SESSION_RECOVERY_REQUIRED_MESSAGE,errorCode:SESSION_RECOVERY_REQUIRED_CODE}),"prompt_timeout_disconnected",e))},t),"object"==typeof e.promptTimeoutTimer&&"unref"in e.promptTimeoutTimer&&e.promptTimeoutTimer.unref())}adoptBackgroundMutation(e,t){const s=this.sessionMutationCoordinator.attachOrStartExternal({sessionKey:e.gatewaySessionKey,label:"background_run"},"acp",t);return s.ok?("owner"===s.ownership&&(e.writeGateToken=s.token),s):(this.logger.error?.(`[bridge-core] background mutation rejected sessionKey=${e.gatewaySessionKey} runId=${t} reason=${s.reason}`),s)}cleanupPrompt(e){this.state.clearPromptTimeout(e),this.state.clearStreamIdleTimer(e);const t=e.currentRunId;if(t){this.state.promptsByRunId.delete(t),this.state.runIdToCronJobId.delete(t),this.state.markRunCompleted(t);const e=`tc_${t}_`;for(const[t,s]of this.state.toolCallIdMap)s.startsWith(e)&&this.state.toolCallIdMap.delete(t)}e.gatewayRequestId&&this.state.promptsByGatewayRequestId.delete(e.gatewayRequestId),e.pendingPromptId=void 0,e.promptDone=void 0,e.currentRunId=void 0,e.gatewayRequestId=void 0,e.assistantStreamStartedAt=void 0,e.gatewayMethod=void 0,e.mutationTerminalOverride=void 0,e.mutationTerminalCommitting=void 0,e.assistantMq?.close(),e.assistantMq=void 0,e.pendingUserTurnUpdates=void 0}async completePrompt(e,t){await this.endRun(e,{kind:"complete",stopReason:t})}async interruptPrompt(e,t){this.logRuntimeFailure(e,t),await this.endRun(e,{kind:"interrupted",stopReason:t.stopReason,message:t.publicMessage,errorCode:t.errorCode})}async failPrompt(e,t,s){await this.endRun(e,{kind:"error",code:t,message:s})}cleanupAllPendingPrompts(){for(const e of this.state.sessions.values())e.writeGateToken?(this.sessionMutationCoordinator.requireRecovery(e.writeGateToken,"gateway_disconnected","disconnect"),this.observeLifecycle(this.endRun(e,{kind:"interrupted",stopReason:"interrupted",message:SESSION_RECOVERY_REQUIRED_MESSAGE,errorCode:SESSION_RECOVERY_REQUIRED_CODE}),"gateway_disconnect_recovery",e)):this.observeLifecycle(this.interruptPrompt(e,classifyAgentRuntimeFailure({raw:Errors.GatewayDisconnected.message,source:"live_gateway",runtimeKind:"openclaw"})),"gateway_disconnect_interrupt",e)}handleGatewayResponse(e){const t=e.id;if(!t)return;const s=this.state.promptsByGatewayRequestId.get(t);if(!s||s.promptDone)return;if(!e.ok){const n=e.error,i=n?.message??"gateway returned error";this.logger.warn?.(`[bridge-core] handleGatewayResponse: gateway rejected prompt gatewayRequestId=${t} error=${i}`);const r=classifyAgentRuntimeFailure({raw:i,source:"live_gateway",runtimeKind:"openclaw"});return void("interrupted"===r.kind?this.observeLifecycle(this.interruptPrompt(s,r),"gateway_response_interrupt",s):(this.logRuntimeFailure(s,r),this.observeLifecycle(this.failPrompt(s,Errors.SendFailed.rpcCode,r.publicMessage),"gateway_response_failure",s)))}const n=e.payload,i=n?.runId;i?(s.currentRunId=i,this.sessionMutationCoordinator.bindRun(s.writeGateToken,i),this.observeActiveRun(s),s.assistantStreamStartedAt=Date.now(),this.state.promptsByRunId.set(i,s),this.logger.debug?.(`[bridge-core] handleGatewayResponse: bound runId=${i} gatewayRequestId=${t} sessionId=${s.sessionId}`)):this.logger.debug?.(`[bridge-core] handleGatewayResponse: no runId in payload gatewayRequestId=${t} sessionId=${s.sessionId}`)}maybeCompletePromptFromChatState(e,t){if(e.promptDone||"chat.send"!==e.gatewayMethod)return;const s="string"==typeof t.state?t.state.toLowerCase():void 0;if(s)if("final"===s){const s="string"==typeof t.stopReason?t.stopReason:void 0;this.observeLifecycle(this.completePrompt(e,s||"end_turn"),"chat_state_final",e)}else if("aborted"===s||"cancelled"===s||"cancel"===s)this.observeLifecycle(this.completePrompt(e,"cancelled"),"chat_state_cancelled",e);else if("error"===s){const s=("string"==typeof t.summary?t.summary:void 0)??("string"==typeof t.message?t.message:void 0)??"gateway chat error",n=classifyAgentRuntimeFailure({raw:s,source:"chat_state",runtimeKind:"openclaw"});"interrupted"===n.kind?this.observeLifecycle(this.interruptPrompt(e,n),"chat_state_interrupt",e):(this.logRuntimeFailure(e,n),this.observeLifecycle(this.failPrompt(e,Errors.AgentError.rpcCode,n.publicMessage),"chat_state_failure",e))}}async handleCronRequest(e){const t=this.getGateway();if(!t?.isConnected)return void this.transport.sendAcpError(e.id,Errors.GatewayNotConnected);const s=e.method.replace("/","."),n={...e.params??{}};["cron.add","cron.update","cron.remove","cron.run","cron.runs"].includes(s)&&(n.__beeosCronIngress="acp"),"cron.update"===s&&(n.patch=n.patch&&"object"==typeof n.patch?{...n.patch}:{});try{const t=await this.dispatchLegacyCronCapability(s,n),i=await this.mapCronResultForAcp(s,t);this.transport.sendResult(e.id,i??{}),"cron.add"!==s&&"cron.update"!==s&&"cron.remove"!==s||this.refreshCronIsolatedSessionKeys()}catch(t){const n=t instanceof Error?t.message:"cron request failed";this.logger.warn?.(`[bridge-core] ${s} failed: ${n}`),this.transport.sendAcpError(e.id,Errors.SendFailed,{message:n})}}async mapCronResultForAcp(e,t){if(!t||"object"!=typeof t)return t;const s=t;await this.cronOverrides.snapshot();const n="cron.list"===e||"cron.status"===e,i=e=>{if(!e||"object"!=typeof e)return e;const t=e,s=t.id;if("string"!=typeof s)return e;const i=this.cronOverrides.localId(s)??s,r=this.cronChannelCache.get(i),o=r?encodeBeeosChannelTag("string"==typeof t.description?t.description:void 0,r):t.description,a=this.cronOverrides.get(i);return{...t,id:i,...void 0===o?{}:{description:o},...n&&"number"==typeof a?{_beeos_meta:{maxDurationMs:a}}:{}}};if(Array.isArray(s.jobs))return{...s,jobs:s.jobs.map(i)};if(s.jobs&&"object"==typeof s.jobs){const e=s.jobs,t={};for(const[s,n]of Object.entries(e))t[s]=i(n);return{...s,jobs:t}}return Array.isArray(s.runs)?{...s,runs:s.runs.map(e=>{if(!e||"object"!=typeof e)return e;const t=e,s="string"==typeof t.jobId?this.cronOverrides.localId(t.jobId)??t.jobId:t.jobId;return{...t,jobId:s}})}:"cron.add"===e||"cron.update"===e?i(t):t}async allocateCronChannel(e){if(this.agentFetch&&this.agentGatewayUrl)try{const t=`${this.agentGatewayUrl}/api/v1/channels`,s=await this.agentFetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({participants:[this.instanceId,"service:cron"],metadata:{source:"cron",job_name:e}})});if(!s.ok)return void this.logger.warn?.(`[bridge-core] channel alloc failed: ${s.status}`);return(await s.json()).id}catch(e){return void this.logger.warn?.(`[bridge-core] channel alloc error: ${e instanceof Error?e.message:e}`)}else this.logger.debug?.("[bridge-core] agentFetch/agentGatewayUrl unavailable, skip channel alloc")}setCronChannelCache(e,t){if(this.cronChannelCache.size>=AcpGatewayBridgeCore.CRON_CHANNEL_CACHE_MAX){const e=this.cronChannelCache.keys().next().value;void 0!==e&&this.cronChannelCache.delete(e)}this.cronChannelCache.set(e,t)}async refreshCronIsolatedSessionKeys(){const e=this.getGateway();if(e?.isConnected)try{const t=await e.request("cron.list",{}),s=t?.jobs;if(!Array.isArray(s))return;const n=new Set,i=[];for(const e of s){if(!e||"object"!=typeof e)continue;const t=e;if("string"==typeof t.id&&i.push(t.id),"isolated"!==t.sessionTarget)continue;const s=t.sessionKey;"string"==typeof s&&s.length>0&&n.add(s)}this.cronIsolatedSessionKeys=n,await this.cronOverrides.reconcile(i)}catch(e){this.logger.debug?.(`[bridge-core] refreshCronIsolatedSessionKeys failed: ${e instanceof Error?e.message:e}`)}}}async function fetchWithRetry(e,t={}){const s=t.maxRetries??3;let n;for(let i=0;i<=s;i++){const r=await fetch(e,{signal:AbortSignal.timeout(12e4),redirect:"follow"});if(r.ok)return Buffer.from(await r.arrayBuffer());if(429===r.status||r.status>=500){const o=Number(r.headers.get("retry-after"))||0,a=1e3*Math.max(o,2**i);if(n=new Error(`HTTP ${r.status} from ${e}`),i<s){t.logger?.info?.(`[bridge-core] Download ${r.status}, retry ${i+1}/${s} in ${(a/1e3).toFixed(0)}s`),await new Promise(e=>setTimeout(e,a));continue}}throw new Error(`Failed to download zip: ${r.status}`)}throw n??new Error("Download failed after retries")}const SKILL_ROOT_MARKERS=["SKILL.md","skill.md","SKILL.MD"];function normalizeSkillArtifactPath(e){const t=e.replaceAll("\\","/").replace(/^\.\//,"");if(!t||t.startsWith("/")||t.split("/").includes(".."))throw new RuntimeCommandError("INVALID_PARAMS",`Invalid skill artifact path: ${e}`);return t}function parseSkillZip(e){if(e.byteLength>MAX_ZIP_BYTES)throw new RuntimeCommandError("INVALID_PARAMS","Skill archive too large");const t=new AdmZip(e).getEntries().filter(e=>!e.isDirectory).map(e=>({name:normalizeSkillArtifactPath(e.entryName),bytes:e.getData()})).filter(e=>!e.name.split("/").includes("__MACOSX"));if(0===t.length)throw new RuntimeCommandError("INVALID_PARAMS","Skill archive is empty");const s=1===new Set(t.map(e=>e.name.split("/")[0])).size&&t.every(e=>e.name.includes("/")),n=new Map;for(const e of t){const t=s?e.name.slice(e.name.indexOf("/")+1):e.name;if(!t||n.has(t))throw new RuntimeCommandError("INVALID_PARAMS",`Duplicate skill path: ${t}`);n.set(t,e.bytes)}return n}async function resolveSkillRoot(e){for(const t of SKILL_ROOT_MARKERS)try{return await stat(join(e,t)),e}catch{}const t=(await readdir(e,{withFileTypes:!0})).filter(e=>e.isDirectory()&&!e.name.startsWith(".")&&"__MACOSX"!==e.name);if(1===t.length){const s=join(e,t[0].name);for(const e of SKILL_ROOT_MARKERS)try{return await stat(join(s,e)),s}catch{}return s}return e}async function extractZipToDir(e,t){const s=new AdmZip(e),n=join(t,".beeos-extract-tmp-"+Date.now());try{await mkdir(n,{recursive:!0}),s.extractAllTo(n,!0);const e=await resolveSkillRoot(n),i=await readdir(e,{withFileTypes:!0});let r=0;for(const s of i){if(s.name.startsWith(".beeos-extract-tmp")||"__MACOSX"===s.name)continue;const n=join(e,s.name),i=join(t,s.name);await rename(n,i).catch(async()=>{await cp(n,i,{recursive:!0})}),r++}if(0===r)throw new Error("Zip archive contained no extractable files")}finally{await rm(n,{recursive:!0,force:!0}).catch(()=>{})}}const VALID_SLUG_PATTERN=/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i,NON_ASCII_PATTERN=/[^\x00-\x7F]/;function validateSkillSlug(e){const t=e.trim();if(!t||t.includes("/")||t.includes("\\")||t.includes(".."))throw new Error(`Invalid skill slug: ${e}`);if(NON_ASCII_PATTERN.test(t)||!VALID_SLUG_PATTERN.test(t))throw new Error(`Invalid skill slug: ${e}`);return t}function safeDirName(e){const t=e.trim();return t?t.replaceAll("/","__").replaceAll("\\","__"):t}function extractMcpCredentialMaterial(e){const t=mcpStringMap(e.headers,"MCP headers"),s=mcpStringMap(e.env,"MCP environment");if(!(t&&0!==Object.keys(t).length||s&&0!==Object.keys(s).length))return;const n=e.transport??("string"==typeof e.command?"stdio":"string"==typeof e.url?"streamable-http":void 0);if("stdio"===n&&t&&Object.keys(t).length)throw new Error("MCP stdio config cannot include headers");if("stdio"!==n&&s&&Object.keys(s).length)throw new Error("MCP HTTP config cannot include env");return{...t&&Object.keys(t).length?{headers:t}:{},...s&&Object.keys(s).length?{env:s}:{}}}function mcpStringMap(e,t){if(void 0!==e){if(!e||"object"!=typeof e||Array.isArray(e)||Object.entries(e).some(([e,t])=>!e||/[\u0000-\u001f\u007f]/.test(e)||"string"!=typeof t))throw new Error(`${t} invalid`);return{...e}}}function requireCredentialReferencePort(e){if(!e)throw new Error("MCP credential reference vault is unavailable");return e}function resolveSkillInstallDir(e,t){const s=safeDirName(t),n=join(e,s),i=resolve(e),r=resolve(n),o=relative(i,r);if(!o||".."===o||o.startsWith(`..${sep}`)||path.isAbsolute(o))throw new Error("Invalid skill target path");return n}
@@ -0,0 +1 @@
1
+ import{createCipheriv,createDecipheriv,createHash,randomBytes,randomUUID}from"node:crypto";import{chmod,mkdir,open,readFile,readdir,rename,unlink}from"node:fs/promises";import{dirname,join}from"node:path";import{requireSecretFreeMcpServerName}from"./mcp-public-config-security.js";import{withOwnedDirectoryLock}from"../runtime/owned-directory-lock.js";import{MonotonicJournalSegments}from"../runtime/monotonic-journal-segments.js";const LOCAL_PREFIX="local-mcp:v1:";export class LocalEncryptedCredentialReferenceVault{path;instanceId;options;key;index;directory;indexPath;lockPath;constructor(e,t,r,i={}){if(this.path=e,this.instanceId=t,this.options=i,!t)throw new Error("local MCP credential vault requires an instance id");if(r.byteLength<32)throw new Error("local MCP credential vault requires an instance key");this.key=createHash("sha256").update("beeos-claw/mcp-credential-vault/v1\0").update(t).update("\0").update(r).digest(),this.directory=`${e}.d`,this.indexPath=`${e}.index`,this.lockPath=`${this.indexPath}.lock`,this.index=new MonotonicJournalSegments(this.indexPath,{beforeWatermark:i.beforeIndexWatermark})}owns(e){return e.startsWith(LOCAL_PREFIX)}async put(e){requireServerName(e.serverName);const t=normalizeMaterial(e.material);if(!t.headers&&!t.env)throw new Error("MCP credential material is empty");const r=`${LOCAL_PREFIX}${randomUUID()}`;return await this.updateIndex(e=>[...e.entries,{credentialRef:r,status:"pending"}]),await this.publish({schemaVersion:1,instanceId:this.instanceId,credentialRef:r,serverName:e.serverName,material:t}),r}async resolve(e){if(!this.owns(e.credentialRef))throw new Error("MCP credential reference is not local");requireServerName(e.serverName);const t=(await this.loadIndex()).entries.find(t=>t.credentialRef===e.credentialRef);if(!t||"deleted"===t.status)throw new Error("MCP credential reference was not found");const r=await this.load(e.credentialRef);if(r.serverName!==e.serverName)throw new Error("MCP credential reference target mismatch");return normalizeMaterial(r.material)}async commit(e){this.owns(e)&&await this.updateIndex(t=>{const r=t.entries.find(t=>t.credentialRef===e);if(!r||"deleted"===r.status)throw new Error("MCP credential reference was not found");return"committed"===r.status?t.entries:t.entries.map(t=>t.credentialRef===e?{...t,status:"committed"}:t)})}async remove(e){if(this.owns(e)){await this.updateIndex(t=>{const r=t.entries.find(t=>t.credentialRef===e);return r&&"deleted"!==r.status?t.entries.map(t=>t.credentialRef===e?{...t,status:"deleted"}:t):t.entries});for(const t of[!1,!0])await unlink(this.entryPath(e,t)).catch(e=>{if("ENOENT"!==e.code)throw e});await syncDirectory(this.directory).catch(()=>{})}}async listOwned(){return(await this.loadIndex()).entries.filter(e=>"committed"===e.status).map(e=>e.credentialRef)}async listPendingOwned(){return(await this.loadIndex()).entries.filter(e=>"pending"===e.status).map(e=>e.credentialRef)}async loadIndex(){const e=await this.legacyIndex(),t=new Map([["snapshot",{value:e,logicalRevision:e.revision,publisherEpoch:"0"}]]),r=(await this.index.reduce(t)).get("snapshot")?.value??e;if(0n===await this.index.authorityFloor())return r;const i=new Set(r.entries.map(e=>e.credentialRef)),n=e.entries.filter(e=>!i.has(e.credentialRef)).map(e=>({...e,status:"pending"}));return 0===n.length?r:{...r,entries:normalizeIndexEntries([...r.entries,...n])}}async updateIndex(e){await withOwnedDirectoryLock(this.lockPath,async t=>{const r=await this.index.authorityFloor(),i=await this.loadIndex(),n=normalizeIndexEntries(e(i));if(sameIndexEntries(n,i.entries))return;const a=Object.freeze({schemaVersion:1,revision:`${BigInt(i.revision)+1n}`,entries:n});await t.assertOwned(),await this.index.publish(t.epoch,[{key:"snapshot",logicalRevision:a.revision,value:a}],r),await this.index.prune()},{...this.options.lockOptions,epochFloor:()=>this.index.authorityFloor()})}async legacyIndex(){let e;try{e=await readdir(this.directory)}catch(e){if("ENOENT"===e.code)return{schemaVersion:1,revision:"0",entries:[]};throw e}const t=new Map;for(const r of e.sort()){const e=/^([0-9a-f-]{36})(\.pending)?\.json$/.exec(r);if(!e)continue;const i=`${LOCAL_PREFIX}${e[1]}`,n=e[2]?"pending":"committed";t.get(i)&&"committed"!==n||t.set(i,{credentialRef:i,status:n})}return{schemaVersion:1,revision:"0",entries:normalizeIndexEntries([...t.values()])}}async load(e){let t,r;try{t=await readFile(this.entryPath(e),"utf8")}catch(r){if("ENOENT"!==r.code)throw r;try{t=await readFile(this.entryPath(e,!0),"utf8")}catch{throw new Error("MCP credential reference was not found")}}try{r=JSON.parse(t)}catch{throw new Error("local MCP credential vault is corrupt")}if(1!==r.schemaVersion||"aes-256-gcm"!==r.algorithm||"string"!=typeof r.iv||"string"!=typeof r.ciphertext||"string"!=typeof r.tag)throw new Error("local MCP credential vault is corrupt");try{const t=createDecipheriv("aes-256-gcm",this.key,Buffer.from(r.iv,"base64"));t.setAAD(this.aad(e)),t.setAuthTag(Buffer.from(r.tag,"base64"));const i=Buffer.concat([t.update(Buffer.from(r.ciphertext,"base64")),t.final()]),n=JSON.parse(i.toString("utf8"));if(1!==n.schemaVersion||n.instanceId!==this.instanceId||n.credentialRef!==e||"string"!=typeof n.serverName)throw new Error("invalid plaintext");return requireServerName(n.serverName),{...n,material:normalizeMaterial(n.material)}}catch{throw new Error("local MCP credential vault authentication failed")}}async publish(e){await mkdir(this.directory,{recursive:!0,mode:448});const t=randomBytes(12),r=createCipheriv("aes-256-gcm",this.key,t);r.setAAD(this.aad(e.credentialRef));const i=Buffer.concat([r.update(JSON.stringify(e),"utf8"),r.final()]),n={schemaVersion:1,algorithm:"aes-256-gcm",iv:t.toString("base64"),ciphertext:i.toString("base64"),tag:r.getAuthTag().toString("base64")},a=this.entryPath(e.credentialRef,!0),s=join(this.directory,`.${process.pid}.${randomUUID()}.tmp`),o=await open(s,"wx",384);try{await o.writeFile(`${JSON.stringify(n)}\n`,"utf8"),await o.sync()}finally{await o.close()}try{await chmod(s,384),await rename(s,a),await chmod(a,384),await(this.options.afterCiphertextRename?.()),await syncDirectory(this.directory),await syncDirectory(dirname(this.directory))}catch(e){throw await unlink(s).catch(()=>{}),e}}aad(e){return Buffer.from(`beeos-claw/mcp-credential-vault/v1\0${this.instanceId}\0${e}`)}entryPath(e,t=!1){if(!this.owns(e)||!/^[0-9a-f-]{36}$/.test(e.slice(13)))throw new Error("MCP credential reference is invalid");return join(this.directory,`${e.slice(13)}${t?".pending":""}.json`)}}export class CredentialReferenceRuntimeResolver{local;constructor(e){this.local=e}currentAuthority(){return{runtimeEpoch:"0"}}async resolve(e){if(!this.local.owns(e.credentialRef))throw new Error("MCP credential reference is not local to ACP");if(e.signal?.aborted)throw e.signal.reason;const t=this.currentAuthority();if(!t)throw new Error("runtime-scoped MCP credential authority is pending");const r=await this.local.resolve({credentialRef:e.credentialRef,serverName:e.serverName});return{runtimeEpoch:t.runtimeEpoch,credentialRevision:"1",credential:normalizeMaterial(r)}}subscribe(e){return()=>{}}}function normalizeMaterial(e){if(!record(e)||Object.keys(e).some(e=>"headers"!==e&&"env"!==e))throw new Error("Invalid MCP credential material");const t=normalizeStringMap(e.headers,"MCP headers"),r=normalizeStringMap(e.env,"MCP environment");if(Buffer.byteLength(JSON.stringify({headers:t,env:r}),"utf8")>65536)throw new Error("MCP credential material is too large");return Object.freeze({...t&&Object.keys(t).length?{headers:Object.freeze(t)}:{},...r&&Object.keys(r).length?{env:Object.freeze(r)}:{}})}function normalizeStringMap(e,t){if(void 0!==e){if(!record(e)||Object.entries(e).some(([e,t])=>!e||e.length>256||/[\u0000-\u001f\u007f]/.test(e)||"string"!=typeof t||t.length>16384))throw new Error(`${t} invalid`);return{...e}}}function requireServerName(e){requireSecretFreeMcpServerName(e)}function record(e){return null!==e&&"object"==typeof e&&!Array.isArray(e)}function normalizeIndexEntries(e){const t=new Map;for(const r of e){if(!(r&&r.credentialRef.startsWith(LOCAL_PREFIX)&&/^[0-9a-f-]{36}$/.test(r.credentialRef.slice(13))&&["pending","committed","deleted"].includes(r.status)))throw new Error("local MCP credential vault index is corrupt");t.set(r.credentialRef,Object.freeze({...r}))}return Object.freeze([...t.values()].sort((e,t)=>e.credentialRef.localeCompare(t.credentialRef)))}function sameIndexEntries(e,t){return e.length===t.length&&e.every((e,r)=>e.credentialRef===t[r]?.credentialRef&&e.status===t[r]?.status)}async function syncDirectory(e){const t=await open(e,"r");try{await t.sync()}finally{await t.close()}}
@@ -0,0 +1 @@
1
+ import{RUNTIME_MESSAGE_TYPES}from"../runtime/runtime-message-channel-contract.js";export class McpCredentialRevokedError extends Error{credentialRef;constructor(e){super("MCP credential is revoked"),this.credentialRef=e}}export class McpDesiredStateController{store;bridge;credentials;references;releases;receiptHistory;tail=Promise.resolve();unsubscribe;started=!1;revoked=new Map;activeCredentialRevisions=new Map;projections=new Map;credentialServers=new Map;liveReleaseKeys=new Map;desiredRevision="0";authorityEpoch;authorityGeneration=0;authorityCloseBlocked=!1;constructor(e,t,i,r,s,n){this.store=e,this.bridge=t,this.credentials=i,this.references=r,this.releases=s,this.receiptHistory=n,this.authorityEpoch=i?.currentAuthority()?.runtimeEpoch??null}setRuntimeReceiptHistoryPort(e){this.receiptHistory=e}async start(e){await this.exclusive(async()=>{this.started||(this.started=!0,this.authorityEpoch=this.credentials?.currentAuthority()?.runtimeEpoch??null,this.unsubscribe=this.credentials?.subscribe(e=>this.acceptCredentialEvent(e)));const t=await this.store.load();await this.reconcileAll(e,t),await this.collectLocalGarbage(void 0,!0)})}async reconnect(e){await this.exclusive(()=>this.reconcileAll(e))}async list(){return await this.exclusive(async()=>{const e=await this.store.load();this.rememberDesired(e);const t=new Map(this.bridge.list().map(e=>[e.name,e]));return{revision:e.revision,servers:e.servers.map(i=>{const r=t.get(i.name);return Object.freeze({...i,connected:Boolean(r),tools:r?.tools??Object.freeze([]),projection:this.projections.get(i.name)??pending(e.revision)})})}})}async set(e,t){return await this.exclusive(async()=>await this.store.transaction(async i=>{const r="service"===t?.source?withoutConfig(e):e,s=await i.set(r);return e.credentialRef&&this.references?.owns(e.credentialRef)&&await this.references.commit(e.credentialRef).catch(()=>{}),this.rememberDesired(s),this.activeCredentialRevisions.delete(e.name),this.liveReleaseKeys.delete(e.name),await this.bridge.unset(e.name),this.projections.set(e.name,pending(s.revision)),await this.recordDesiredStateCommit(s,e.name,t),await this.collectLocalGarbage(i),s}))}async unset(e,t){return await this.exclusive(async()=>await this.store.transaction(async i=>{const r=await i.unset(e);return this.rememberDesired(r),this.activeCredentialRevisions.delete(e),this.liveReleaseKeys.delete(e),await this.bridge.unset(e),this.projections.set(e,pending(r.revision)),await this.recordDesiredStateCommit(r,e,t),await this.collectLocalGarbage(i),r}))}async isCredentialReferenced(e){return await this.exclusive(async()=>(await this.store.load()).servers.some(t=>t.credentialRef===e))}async removeLocalCredentialIfUnreferenced(e){await this.exclusive(async()=>{this.references?.owns(e)&&await this.store.transaction(async t=>{(await t.load()).servers.some(t=>t.credentialRef===e)||(await t.assertOwned(),await this.references.remove(e))})})}async project(e,t,i,r){return await this.exclusive(async()=>{const s=await this.store.load();this.rememberDesired(s);const n=s.servers.find(t=>t.name===e);if(t?.aborted)return pending(s.revision);if(i&&!matchesProjectionAttempt(e,n,i))return blocked(s.revision);const a=i?await this.findAttemptActive(s,n,i):void 0;if((i||this.releases)&&!a){this.activeCredentialRevisions.delete(e),this.liveReleaseKeys.delete(e),await this.bridge.unset(e);const t=pending(s.revision);return this.projections.set(e,t),t}if(i&&!r&&n?.credentialRef&&this.credentials?.currentAuthority()?.runtimeEpoch!==i.runtimeEpoch)return this.projections.set(e,blocked(s.revision)),blocked(s.revision);if(!n){this.activeCredentialRevisions.delete(e),await this.bridge.unset(e);const t=synced(s.revision);return this.projections.set(e,t),withOperationReceipt(t,s,void 0,i,a)}const o=a?releaseKey(a):void 0;if(o&&this.liveReleaseKeys.get(e)===o&&this.bridge.list().some(t=>t.name===e)){const t=synced(s.revision);return this.projections.set(e,t),withOperationReceipt(t,s,n,i,a)}const c=await this.reconcileServer(n,s,t,a,i,r);return"synced"===c.state&&o&&this.liveReleaseKeys.set(e,o),withOperationReceipt(c,s,n,i,a)})}async stop(){await this.exclusive(async()=>{this.started=!1,this.unsubscribe?.(),this.unsubscribe=void 0,this.activeCredentialRevisions.clear(),this.revoked.clear(),this.projections.clear(),this.credentialServers.clear(),this.liveReleaseKeys.clear(),this.authorityEpoch=null,await this.bridge.close()})}acceptCredentialEvent(e){if("authority"===e.type){this.authorityEpoch=null;const t=++this.authorityGeneration;for(const e of this.credentialServers.keys())this.activeCredentialRevisions.delete(e),this.liveReleaseKeys.delete(e),this.projections.set(e,pending(this.desiredRevision));const i=this.bridge.detachMany(this.credentialServers.keys()).then(()=>({ok:!0}),e=>({ok:!1,error:e}));return void this.exclusive(()=>this.onAuthorityEvent(e.runtimeEpoch,t,i))}this.exclusive(()=>this.onCredentialEvent(e))}async onAuthorityEvent(e,t,i){const r=await i;if(!r.ok)throw this.authorityCloseBlocked=!0,r.error;const s=await this.store.load();if(this.rememberDesired(s),t!==this.authorityGeneration||null===e||this.authorityCloseBlocked)return;const n=this.credentials?.currentAuthority();n&&n.runtimeEpoch===e&&(this.authorityEpoch=e,await this.reconcileAll(void 0,s))}async onCredentialEvent(e){const t=BigInt(e.credentialRevision),i=this.revoked.get(e.credentialRef)??-1n,r=await this.store.load();this.rememberDesired(r);const s=r.servers.filter(t=>t.credentialRef===e.credentialRef);if("revoked"!==e.type){if(!(t<=i)){this.revoked.delete(e.credentialRef);for(const e of s){if(t>(this.activeCredentialRevisions.get(e.name)??-1n)){if(this.activeCredentialRevisions.delete(e.name),this.liveReleaseKeys.delete(e.name),await this.bridge.unset(e.name),!this.releases){await this.reconcileServer(e,r);continue}this.projections.set(e.name,blocked(r.revision))}}}}else{if(t<i)return;this.revoked.set(e.credentialRef,t);for(const e of s)this.activeCredentialRevisions.delete(e.name),this.liveReleaseKeys.delete(e.name),this.projections.set(e.name,blocked(r.revision)),await this.bridge.unset(e.name)}}async reconcileAll(e,t){const i=t??await this.store.load();if(this.rememberDesired(i),!this.releases){const t=new Set(i.servers.map(e=>e.name));for(const e of this.bridge.list())t.has(e.name)||await this.bridge.unset(e.name);for(const t of i.servers)await this.reconcileServer(t,i,e);return}const r=await this.releases.listMcpActiveDesiredStates(),s=await this.releases.listMcpLiveReleases(),n=new Set(i.servers.map(e=>e.name));for(const e of this.bridge.list())n.has(e.name)||await this.bridge.unset(e.name);for(const t of i.servers){let n=r.find(e=>releaseMatchesDesired(e,i,t));if(!n){const e=s.find(e=>releaseMatchesDesired(e,i,t)),r=void 0===e?"pending":await this.recoverPendingServiceRelease(e,i,t);if("string"==typeof r){this.activeCredentialRevisions.delete(t.name),this.liveReleaseKeys.delete(t.name),await this.bridge.unset(t.name),this.projections.set(t.name,"pending"===r?pending(i.revision):blocked(i.revision));continue}n=r}if(t.credentialRef&&this.credentials?.currentAuthority()?.runtimeEpoch!==n.runtimeEpoch){this.activeCredentialRevisions.delete(t.name),this.liveReleaseKeys.delete(t.name),await this.bridge.unset(t.name),this.projections.set(t.name,blocked(i.revision));continue}const a=releaseKey(n);if(this.liveReleaseKeys.get(t.name)===a&&this.bridge.list().some(e=>e.name===t.name)){this.projections.set(t.name,synced(i.revision));continue}"synced"===(await this.reconcileServer(t,i,e,n)).state&&this.liveReleaseKeys.set(t.name,releaseKey(n))}}async recoverPendingServiceRelease(e,t,i){if("service"!==e.source)return"blocked";if(!this.releases||!releaseMatchesDesired(e,t,i))return"blocked";let r;try{r=await this.releases.get(e.operationId)}catch{return"pending"}if(!r||!releaseMatchesJournalRecord(e,r))return"blocked";const s=await this.readReceiptHistory(e.operationId);if("unavailable"===s.status)return"pending";if("not_found"===s.status||"expired"===s.status)return"blocked";if(!serviceHistoryMatchesRelease(e,r,s.snapshot))return"blocked";try{const r=(await this.releases.promoteMcpDesiredStateActive({operationId:e.operationId,source:"service",effectRecordHash:e.effectRecordHash,terminalMessageId:s.snapshot.final.message_id})).mcpActiveDesiredState;return r&&releaseMatchesDesired(r,t,i)?r:"blocked"}catch{return"pending"}}async readReceiptHistory(e){if(!this.receiptHistory)return{status:"unavailable"};let t=await this.receiptHistory.readOperationHistory({operationId:e});return"unavailable"!==t.status||(await Promise.resolve(),t=await this.receiptHistory.readOperationHistory({operationId:e})),t}async reconcileServer(e,t,i,r,s,n){try{e.credentialRef&&this.bridge.list().some(t=>t.name===e.name)&&await this.bridge.detach(e.name),await this.connectServer(e,t,i,r,s,n);const a=synced(t.revision);return this.projections.set(e.name,a),a}catch(i){if(i instanceof McpCredentialRevokedError){this.revoked.set(e.credentialRef,this.revoked.get(e.credentialRef)??0n),this.activeCredentialRevisions.delete(e.name),await this.bridge.unset(e.name);const i=blocked(t.revision);return this.projections.set(e.name,i),i}const r=pending(t.revision);return this.projections.set(e.name,r),r}}async connectServer(e,t,i,r,s,n){if(i?.aborted)throw new Error("MCP projection attempt is no longer authoritative");if(n){if(!s||n.runtimeEpoch!==s.runtimeEpoch||r&&!releaseAuthorizesServer(r,e,t,n.runtimeEpoch,s))throw new Error("MCP operation-scoped activation is no longer authoritative");const a={generation:this.authorityGeneration,isCurrent:()=>!i?.aborted&&n.runtimeEpoch===s.runtimeEpoch},o=await this.bridge.set(e.name,n.config,n.credential,a);if(!a.isCurrent())throw await this.bridge.detach(e.name),new Error("MCP operation-scoped activation lost authority during bridge connection");return o}if(r&&!releaseAuthorizesServer(r,e,t,this.credentials?.currentAuthority()?.runtimeEpoch,s))throw new Error("MCP live release proof no longer authorizes this server");if(!e.credentialRef){if(this.activeCredentialRevisions.delete(e.name),!e.config)throw new Error("MCP public config is unavailable outside its Service attempt");const t=await this.bridge.set(e.name,e.config);if(i?.aborted)throw await this.bridge.detach(e.name),new Error("MCP projection attempt lost authority during bridge connection");return t}if(!this.credentials)throw new Error("runtime-scoped MCP credential resolver is unavailable");if(this.revoked.has(e.credentialRef))throw new McpCredentialRevokedError(e.credentialRef);const a=this.credentials.currentAuthority();if(!a||null===this.authorityEpoch||a.runtimeEpoch!==this.authorityEpoch)throw new Error("runtime-scoped MCP credential authority is pending");const o=this.authorityGeneration,c=this.authorityEpoch,d={generation:o,isCurrent:()=>o===this.authorityGeneration&&this.authorityEpoch===c&&this.credentials?.currentAuthority()?.runtimeEpoch===c};if(!d.isCurrent())throw new Error("runtime-scoped MCP credential authority changed");const h=await this.credentials.resolve({credentialRef:e.credentialRef,serverName:e.name,...i?{signal:i}:{}});if(i?.aborted||h.runtimeEpoch!==c||!d.isCurrent()||r&&!releaseAuthorizesServer(r,e,t,this.credentials?.currentAuthority()?.runtimeEpoch,s))throw new Error("runtime-scoped MCP credential authority changed during resolution");const l=BigInt(h.credentialRevision),u=this.revoked.get(e.credentialRef);if(void 0!==u&&l<=u)throw new McpCredentialRevokedError(e.credentialRef);if(!e.config)throw new Error("MCP public config is unavailable outside its Service attempt");const m=await this.bridge.set(e.name,e.config,h.credential,d);if(i?.aborted||!d.isCurrent()||r&&!releaseAuthorizesServer(r,e,t,this.credentials?.currentAuthority()?.runtimeEpoch,s))throw await this.bridge.detach(e.name),new Error("runtime-scoped MCP credential authority changed during bridge connection");return this.activeCredentialRevisions.set(e.name,l),m}rememberDesired(e){this.desiredRevision=e.revision,this.credentialServers.clear();for(const t of e.servers)t.credentialRef&&this.credentialServers.set(t.name,t.credentialRef)}async recordDesiredStateCommit(e,t,i){if(!i)return;if(!this.releases)throw new Error("MCP desired state requires the shared command journal release authority");const r=e.servers.find(e=>e.name===t);await this.releases.recordMcpDesiredStateCommit({schemaVersion:1,...i,desiredStateRevision:e.revision,desiredStateHash:e.sha256,server:r?{name:r.name,definitionRef:r.definitionRef,version:r.version,definitionSha256:r.sha256,...r.credentialRef?{credentialRef:r.credentialRef}:{}}:{name:t,removed:!0}})}async findAttemptActive(e,t,i){const r=(await(this.releases?.listMcpActiveDesiredStates())??[]).find(e=>e.operationId===i.operationId&&e.method===i.method&&e.source===i.source);if(r&&releaseMatchesAttempt(r,e,t,i))return r}async collectLocalGarbage(e,t=!1){if(!this.references)return;if(!e)return void await this.store.transaction(async e=>this.collectLocalGarbage(e,t));const i=await this.references.listOwned(),r=t?await(this.references.listPendingOwned?.())??[]:[];for(const t of[...new Set([...i,...r])]){const i=(await e.load()).servers.some(e=>e.credentialRef===t);await e.assertOwned(),i?r.includes(t)&&await this.references.commit(t).catch(()=>{}):await this.references.remove(t).catch(()=>{})}}async exclusive(e){const t=this.tail.catch(()=>{}).then(e);return this.tail=t.catch(()=>{}),await t}}function pending(e){return Object.freeze({revision:e,state:"pending"})}function withoutConfig(e){const{config:t,...i}=e;return i}function synced(e){return Object.freeze({revision:e,state:"synced"})}function blocked(e){return Object.freeze({revision:e,state:"blocked"})}function matchesProjectionAttempt(e,t,i){return i.expected.name===e&&(i.expected.absent?void 0===t:void 0!==t&&t.name===i.expected.name&&t.definitionRef===i.expected.definitionRef&&t.version===i.expected.version&&t.sha256===i.expected.sha256&&t.credentialRef===i.expected.credentialRef)}function withOperationReceipt(e,t,i,r,s){if(!r||!s||"synced"!==e.state)return e;const n=Object.freeze({schemaVersion:1,operationId:r.operationId,method:r.method,runtimeEpoch:r.runtimeEpoch,desiredStateRevision:t.revision,desiredStateHash:t.sha256,server:Object.freeze(i?{name:i.name,definitionRef:i.definitionRef,definitionSha256:i.sha256}:{name:r.expected.name,removed:!0})});return Object.freeze({...e,mcpReceipt:n})}function releaseMatchesDesired(e,t,i){return"mcp.set"===e.method&&e.desiredStateRevision===t.revision&&e.desiredStateHash===t.sha256&&e.server.name===i.name&&e.server.definitionRef===i.definitionRef&&e.server.version===i.version&&e.server.definitionSha256===i.sha256&&e.server.credentialRef===i.credentialRef}function releaseMatchesJournalRecord(e,t){const i=t.mcpDesiredStateCommit,r=t.mcpLiveRelease;return t.operationId===e.operationId&&t.command===e.method&&t.paramsHash===e.paramsHash&&t.runtimeEpoch===e.runtimeEpoch&&("effect_committed"===t.phase||"projection_committed"===t.phase)&&void 0!==t.messageBinding&&void 0!==i&&void 0!==r&&"service"===i.source&&sameMcpRelease(r,e)&&r.effectRecordHash===e.effectRecordHash&&sameMcpRelease(i,e)}function serviceHistoryMatchesRelease(e,t,i){const r=t.messageBinding;if(!r||!e.effectRecordHash)return!1;const s=i.request,n=s.payload,a=i.effectCommitted,o=a?.payload;if(i.operationId!==e.operationId||i.requestMessageId!==r.requestMessageId||i.requestHash!==r.requestHash||s.type!==RUNTIME_MESSAGE_TYPES.request||s.channel_id!==e.operationId||s.message_id!==r.requestMessageId||n.operationId!==e.operationId||n.requestMessageId!==r.requestMessageId||n.requestHash!==r.requestHash||n.paramsHash!==e.paramsHash||("mcp.set"===e.method?"mcp/set"!==n.method:"mcp/unset"!==n.method)||n.command.name!==e.method||n.dispatchEpoch!==e.runtimeEpoch||!a||!o||a.type!==RUNTIME_MESSAGE_TYPES.effectCommitted||a.channel_id!==e.operationId||a.in_reply_to!==r.requestMessageId||a.publisher_id!==n.instanceId||o.operationId!==e.operationId||o.requestMessageId!==r.requestMessageId||o.requestHash!==r.requestHash||o.dispatchEpoch!==e.runtimeEpoch||o.method!==n.method||o.effectRecordHash!==e.effectRecordHash||o.effectRecord.operationId!==e.operationId||o.effectRecord.method!==n.method)return!1;if(i.cancelRequested)return!1;if(!i.terminal)return!1;const c=i.final;return"final_message"===i.terminalSource&&"succeeded"===i.status&&void 0!==c&&c.type===RUNTIME_MESSAGE_TYPES.final&&c.channel_id===e.operationId&&c.in_reply_to===r.requestMessageId&&c.publisher_id===n.instanceId&&c.payload.operationId===e.operationId&&c.payload.requestMessageId===r.requestMessageId&&c.payload.requestHash===r.requestHash&&c.payload.dispatchEpoch===e.runtimeEpoch&&c.payload.method===n.method&&"succeeded"===c.payload.outcome&&c.payload.effectRecordHash===e.effectRecordHash}function sameMcpRelease(e,t){return e.source===t.source&&e.operationId===t.operationId&&e.method===t.method&&e.paramsHash===t.paramsHash&&e.runtimeEpoch===t.runtimeEpoch&&e.desiredStateRevision===t.desiredStateRevision&&e.desiredStateHash===t.desiredStateHash&&e.server.name===t.server.name&&e.server.definitionRef===t.server.definitionRef&&e.server.version===t.server.version&&e.server.definitionSha256===t.server.definitionSha256&&e.server.credentialRef===t.server.credentialRef&&e.server.removed===t.server.removed}function releaseMatchesAttempt(e,t,i,r){return!(e.operationId!==r.operationId||e.method!==r.method||e.source!==r.source||e.paramsHash!==r.paramsHash||e.runtimeEpoch!==r.runtimeEpoch||e.desiredStateRevision!==t.revision||e.desiredStateHash!==t.sha256||"service"===r.source&&!e.effectRecordHash||"acp_local"===r.source&&void 0!==e.effectRecordHash)&&(r.expected.absent?void 0===i&&e.server.name===r.expected.name&&!0===e.server.removed:void 0!==i&&releaseMatchesDesired(e,t,i)&&i.definitionRef===r.expected.definitionRef&&i.version===r.expected.version&&i.sha256===r.expected.sha256&&i.credentialRef===r.expected.credentialRef)}function releaseAuthorizesServer(e,t,i,r,s){return"mcp.set"===e.method&&(!t.credentialRef||e.runtimeEpoch===r)&&releaseMatchesDesired(e,i,t)&&(!s||releaseMatchesAttempt(e,i,t,s))}function releaseKey(e){return[e.source,e.operationId,e.paramsHash,e.runtimeEpoch,e.desiredStateRevision,e.desiredStateHash,e.effectRecordHash??"local"].join("\0")}
@@ -0,0 +1 @@
1
+ import{createHash,randomUUID}from"node:crypto";import{chmod,mkdir,open,readFile,rename,unlink}from"node:fs/promises";import{dirname}from"node:path";import{assertPublicMcpValues,requireSecretFreeMcpServerName}from"./mcp-public-config-security.js";import{withOwnedDirectoryLock}from"../runtime/owned-directory-lock.js";import{MonotonicJournalSegments}from"../runtime/monotonic-journal-segments.js";const EMPTY_PAYLOAD={schemaVersion:1,revision:"0",servers:[]};export class McpDesiredStateStore{path;options;walPath;lockPath;tail=Promise.resolve();segments;constructor(e,t={}){this.path=e,this.options=t,this.walPath=`${e}.wal`,this.lockPath=`${e}.lock`,this.segments=new MonotonicJournalSegments(e,{beforeWatermark:t.beforeAuthorityWatermark})}async load(){return await this.transaction(e=>e.load())}async set(e){return await this.transaction(t=>t.set(e))}async unset(e){return await this.transaction(t=>t.unset(e))}async transaction(e){return await this.exclusive(()=>withOwnedDirectoryLock(this.lockPath,async t=>{const i=await this.segments.authorityFloor();let r=await this.loadAndRecover();const n=async()=>(await t.assertOwned(),r=await this.loadAndRecover(),r),a={load:n,set:async e=>{const a=normalizeServer(e);r=await n();const s=new Map(r.servers.map(e=>[e.name,e]));return s.set(a.name,a),r=await this.commit(r,[...s.values()].sort((e,t)=>e.name.localeCompare(t.name)),t,i),r},unset:async e=>(requireName(e),r=await n(),r.servers.some(t=>t.name===e)?(r=await this.commit(r,r.servers.filter(t=>t.name!==e),t,i),r):r),assertOwned:()=>t.assertOwned()};return await e(a)},{...this.options.lockOptions,epochFloor:()=>this.segments.authorityFloor()}))}async loadAndRecover(){await mkdir(dirname(this.path),{recursive:!0,mode:448});const[e,t]=await Promise.all([readState(this.path),readState(this.walPath)]);if("corrupt"===t.kind)throw new Error("MCP desired-state WAL is corrupt");if("corrupt"===e.kind&&"valid"!==t.kind)throw new Error("MCP desired state is corrupt");let i;if("valid"===t.kind)if("valid"===e.kind&&BigInt(e.state.revision)>BigInt(t.state.revision))await unlinkIfPresent(this.walPath),await syncDirectory(dirname(this.path)),i=e.state;else{if("valid"===e.kind&&BigInt(t.state.revision)>BigInt(e.state.revision)+1n)throw new Error("MCP desired-state WAL revision is discontinuous");"valid"===e.kind&&e.state.sha256===t.state.sha256||(await atomicWrite(this.path,encode(t.state),this.temp("recover")),await syncDirectory(dirname(this.path))),await unlinkIfPresent(this.walPath),await syncDirectory(dirname(this.path)),i=t.state}else i="valid"===e.kind?e.state:state(EMPTY_PAYLOAD);const r=new Map([["snapshot",{value:i,logicalRevision:i.revision,publisherEpoch:"0"}]]);return(await this.segments.reduce(r)).get("snapshot")?.value??i}async commit(e,t,i,r){const n=state({schemaVersion:1,revision:`${BigInt(e.revision)+1n}`,servers:t}),a=encode(n);return await i.assertOwned(),await this.segments.publish(i.epoch,[{key:"snapshot",logicalRevision:n.revision,value:n}],r),await atomicWrite(this.walPath,a,this.temp("wal")),await syncDirectory(dirname(this.path)),await(this.options.fault?.("wal-synced")),await atomicWrite(this.path,a,this.temp("state")),await syncDirectory(dirname(this.path)),await(this.options.fault?.("state-renamed")),await unlinkIfPresent(this.walPath),await syncDirectory(dirname(this.path)),await(this.options.fault?.("wal-cleared")),await this.segments.prune(),n}temp(e){return`${this.path}.${e}.${process.pid}.${randomUUID()}.tmp`}async exclusive(e){const t=this.tail.catch(()=>{}).then(e);return this.tail=t.catch(()=>{}),await t}}function state(e){const t={schemaVersion:1,revision:e.revision,servers:Object.freeze(e.servers.map(normalizeServer).sort((e,t)=>e.name.localeCompare(t.name)))};return Object.freeze({...t,sha256:digest(t)})}function normalizeServer(e){requireName(e.name);for(const[t,i]of[["definitionRef",e.definitionRef],["version",e.version],["sha256",e.sha256]])if("string"!=typeof i||!i||i.length>512)throw new Error(`Invalid MCP ${t}`);if(!/^[a-f0-9]{64}$/i.test(e.sha256))throw new Error("Invalid MCP sha256");if(void 0!==e.credentialRef&&(!e.credentialRef||e.credentialRef.length>512))throw new Error("Invalid MCP credentialRef");assertPublicMcpValues([e.definitionRef,e.version,e.credentialRef],"MCP durable reference");const t=void 0===e.config?void 0:normalizeMcpPublicServerConfig(e.config);return Object.freeze({name:e.name,definitionRef:e.definitionRef,version:e.version,sha256:e.sha256.toLowerCase(),...e.credentialRef?{credentialRef:e.credentialRef}:{},...t?{config:t}:{}})}export function normalizeMcpPublicServerConfig(e){if(!e||"object"!=typeof e||Array.isArray(e))throw new Error("Invalid MCP public config");const t=new Set(["transport","url","connectionTimeoutMs","command","args","cwd"]);if(Object.keys(e).some(e=>!t.has(e)))throw new Error("MCP desired state accepts public config only");if(!["sse","streamable-http","stdio"].includes(e.transport))throw new Error("Invalid MCP transport");if(void 0!==e.connectionTimeoutMs&&(!Number.isSafeInteger(e.connectionTimeoutMs)||e.connectionTimeoutMs<=0||e.connectionTimeoutMs>12e4))throw new Error("Invalid MCP connection timeout");if("stdio"===e.transport){if("string"!=typeof e.command||!e.command||void 0!==e.url)throw new Error("Invalid MCP stdio config");if(void 0!==e.args&&(!Array.isArray(e.args)||e.args.some(e=>"string"!=typeof e)))throw new Error("Invalid MCP stdio args");return assertPublicMcpValues([e.command,...e.args??[],e.cwd],"MCP stdio public definition contains secret-like argv"),Object.freeze({transport:e.transport,command:e.command,...e.args?{args:Object.freeze([...e.args])}:{},...e.cwd?{cwd:e.cwd}:{},...e.connectionTimeoutMs?{connectionTimeoutMs:e.connectionTimeoutMs}:{}})}if("string"!=typeof e.url||void 0!==e.command||void 0!==e.args||void 0!==e.cwd)throw new Error("Invalid MCP HTTP config");let i;try{i=new URL(e.url)}catch{throw new Error("Invalid MCP HTTP config")}if("https:"!==i.protocol||i.username||i.password||i.search||i.hash)throw new Error("MCP HTTP definition requires a credential-free HTTPS URL");let r=i.pathname;try{r=decodeURIComponent(r)}catch{}return assertPublicMcpValues([i.toString(),r],"MCP HTTP public definition"),Object.freeze({transport:e.transport,url:i.toString(),...e.connectionTimeoutMs?{connectionTimeoutMs:e.connectionTimeoutMs}:{}})}function digest(e){return createHash("sha256").update(canonical(e)).digest("hex")}function canonical(e){return null===e||"object"!=typeof e?JSON.stringify(e):Array.isArray(e)?`[${e.map(canonical).join(",")}]`:`{${Object.entries(e).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>`${JSON.stringify(e)}:${canonical(t)}`).join(",")}}`}function encode(e){return`${canonical(e)}\n`}async function readState(e){let t;try{t=await readFile(e,"utf8")}catch(e){return"ENOENT"===e.code?{kind:"missing"}:{kind:"corrupt"}}try{const e=JSON.parse(t);if("revision,schemaVersion,servers,sha256"!==Object.keys(e).sort().join(",")||1!==e.schemaVersion||"string"!=typeof e.revision||!/^(0|[1-9][0-9]*)$/.test(e.revision)||!Array.isArray(e.servers)||"string"!=typeof e.sha256)return{kind:"corrupt"};const i=state({schemaVersion:1,revision:e.revision,servers:e.servers});return i.sha256===e.sha256?{kind:"valid",state:i}:{kind:"corrupt"}}catch{return{kind:"corrupt"}}}async function atomicWrite(e,t,i){const r=await open(i,"wx",384);try{await r.writeFile(t,"utf8"),await r.sync()}finally{await r.close()}await chmod(i,384),await rename(i,e),await chmod(e,384)}async function syncDirectory(e){const t=await open(e,"r");try{await t.sync()}finally{await t.close()}}async function unlinkIfPresent(e){try{await unlink(e)}catch(e){if("ENOENT"!==e.code)throw e}}const requireName=e=>{requireSecretFreeMcpServerName(e)};
@@ -0,0 +1 @@
1
+ const JWT_RE=/(?:^|[^A-Za-z0-9_-])eyJ[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}(?:$|[^A-Za-z0-9_-])/,AUTH_RE=/(?:^|[\s"'])(?:bearer\s+[A-Za-z0-9._~+\/-]{12,}|basic\s+[A-Za-z0-9+/]{12,}={0,2})(?:$|[\s"'])/i,PROVIDER_KEY_RE=/(?:^|[^A-Za-z0-9])(?:sk-[A-Za-z0-9_-]{16,}|ghp_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,}|xox[baprs]-[A-Za-z0-9-]{16,}|(?:AKIA|ASIA)[A-Z0-9]{16})(?:$|[^A-Za-z0-9])/,PEM_PRIVATE_KEY_RE=/-----BEGIN(?: [A-Z0-9]+)* PRIVATE KEY-----/,SECRET_ASSIGNMENT_RE=/(?:^|[\s,;])(?:--?)?(?:api[-_]?key|access[-_]?token|auth(?:orization)?|bearer|credential|password|passwd|private[-_]?key|secret|token)(?:\s|=|:|$)/i,SECRET_NAME_ASSIGNMENT_RE=/(?:^|[\s,;])(?:--?)?[A-Za-z0-9]*(?:[-_][A-Za-z0-9]+)*[-_](?:api[-_]?key|access[-_]?key|access[-_]?token|auth(?:orization)?|credential|password|passwd|private[-_]?key|secret|token)(?:[-_][A-Za-z0-9]+)*(?:\s*=|\s*:|\s+|$)/i,URL_CREDENTIAL_RE=/(?:https?|wss?):\/\/[^/@\s]+:[^/@\s]+@/i;export function containsHighConfidenceSecret(e){return JWT_RE.test(e)||AUTH_RE.test(e)||PROVIDER_KEY_RE.test(e)||PEM_PRIVATE_KEY_RE.test(e)||SECRET_ASSIGNMENT_RE.test(e)||SECRET_NAME_ASSIGNMENT_RE.test(e)||URL_CREDENTIAL_RE.test(e)}export function assertNoHighConfidenceSecret(e,t){if(containsHighConfidenceSecret(e))throw new Error(`${t}: secret-like material`)}export function assertPublicMcpValues(e,t){for(const s of e)void 0!==s&&assertNoHighConfidenceSecret(s,t)}export function requireSecretFreeMcpServerName(e){if("string"!=typeof e||!/^[a-zA-Z0-9_-]{1,64}$/.test(e))throw new Error("Invalid MCP server name");return assertNoHighConfidenceSecret(e,"MCP server name"),e}