From 09426a692de1c71d4aa3fa6e8e9d30a548a4398a Mon Sep 17 00:00:00 2001 From: Hulkito Date: Mon, 23 Mar 2026 11:51:16 +0100 Subject: [PATCH] feat(ext): add /api/metrics fallback and single-flight startup guard The extension's probeServer() only checked /health with a 1s timeout. When /health was slow or unavailable during startup, the extension reported the server as down even though /api/metrics was responding. Additionally, ensureServerRunning() had no concurrency guard, so VS Code activation and MCP provider resolution could race and trigger duplicate startup sequences. Changes: - Replace untyped probeServer() with generic probeJsonEndpoint() supporting typed StartupProbeOutcome/StartupProbeResolution - probeServer() now tries /health first, falls back to /api/metrics - Add describeStartupProbeFailure() for structured probe diagnostics - Add resolveStartupProbeResult() for multi-endpoint resolution - Add createSingleFlightRunner() to coalesce concurrent calls - Increase probe timeout from 1000ms to 2000ms - Log probe failure details in startup and wait-for-ready loops All workspace-dev logic remains intact and unmodified. --- vscode-agentchatbus/out/extension.js | 68 ++++----- vscode-agentchatbus/out/logic/testExports.js | 6 +- vscode-agentchatbus/src/busServerManager.ts | 136 +++++++++++++++--- .../src/logic/busServerManager.ts | 110 ++++++++++++++ vscode-agentchatbus/src/logic/testExports.ts | 3 + .../test/logicBusServerManager.test.js | 101 +++++++++++++ 6 files changed, 366 insertions(+), 58 deletions(-) diff --git a/vscode-agentchatbus/out/extension.js b/vscode-agentchatbus/out/extension.js index 500457c..e7d4640 100644 --- a/vscode-agentchatbus/out/extension.js +++ b/vscode-agentchatbus/out/extension.js @@ -1,9 +1,9 @@ -"use strict";var Ft=Object.create;var ve=Object.defineProperty;var Ot=Object.getOwnPropertyDescriptor;var Ht=Object.getOwnPropertyNames;var jt=Object.getPrototypeOf,Vt=Object.prototype.hasOwnProperty;var qt=(s,e)=>()=>(e||s((e={exports:{}}).exports,e),e.exports),Gt=(s,e)=>{for(var t in e)ve(s,t,{get:e[t],enumerable:!0})},je=(s,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of Ht(e))!Vt.call(s,o)&&o!==t&&ve(s,o,{get:()=>e[o],enumerable:!(r=Ot(e,o))||r.enumerable});return s};var h=(s,e,t)=>(t=s!=null?Ft(jt(s)):{},je(e||!s||!s.__esModule?ve(t,"default",{value:s,enumerable:!0}):t,s)),zt=s=>je(ve({},"__esModule",{value:!0}),s);var Ye=qt((Es,Je)=>{var Ve=require("url").parse,Jt=require("events"),Yt=require("https"),Kt=require("http"),Qt=require("util"),Xt=["pfx","key","passphrase","cert","ca","ciphers","rejectUnauthorized","secureProtocol","servername","checkServerIdentity"],ze=[239,187,191],Zt=58,es=32,qe=10,ts=13,Ge=1024*256,ss=/^(cookie|authorization)$/i;function rs(s){return ze.every(function(e,t){return s[t]===e})}function w(s,e){var t=w.CONNECTING,r=e&&e.headers,o=!1;Object.defineProperty(this,"readyState",{get:function(){return t}}),Object.defineProperty(this,"url",{get:function(){return s}});var n=this;n.reconnectInterval=1e3,n.connectionInProgress=!1;function i(g){t!==w.CLOSED&&(t=w.CONNECTING,U("error",new le("error",{message:g})),k&&(s=k,k=null,o=!1),setTimeout(function(){t!==w.CONNECTING||n.connectionInProgress||(n.connectionInProgress=!0,W())},n.reconnectInterval))}var a,l="";r&&r["Last-Event-ID"]&&(l=r["Last-Event-ID"],delete r["Last-Event-ID"]);var d=!1,v="",P="",k=null;function W(){var g=Ve(s),C=g.protocol==="https:";if(g.headers={"Cache-Control":"no-cache",Accept:"text/event-stream"},l&&(g.headers["Last-Event-ID"]=l),r){var b=o?os(r):r;for(var T in b){var R=b[T];R&&(g.headers[T]=R)}}g.rejectUnauthorized=!(e&&!e.rejectUnauthorized),e&&e.createConnection!==void 0&&(g.createConnection=e.createConnection);var $=e&&e.proxy;if($){var x=Ve(e.proxy);C=x.protocol==="https:",g.protocol=C?"https:":"http:",g.path=s,g.headers.Host=g.host,g.hostname=x.hostname,g.host=x.host,g.port=x.port}if(e&&e.https){for(var M in e.https)if(Xt.indexOf(M)!==-1){var D=e.https[M];D!==void 0&&(g[M]=D)}}e&&e.withCredentials!==void 0&&(g.withCredentials=e.withCredentials),a=(C?Yt:Kt).request(g,function(p){if(n.connectionInProgress=!1,p.statusCode===500||p.statusCode===502||p.statusCode===503||p.statusCode===504){U("error",new le("error",{status:p.statusCode,message:p.statusMessage})),i();return}if(p.statusCode===301||p.statusCode===302||p.statusCode===307){var F=p.headers.location;if(!F){U("error",new le("error",{status:p.statusCode,message:p.statusMessage}));return}var Ae=new URL(s).origin,Ee=new URL(F).origin;o=Ae!==Ee,p.statusCode===307&&(k=s),s=F,process.nextTick(W);return}if(p.statusCode!==200)return U("error",new le("error",{status:p.statusCode,message:p.statusMessage})),n.close();t=w.OPEN,p.on("close",function(){p.removeAllListeners("close"),p.removeAllListeners("end"),i()}),p.on("end",function(){p.removeAllListeners("close"),p.removeAllListeners("end"),i()}),U("open",new le("open"));var S,ce,O=0,de=-1,J=0,L=0;p.on("data",function(H){S?(H.length>S.length-L&&(J=S.length*2+H.length,J>Ge&&(J=S.length+H.length+Ge),ce=Buffer.alloc(J),S.copy(ce,0,0,L),S=ce),H.copy(S,L),L+=H.length):(S=H,rs(S)&&(S=S.slice(ze.length)),L=S.length);for(var A=0,Y=L;A0&&(S=S.slice(A,L),L=S.length)})}),a.on("error",function(p){n.connectionInProgress=!1,i(p.message)}),a.setNoDelay&&a.setNoDelay(!0),a.end()}W();function U(){n.listeners(arguments[0]).length>0&&n.emit.apply(n,arguments)}this._close=function(){t!==w.CLOSED&&(t=w.CLOSED,a.abort&&a.abort(),a.xhr&&a.xhr.abort&&a.xhr.abort())};function ae(g,C,b,T){if(T===0){if(v.length>0){var R=P||"message";U(R,new ns(R,{data:v.slice(0,-1),lastEventId:l,origin:new URL(s).origin})),v=""}P=void 0}else if(b>0){var $=b<0,x=0,M=g.slice(C,C+($?T:b)).toString();$?x=T:g[C+b+1]!==es?x=b+1:x=b+2,C+=x;var D=T-x,p=g.slice(C,C+D).toString();if(M==="data")v+=p+` -`;else if(M==="event")P=p;else if(M==="id")l=p;else if(M==="retry"){var F=parseInt(p,10);Number.isNaN(F)||(n.reconnectInterval=F)}}}}Je.exports=w;Qt.inherits(w,Jt.EventEmitter);w.prototype.constructor=w;["open","error","message"].forEach(function(s){Object.defineProperty(w.prototype,"on"+s,{get:function(){var t=this.listeners(s)[0];return t?t._listener?t._listener:t:void 0},set:function(t){this.removeAllListeners(s),this.addEventListener(s,t)}})});Object.defineProperty(w,"CONNECTING",{enumerable:!0,value:0});Object.defineProperty(w,"OPEN",{enumerable:!0,value:1});Object.defineProperty(w,"CLOSED",{enumerable:!0,value:2});w.prototype.CONNECTING=0;w.prototype.OPEN=1;w.prototype.CLOSED=2;w.prototype.close=function(){this._close()};w.prototype.addEventListener=function(e,t){typeof t=="function"&&(t._listener=t,this.on(e,t))};w.prototype.dispatchEvent=function(e){if(!e.type)throw new Error("UNSPECIFIED_EVENT_TYPE_ERR");this.emit(e.type,e.detail)};w.prototype.removeEventListener=function(e,t){typeof t=="function"&&(t._listener=void 0,this.removeListener(e,t))};function le(s,e){if(Object.defineProperty(this,"type",{writable:!1,value:s,enumerable:!0}),e)for(var t in e)e.hasOwnProperty(t)&&Object.defineProperty(this,t,{writable:!1,value:e[t],enumerable:!0})}function ns(s,e){Object.defineProperty(this,"type",{writable:!1,value:s,enumerable:!0});for(var t in e)e.hasOwnProperty(t)&&Object.defineProperty(this,t,{writable:!1,value:e[t],enumerable:!0})}function os(s){var e={};for(var t in s)ss.test(t)||(e[t]=s[t]);return e}});var Ts={};Gt(Ts,{activate:()=>Ps,deactivate:()=>Ms});module.exports=zt(Ts);var c=h(require("vscode")),Te=h(require("os"));var X=h(require("vscode")),st=h(Ye());function is(s){return typeof s=="string"?{content:s}:s}function Ke(s,e){let t=is(s);return{author:t.author||"human",content:t.content,mentions:t.mentions,metadata:t.metadata,images:t.images,reply_to_msg_id:t.reply_to_msg_id,expected_last_seq:e.current_seq,reply_token:e.reply_token}}function Qe(s,e){return s!==409||!e||typeof e!="object"?!1:e.action==="READ_MESSAGES_THEN_CALL_MSG_WAIT"}function Xe(s,e,t){let r=e!==s;return{baseUrl:e,uiAgentAuth:r?null:t,shouldReconnectSse:r}}function Ze(s){if(!s||typeof s!="object")throw new Error("Invalid UI agent registration payload");let e=s;if(!e.agent_id||!e.token)throw new Error("Invalid UI agent registration payload");return{agent_id:String(e.agent_id),token:String(e.token)}}function et(s){return`${s}/events`}function tt(s){try{return{ok:!0,data:JSON.parse(s)}}catch(e){return{ok:!1,error:e}}}var me=class{baseUrl;eventSource=null;uiAgentAuth=null;onSseEvent=new X.EventEmitter;constructor(){let e=X.workspace.getConfiguration("agentchatbus");this.baseUrl=e.get("serverUrl","http://127.0.0.1:39765"),X.workspace.onDidChangeConfiguration(t=>{if(t.affectsConfiguration("agentchatbus.serverUrl")){let r=X.workspace.getConfiguration("agentchatbus"),o=Xe(this.baseUrl,r.get("serverUrl","http://127.0.0.1:39765"),this.uiAgentAuth);this.baseUrl=o.baseUrl,this.uiAgentAuth=o.uiAgentAuth,o.shouldReconnectSse&&this.reconnectSSE()}})}getBaseUrl(){return this.baseUrl}async getThreads(e=!1){let t=await fetch(`${this.baseUrl}/api/threads?include_archived=${e}`);if(!t.ok)throw new Error(`HTTP ${t.status} fetching threads`);return(await t.json()).threads}async getMessages(e,t){let r=`${this.baseUrl}/api/threads/${e}/messages`+(t!==void 0?`?after_seq=${t}`:""),o=await fetch(r);if(!o.ok)throw new Error(`HTTP ${o.status} fetching messages`);return await o.json()}async getSyncContext(e){let t=await fetch(`${this.baseUrl}/api/threads/${e}/sync-context`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({})});if(!t.ok)throw new Error(`HTTP ${t.status} fetching sync-context`);return await t.json()}async sendMessage(e,t,r){let o=Ke(t,r);if(!o.reply_token||typeof o.expected_last_seq!="number"){let a=await this.getSyncContext(e);o.reply_token=a.reply_token,o.expected_last_seq=a.current_seq}let n=await fetch(`${this.baseUrl}/api/threads/${e}/messages`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)}),i="";if(!n.ok){i=await n.text();try{let a=JSON.parse(i);if(Qe(n.status,a)){console.log("[AgentChatBus] Recovering from sync mismatch. Fetching new context...");let l=await this.getSyncContext(e);o.reply_token=l.reply_token,o.expected_last_seq=l.current_seq,n=await fetch(`${this.baseUrl}/api/threads/${e}/messages`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)}),n.ok||(i=await n.text())}}catch(a){console.error("[AgentChatBus] Retry logic failed:",a.message)}}if(!n.ok)throw new Error(`Failed to send message: HTTP ${n.status} ${i}`);return await n.json()}async getAgents(){let e=await fetch(`${this.baseUrl}/api/agents`);if(!e.ok)throw new Error(`HTTP ${e.status} fetching agents`);let t=await e.json();return t.agents||t}async getThreadAgents(e){let t=await fetch(`${this.baseUrl}/api/threads/${encodeURIComponent(e)}/agents`);if(!t.ok)throw new Error(`HTTP ${t.status} fetching thread agents`);let r=await t.json();return r.agents||r}async getMetrics(){let e=await fetch(`${this.baseUrl}/api/metrics`);if(!e.ok)throw new Error(`HTTP ${e.status} fetching metrics`);return await e.json()}async getHealth(){let e=await fetch(`${this.baseUrl}/health`);if(!e.ok)throw new Error(`HTTP ${e.status} fetching health`);return await e.json()}async ensureUiAgentAuth(){if(this.uiAgentAuth?.agent_id&&this.uiAgentAuth?.token)return this.uiAgentAuth;let e=await fetch(`${this.baseUrl}/api/agents/register`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:"vscode-ui-human",display_name:"VSCode User",ide:"vscode",model:"human"})});if(!e.ok)throw new Error(`HTTP ${e.status} registering UI agent`);let t=await e.json();return this.uiAgentAuth=Ze(t),this.uiAgentAuth}async createThread(e){let t=await this.ensureUiAgentAuth(),r=await fetch(`${this.baseUrl}/api/threads`,{method:"POST",headers:{"Content-Type":"application/json","X-Agent-Token":t.token},body:JSON.stringify({topic:e,creator_agent_id:t.agent_id})});if(!r.ok)throw new Error(`HTTP ${r.status} creating thread: ${await r.text()}`);let o=await r.json();return{id:String(o.id),topic:String(o.topic||e),status:String(o.status||"discuss"),created_at:String(o.created_at||new Date().toISOString())}}async deleteThread(e){return(await fetch(`${this.baseUrl}/api/threads/${e}`,{method:"DELETE"})).ok}async archiveThread(e){return(await fetch(`${this.baseUrl}/api/threads/${e}/archive`,{method:"POST"})).ok}async unarchiveThread(e){return(await fetch(`${this.baseUrl}/api/threads/${e}/unarchive`,{method:"POST"})).ok}async setThreadState(e,t){return(await fetch(`${this.baseUrl}/api/threads/${e}/state`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({state:t})})).ok}async uploadImage(e,t,r){let o=new Uint8Array(r.byteLength);o.set(r);let n=new Blob([o.buffer],{type:t||"application/octet-stream"}),i=new FormData;i.append("file",n,e||"image");let a=await fetch(`${this.baseUrl}/api/upload/image`,{method:"POST",body:i});if(!a.ok)throw new Error(`HTTP ${a.status} uploading image: ${await a.text()}`);return await a.json()}connectSSE(){this.disconnectSSE();let e=et(this.baseUrl);console.log(`[AgentChatBus] Connecting SSE to ${e}...`),this.eventSource=new st.default(e),this.eventSource.onmessage=t=>{let r=tt(t.data);r.ok?this.onSseEvent.fire(r.data):console.error("Failed to parse SSE event",r.error)},this.eventSource.onerror=t=>{console.log("[AgentChatBus] SSE error or disconnected. EventSource will typically auto-reconnect.")}}disconnectSSE(){this.eventSource&&(this.eventSource.close(),this.eventSource=null)}reconnectSSE(){this.disconnectSSE(),this.connectSSE()}};var q=h(require("vscode"));var rt=h(require("fs")),Z=h(require("path")),nt=h(require("vscode"));function as(){let s=Z.resolve(__dirname,".."),e=Z.resolve(__dirname,"..","..");return rt.existsSync(Z.join(s,"resources"))?s:e}var cs=as();function fe(s){let e=nt.Uri.file(Z.join(cs,"resources","icons","tree",s));return{light:e,dark:e}}function ot(s){let e=Date.parse(String(s||""));return Number.isFinite(e)?e:0}function it(s,e){let t=new Set(e);return s.filter(r=>t.has(r.status)).sort((r,o)=>ot(o.created_at)-ot(r.created_at))}function ds(s){switch(s){case"implement":return"thread-implement.svg";case"review":return"thread-review.svg";case"done":return"thread-done.svg";case"closed":return"thread-closed.svg";case"archived":return"thread-archived.svg";default:return"thread-discuss.svg"}}function at(s){return!!(s&&(s.startsWith("thread.")||s==="msg.new"))}function ct(s){return new Set(s).has("archived")}function dt(s){return{label:s.topic||"Untitled Thread",tooltip:`ID: ${s.id} -Status: ${s.status}`,description:s.status,iconFile:ds(s.status),contextValue:`thread:${s.status}`,commandId:"agentchatbus.openThread",commandArguments:[s]}}var be=class{constructor(e){this.apiClient=e;e.onSseEvent.event(t=>{at(t.type)&&this.refresh()})}_onDidChangeTreeData=new q.EventEmitter;onDidChangeTreeData=this._onDidChangeTreeData.event;_statusFilter=new Set(["discuss","implement","review","done","closed"]);setStatusFilter(e){this._statusFilter=new Set(e),this.refresh()}getStatusFilter(){return Array.from(this._statusFilter)}refresh(){this._onDidChangeTreeData.fire()}getTreeItem(e){return e}async getChildren(e){if(e)return[];try{let t=ct(this._statusFilter);return it(await this.apiClient.getThreads(t),this._statusFilter).map(o=>new Ie(o))}catch(t){return q.window.showErrorMessage(`Failed to fetch AgentChatBus threads: ${t.message}`),[]}}},Ie=class extends q.TreeItem{constructor(t){let r=dt(t);super(r.label,q.TreeItemCollapsibleState.None);this.thread=t;this.tooltip=r.tooltip,this.description=r.description,this.iconPath=fe(r.iconFile),this.contextValue=r.contextValue,this.command={command:r.commandId,title:"Open Thread",arguments:r.commandArguments}}};var _=h(require("vscode"));function ls(s){let e=Date.parse(String(s||""));return Number.isFinite(e)?e:0}function _e(s){return ls(s.last_activity_time||s.last_heartbeat)}function lt(s,e){let t=e?.showOnlyRecent??!0,o=(e?.nowMs??Date.now())-3600*1e3;return(t?s.filter(i=>_e(i)>o||i.is_online):s.slice()).sort((i,a)=>i.is_online!==a.is_online?i.is_online?-1:1:_e(a)-_e(i))}function ps(s,e=Date.now()){let t=Math.round((e-s.getTime())/1e3);return t<60?"just now":t<3600?`${Math.floor(t/60)}m ago`:t<86400?`${Math.floor(t/3600)}h ago`:`${Math.floor(t/86400)}d ago`}function pt(s){return!!(s&&(s.startsWith("agent.")||s==="msg.new"))}function ut(s,e){let t=s.display_name||s.name||s.id,r=s.last_activity_time||s.last_heartbeat,o=r?ps(new Date(r),e?.nowMs):"Never";return{label:t,tooltip:`IDE: ${s.ide||"N/A"} +"use strict";var jt=Object.create;var ve=Object.defineProperty;var Vt=Object.getOwnPropertyDescriptor;var qt=Object.getOwnPropertyNames;var Gt=Object.getPrototypeOf,zt=Object.prototype.hasOwnProperty;var Jt=(s,e)=>()=>(e||s((e={exports:{}}).exports,e),e.exports),Yt=(s,e)=>{for(var t in e)ve(s,t,{get:e[t],enumerable:!0})},Ve=(s,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of qt(e))!zt.call(s,o)&&o!==t&&ve(s,o,{get:()=>e[o],enumerable:!(r=Vt(e,o))||r.enumerable});return s};var h=(s,e,t)=>(t=s!=null?jt(Gt(s)):{},Ve(e||!s||!s.__esModule?ve(t,"default",{value:s,enumerable:!0}):t,s)),Kt=s=>Ve(ve({},"__esModule",{value:!0}),s);var Ke=Jt((Us,Ye)=>{var qe=require("url").parse,Qt=require("events"),Xt=require("https"),Zt=require("http"),es=require("util"),ts=["pfx","key","passphrase","cert","ca","ciphers","rejectUnauthorized","secureProtocol","servername","checkServerIdentity"],Je=[239,187,191],ss=58,rs=32,Ge=10,ns=13,ze=1024*256,os=/^(cookie|authorization)$/i;function as(s){return Je.every(function(e,t){return s[t]===e})}function w(s,e){var t=w.CONNECTING,r=e&&e.headers,o=!1;Object.defineProperty(this,"readyState",{get:function(){return t}}),Object.defineProperty(this,"url",{get:function(){return s}});var n=this;n.reconnectInterval=1e3,n.connectionInProgress=!1;function a(g){t!==w.CLOSED&&(t=w.CONNECTING,R("error",new pe("error",{message:g})),k&&(s=k,k=null,o=!1),setTimeout(function(){t!==w.CONNECTING||n.connectionInProgress||(n.connectionInProgress=!0,W())},n.reconnectInterval))}var i,d="";r&&r["Last-Event-ID"]&&(d=r["Last-Event-ID"],delete r["Last-Event-ID"]);var l=!1,v="",P="",k=null;function W(){var g=qe(s),C=g.protocol==="https:";if(g.headers={"Cache-Control":"no-cache",Accept:"text/event-stream"},d&&(g.headers["Last-Event-ID"]=d),r){var b=o?cs(r):r;for(var T in b){var U=b[T];U&&(g.headers[T]=U)}}g.rejectUnauthorized=!(e&&!e.rejectUnauthorized),e&&e.createConnection!==void 0&&(g.createConnection=e.createConnection);var $=e&&e.proxy;if($){var M=qe(e.proxy);C=M.protocol==="https:",g.protocol=C?"https:":"http:",g.path=s,g.headers.Host=g.host,g.hostname=M.hostname,g.host=M.host,g.port=M.port}if(e&&e.https){for(var x in e.https)if(ts.indexOf(x)!==-1){var D=e.https[x];D!==void 0&&(g[x]=D)}}e&&e.withCredentials!==void 0&&(g.withCredentials=e.withCredentials),i=(C?Xt:Zt).request(g,function(p){if(n.connectionInProgress=!1,p.statusCode===500||p.statusCode===502||p.statusCode===503||p.statusCode===504){R("error",new pe("error",{status:p.statusCode,message:p.statusMessage})),a();return}if(p.statusCode===301||p.statusCode===302||p.statusCode===307){var F=p.headers.location;if(!F){R("error",new pe("error",{status:p.statusCode,message:p.statusMessage}));return}var Ee=new URL(s).origin,Ie=new URL(F).origin;o=Ee!==Ie,p.statusCode===307&&(k=s),s=F,process.nextTick(W);return}if(p.statusCode!==200)return R("error",new pe("error",{status:p.statusCode,message:p.statusMessage})),n.close();t=w.OPEN,p.on("close",function(){p.removeAllListeners("close"),p.removeAllListeners("end"),a()}),p.on("end",function(){p.removeAllListeners("close"),p.removeAllListeners("end"),a()}),R("open",new pe("open"));var S,de,O=0,le=-1,Y=0,L=0;p.on("data",function(H){S?(H.length>S.length-L&&(Y=S.length*2+H.length,Y>ze&&(Y=S.length+H.length+ze),de=Buffer.alloc(Y),S.copy(de,0,0,L),S=de),H.copy(S,L),L+=H.length):(S=H,as(S)&&(S=S.slice(Je.length)),L=S.length);for(var A=0,K=L;A0&&(S=S.slice(A,L),L=S.length)})}),i.on("error",function(p){n.connectionInProgress=!1,a(p.message)}),i.setNoDelay&&i.setNoDelay(!0),i.end()}W();function R(){n.listeners(arguments[0]).length>0&&n.emit.apply(n,arguments)}this._close=function(){t!==w.CLOSED&&(t=w.CLOSED,i.abort&&i.abort(),i.xhr&&i.xhr.abort&&i.xhr.abort())};function ce(g,C,b,T){if(T===0){if(v.length>0){var U=P||"message";R(U,new is(U,{data:v.slice(0,-1),lastEventId:d,origin:new URL(s).origin})),v=""}P=void 0}else if(b>0){var $=b<0,M=0,x=g.slice(C,C+($?T:b)).toString();$?M=T:g[C+b+1]!==rs?M=b+1:M=b+2,C+=M;var D=T-M,p=g.slice(C,C+D).toString();if(x==="data")v+=p+` +`;else if(x==="event")P=p;else if(x==="id")d=p;else if(x==="retry"){var F=parseInt(p,10);Number.isNaN(F)||(n.reconnectInterval=F)}}}}Ye.exports=w;es.inherits(w,Qt.EventEmitter);w.prototype.constructor=w;["open","error","message"].forEach(function(s){Object.defineProperty(w.prototype,"on"+s,{get:function(){var t=this.listeners(s)[0];return t?t._listener?t._listener:t:void 0},set:function(t){this.removeAllListeners(s),this.addEventListener(s,t)}})});Object.defineProperty(w,"CONNECTING",{enumerable:!0,value:0});Object.defineProperty(w,"OPEN",{enumerable:!0,value:1});Object.defineProperty(w,"CLOSED",{enumerable:!0,value:2});w.prototype.CONNECTING=0;w.prototype.OPEN=1;w.prototype.CLOSED=2;w.prototype.close=function(){this._close()};w.prototype.addEventListener=function(e,t){typeof t=="function"&&(t._listener=t,this.on(e,t))};w.prototype.dispatchEvent=function(e){if(!e.type)throw new Error("UNSPECIFIED_EVENT_TYPE_ERR");this.emit(e.type,e.detail)};w.prototype.removeEventListener=function(e,t){typeof t=="function"&&(t._listener=void 0,this.removeListener(e,t))};function pe(s,e){if(Object.defineProperty(this,"type",{writable:!1,value:s,enumerable:!0}),e)for(var t in e)e.hasOwnProperty(t)&&Object.defineProperty(this,t,{writable:!1,value:e[t],enumerable:!0})}function is(s,e){Object.defineProperty(this,"type",{writable:!1,value:s,enumerable:!0});for(var t in e)e.hasOwnProperty(t)&&Object.defineProperty(this,t,{writable:!1,value:e[t],enumerable:!0})}function cs(s){var e={};for(var t in s)os.test(t)||(e[t]=s[t]);return e}});var _s={};Yt(_s,{activate:()=>Ts,deactivate:()=>Es});module.exports=Kt(_s);var c=h(require("vscode")),Ae=h(require("os"));var Z=h(require("vscode")),rt=h(Ke());function ds(s){return typeof s=="string"?{content:s}:s}function Qe(s,e){let t=ds(s);return{author:t.author||"human",content:t.content,mentions:t.mentions,metadata:t.metadata,images:t.images,reply_to_msg_id:t.reply_to_msg_id,expected_last_seq:e.current_seq,reply_token:e.reply_token}}function Xe(s,e){return s!==409||!e||typeof e!="object"?!1:e.action==="READ_MESSAGES_THEN_CALL_MSG_WAIT"}function Ze(s,e,t){let r=e!==s;return{baseUrl:e,uiAgentAuth:r?null:t,shouldReconnectSse:r}}function et(s){if(!s||typeof s!="object")throw new Error("Invalid UI agent registration payload");let e=s;if(!e.agent_id||!e.token)throw new Error("Invalid UI agent registration payload");return{agent_id:String(e.agent_id),token:String(e.token)}}function tt(s){return`${s}/events`}function st(s){try{return{ok:!0,data:JSON.parse(s)}}catch(e){return{ok:!1,error:e}}}var me=class{baseUrl;eventSource=null;uiAgentAuth=null;onSseEvent=new Z.EventEmitter;constructor(){let e=Z.workspace.getConfiguration("agentchatbus");this.baseUrl=e.get("serverUrl","http://127.0.0.1:39765"),Z.workspace.onDidChangeConfiguration(t=>{if(t.affectsConfiguration("agentchatbus.serverUrl")){let r=Z.workspace.getConfiguration("agentchatbus"),o=Ze(this.baseUrl,r.get("serverUrl","http://127.0.0.1:39765"),this.uiAgentAuth);this.baseUrl=o.baseUrl,this.uiAgentAuth=o.uiAgentAuth,o.shouldReconnectSse&&this.reconnectSSE()}})}getBaseUrl(){return this.baseUrl}async getThreads(e=!1){let t=await fetch(`${this.baseUrl}/api/threads?include_archived=${e}`);if(!t.ok)throw new Error(`HTTP ${t.status} fetching threads`);return(await t.json()).threads}async getMessages(e,t){let r=`${this.baseUrl}/api/threads/${e}/messages`+(t!==void 0?`?after_seq=${t}`:""),o=await fetch(r);if(!o.ok)throw new Error(`HTTP ${o.status} fetching messages`);return await o.json()}async getSyncContext(e){let t=await fetch(`${this.baseUrl}/api/threads/${e}/sync-context`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({})});if(!t.ok)throw new Error(`HTTP ${t.status} fetching sync-context`);return await t.json()}async sendMessage(e,t,r){let o=Qe(t,r);if(!o.reply_token||typeof o.expected_last_seq!="number"){let i=await this.getSyncContext(e);o.reply_token=i.reply_token,o.expected_last_seq=i.current_seq}let n=await fetch(`${this.baseUrl}/api/threads/${e}/messages`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)}),a="";if(!n.ok){a=await n.text();try{let i=JSON.parse(a);if(Xe(n.status,i)){console.log("[AgentChatBus] Recovering from sync mismatch. Fetching new context...");let d=await this.getSyncContext(e);o.reply_token=d.reply_token,o.expected_last_seq=d.current_seq,n=await fetch(`${this.baseUrl}/api/threads/${e}/messages`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)}),n.ok||(a=await n.text())}}catch(i){console.error("[AgentChatBus] Retry logic failed:",i.message)}}if(!n.ok)throw new Error(`Failed to send message: HTTP ${n.status} ${a}`);return await n.json()}async getAgents(){let e=await fetch(`${this.baseUrl}/api/agents`);if(!e.ok)throw new Error(`HTTP ${e.status} fetching agents`);let t=await e.json();return t.agents||t}async getThreadAgents(e){let t=await fetch(`${this.baseUrl}/api/threads/${encodeURIComponent(e)}/agents`);if(!t.ok)throw new Error(`HTTP ${t.status} fetching thread agents`);let r=await t.json();return r.agents||r}async getMetrics(){let e=await fetch(`${this.baseUrl}/api/metrics`);if(!e.ok)throw new Error(`HTTP ${e.status} fetching metrics`);return await e.json()}async getHealth(){let e=await fetch(`${this.baseUrl}/health`);if(!e.ok)throw new Error(`HTTP ${e.status} fetching health`);return await e.json()}async ensureUiAgentAuth(){if(this.uiAgentAuth?.agent_id&&this.uiAgentAuth?.token)return this.uiAgentAuth;let e=await fetch(`${this.baseUrl}/api/agents/register`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:"vscode-ui-human",display_name:"VSCode User",ide:"vscode",model:"human"})});if(!e.ok)throw new Error(`HTTP ${e.status} registering UI agent`);let t=await e.json();return this.uiAgentAuth=et(t),this.uiAgentAuth}async createThread(e){let t=await this.ensureUiAgentAuth(),r=await fetch(`${this.baseUrl}/api/threads`,{method:"POST",headers:{"Content-Type":"application/json","X-Agent-Token":t.token},body:JSON.stringify({topic:e,creator_agent_id:t.agent_id})});if(!r.ok)throw new Error(`HTTP ${r.status} creating thread: ${await r.text()}`);let o=await r.json();return{id:String(o.id),topic:String(o.topic||e),status:String(o.status||"discuss"),created_at:String(o.created_at||new Date().toISOString())}}async deleteThread(e){return(await fetch(`${this.baseUrl}/api/threads/${e}`,{method:"DELETE"})).ok}async archiveThread(e){return(await fetch(`${this.baseUrl}/api/threads/${e}/archive`,{method:"POST"})).ok}async unarchiveThread(e){return(await fetch(`${this.baseUrl}/api/threads/${e}/unarchive`,{method:"POST"})).ok}async setThreadState(e,t){return(await fetch(`${this.baseUrl}/api/threads/${e}/state`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({state:t})})).ok}async uploadImage(e,t,r){let o=new Uint8Array(r.byteLength);o.set(r);let n=new Blob([o.buffer],{type:t||"application/octet-stream"}),a=new FormData;a.append("file",n,e||"image");let i=await fetch(`${this.baseUrl}/api/upload/image`,{method:"POST",body:a});if(!i.ok)throw new Error(`HTTP ${i.status} uploading image: ${await i.text()}`);return await i.json()}connectSSE(){this.disconnectSSE();let e=tt(this.baseUrl);console.log(`[AgentChatBus] Connecting SSE to ${e}...`),this.eventSource=new rt.default(e),this.eventSource.onmessage=t=>{let r=st(t.data);r.ok?this.onSseEvent.fire(r.data):console.error("Failed to parse SSE event",r.error)},this.eventSource.onerror=t=>{console.log("[AgentChatBus] SSE error or disconnected. EventSource will typically auto-reconnect.")}}disconnectSSE(){this.eventSource&&(this.eventSource.close(),this.eventSource=null)}reconnectSSE(){this.disconnectSSE(),this.connectSSE()}};var q=h(require("vscode"));var nt=h(require("fs")),ee=h(require("path")),ot=h(require("vscode"));function ls(){let s=ee.resolve(__dirname,".."),e=ee.resolve(__dirname,"..","..");return nt.existsSync(ee.join(s,"resources"))?s:e}var ps=ls();function fe(s){let e=ot.Uri.file(ee.join(ps,"resources","icons","tree",s));return{light:e,dark:e}}function at(s){let e=Date.parse(String(s||""));return Number.isFinite(e)?e:0}function it(s,e){let t=new Set(e);return s.filter(r=>t.has(r.status)).sort((r,o)=>at(o.created_at)-at(r.created_at))}function us(s){switch(s){case"implement":return"thread-implement.svg";case"review":return"thread-review.svg";case"done":return"thread-done.svg";case"closed":return"thread-closed.svg";case"archived":return"thread-archived.svg";default:return"thread-discuss.svg"}}function ct(s){return!!(s&&(s.startsWith("thread.")||s==="msg.new"))}function dt(s){return new Set(s).has("archived")}function lt(s){return{label:s.topic||"Untitled Thread",tooltip:`ID: ${s.id} +Status: ${s.status}`,description:s.status,iconFile:us(s.status),contextValue:`thread:${s.status}`,commandId:"agentchatbus.openThread",commandArguments:[s]}}var be=class{constructor(e){this.apiClient=e;e.onSseEvent.event(t=>{ct(t.type)&&this.refresh()})}_onDidChangeTreeData=new q.EventEmitter;onDidChangeTreeData=this._onDidChangeTreeData.event;_statusFilter=new Set(["discuss","implement","review","done","closed"]);setStatusFilter(e){this._statusFilter=new Set(e),this.refresh()}getStatusFilter(){return Array.from(this._statusFilter)}refresh(){this._onDidChangeTreeData.fire()}getTreeItem(e){return e}async getChildren(e){if(e)return[];try{let t=dt(this._statusFilter);return it(await this.apiClient.getThreads(t),this._statusFilter).map(o=>new _e(o))}catch(t){return q.window.showErrorMessage(`Failed to fetch AgentChatBus threads: ${t.message}`),[]}}},_e=class extends q.TreeItem{constructor(t){let r=lt(t);super(r.label,q.TreeItemCollapsibleState.None);this.thread=t;this.tooltip=r.tooltip,this.description=r.description,this.iconPath=fe(r.iconFile),this.contextValue=r.contextValue,this.command={command:r.commandId,title:"Open Thread",arguments:r.commandArguments}}};var _=h(require("vscode"));function hs(s){let e=Date.parse(String(s||""));return Number.isFinite(e)?e:0}function Re(s){return hs(s.last_activity_time||s.last_heartbeat)}function pt(s,e){let t=e?.showOnlyRecent??!0,o=(e?.nowMs??Date.now())-3600*1e3;return(t?s.filter(a=>Re(a)>o||a.is_online):s.slice()).sort((a,i)=>a.is_online!==i.is_online?a.is_online?-1:1:Re(i)-Re(a))}function gs(s,e=Date.now()){let t=Math.round((e-s.getTime())/1e3);return t<60?"just now":t<3600?`${Math.floor(t/60)}m ago`:t<86400?`${Math.floor(t/3600)}h ago`:`${Math.floor(t/86400)}d ago`}function ut(s){return!!(s&&(s.startsWith("agent.")||s==="msg.new"))}function ht(s,e){let t=s.display_name||s.name||s.id,r=s.last_activity_time||s.last_heartbeat,o=r?gs(new Date(r),e?.nowMs):"Never";return{label:t,tooltip:`IDE: ${s.ide||"N/A"} Model: ${s.model||"N/A"} Last Seen: ${o} -Activity: ${s.last_activity||"None"}`,description:s.is_online?"Online":`Last seen ${o}`,iconId:s.is_online?"circle-filled":"circle-outline",colorId:s.is_online?"testing.iconPassed":"testing.iconUntested",contextValue:"agent"}}var we=class{constructor(e){this.apiClient=e;e.onSseEvent.event(t=>{pt(t.type)&&this.refresh()})}_onDidChangeTreeData=new _.EventEmitter;onDidChangeTreeData=this._onDidChangeTreeData.event;showOnlyRecent=!0;toggleRecentFilter(){this.showOnlyRecent=!this.showOnlyRecent,_.commands.executeCommand("setContext","agentchatbus:agentsFilterActive",this.showOnlyRecent),this.refresh()}refresh(){this._onDidChangeTreeData.fire()}getTreeItem(e){return e}async getChildren(e){if(e)return[];try{let t=await this.apiClient.getAgents();return lt(t,{showOnlyRecent:this.showOnlyRecent}).map(r=>new Ue(r))}catch(t){return console.error("Failed to fetch agents:",t),[]}}},Ue=class extends _.TreeItem{constructor(t){let r=ut(t);super(r.label,_.TreeItemCollapsibleState.None);this.agent=t;this.tooltip=r.tooltip,this.description=r.description,this.iconPath=new _.ThemeIcon(r.iconId,new _.ThemeColor(r.colorId)),this.contextValue=r.contextValue}};var m=h(require("vscode")),ft=h(require("fs")),bt=h(require("path"));function ee(s){return String(s).replace(/&/g,"&").replace(/"/g,""").replace(//g,">")}function ht(s){return{enableScripts:!0,retainContextWhenHidden:!0,localResourceRoots:s}}function gt(s){return{enableScripts:!1,localResourceRoots:s}}function vt(){return` +Activity: ${s.last_activity||"None"}`,description:s.is_online?"Online":`Last seen ${o}`,iconId:s.is_online?"circle-filled":"circle-outline",colorId:s.is_online?"testing.iconPassed":"testing.iconUntested",contextValue:"agent"}}var we=class{constructor(e){this.apiClient=e;e.onSseEvent.event(t=>{ut(t.type)&&this.refresh()})}_onDidChangeTreeData=new _.EventEmitter;onDidChangeTreeData=this._onDidChangeTreeData.event;showOnlyRecent=!0;toggleRecentFilter(){this.showOnlyRecent=!this.showOnlyRecent,_.commands.executeCommand("setContext","agentchatbus:agentsFilterActive",this.showOnlyRecent),this.refresh()}refresh(){this._onDidChangeTreeData.fire()}getTreeItem(e){return e}async getChildren(e){if(e)return[];try{let t=await this.apiClient.getAgents();return pt(t,{showOnlyRecent:this.showOnlyRecent}).map(r=>new Ue(r))}catch(t){return console.error("Failed to fetch agents:",t),[]}}},Ue=class extends _.TreeItem{constructor(t){let r=ht(t);super(r.label,_.TreeItemCollapsibleState.None);this.agent=t;this.tooltip=r.tooltip,this.description=r.description,this.iconPath=new _.ThemeIcon(r.iconId,new _.ThemeColor(r.colorId)),this.contextValue=r.contextValue}};var m=h(require("vscode")),bt=h(require("fs")),wt=h(require("path"));function te(s){return String(s).replace(/&/g,"&").replace(/"/g,""").replace(//g,">")}function gt(s){return{enableScripts:!0,retainContextWhenHidden:!0,localResourceRoots:s}}function vt(s){return{enableScripts:!1,localResourceRoots:s}}function mt(){return` @@ -21,7 +21,7 @@ Activity: ${s.last_activity||"None"}`,description:s.is_online?"Online":`Last see

Please open the thread again from the Threads panel.

- `}function mt(s,e){return` + `}function ft(s,e){return` @@ -31,12 +31,12 @@ Activity: ${s.last_activity||"None"}`,description:s.is_online?"Online":`Last see
@@ -145,11 +145,11 @@ Activity: ${s.last_activity||"None"}`,description:s.is_online?"Online":`Last see - `}var I=class s{static VIEW_TYPE="agentChatBusChat.v2";static LEGACY_VIEW_TYPE="agentChatBusChat";static currentPanel;static _extensionPath="";static _workspaceDevWebUiRoot;_panel;_thread;_apiClient;_disposables=[];_currentSeq=0;_loadGeneration=0;constructor(e,t,r){this._panel=e,this._thread=t,this._apiClient=r,this._update(),this._loadInitialMessages(),this._panel.onDidDispose(()=>this.dispose(),null,this._disposables),this._panel.webview.onDidReceiveMessage(async n=>{switch(n.command){case"sendMessage":await this._handleSendMessage(n.payload);return;case"createThread":await this._handleCreateThread(n.topic);return;case"getServerIndicators":await this._handleServerIndicators(n.requestId);return;case"uploadImage":await this._handleUploadImage(n.requestId,n.payload);return;case"loadAgents":await this._handleLoadAgents(n.requestId);return}},null,this._disposables);let o=this._apiClient.onSseEvent.event(async n=>{if(n.type==="msg.new"&&n.payload&&n.payload.thread_id===this._thread.id){if(n.payload.seq<=this._currentSeq)return;await this._loadNewMessages()}});this._disposables.push(o)}static setExtensionPath(e){s._extensionPath=e}static setWorkspaceDevWebUiRoot(e){s._workspaceDevWebUiRoot=e}static reloadCurrentPanelForSourceChange(){s.currentPanel&&s.currentPanel._reloadForSourceChange()}static _getWebviewLocalResourceRoots(){let e=[m.Uri.file(s._extensionPath)];return s._workspaceDevWebUiRoot&&e.push(m.Uri.file(s._workspaceDevWebUiRoot)),e}static _escapeHtml(e){return String(e).replace(/&/g,"&").replace(/"/g,""").replace(//g,">")}static reviveRecoveredPanel(e){e.title="ACB: Chat (Restore)",e.webview.options=gt(s._getWebviewLocalResourceRoots()),e.webview.html=vt()}static createOrShow(e,t){let r=m.window.activeTextEditor?m.window.activeTextEditor.viewColumn:void 0;if(s.currentPanel){s.currentPanel._panel.reveal(r),s.currentPanel._switchThread(e);return}try{console.log("[ACB-ChatPanel] Creating webview panel, extensionPath:",s._extensionPath);let o=m.window.createWebviewPanel(s.VIEW_TYPE,`ACB: ${e.topic||e.id.substring(0,8)}`,r||m.ViewColumn.One,ht(s._getWebviewLocalResourceRoots()));console.log("[ACB-ChatPanel] Panel created successfully, setting content..."),s.currentPanel=new s(o,e,t),console.log("[ACB-ChatPanel] Panel fully initialized.")}catch(o){console.error("[ACB-ChatPanel] Failed to create webview panel:",o),m.window.showErrorMessage(`Failed to open chat panel: ${o?.message||o}`)}}_switchThread(e){this._thread.id!==e.id&&(this._thread=e,this._currentSeq=0,this._panel.title=`ACB: ${e.topic||e.id.substring(0,8)}`,this._update(),this._loadInitialMessages())}async _loadInitialMessages(){let e=this._thread.id,t=++this._loadGeneration;try{let r=await this._apiClient.getMessages(e),o=Array.isArray(r)?r:r.messages||[];if(e!==this._thread.id||t!==this._loadGeneration)return;o.length>0&&(this._currentSeq=o[o.length-1].seq),Array.isArray(r)||r.current_seq&&(this._currentSeq=r.current_seq),this._panel.webview.postMessage({command:"loadMessages",messages:o})}catch(r){m.window.showErrorMessage(`Failed to load messages: ${r.message}`)}}async _loadNewMessages(){let e=this._thread.id,t=this._loadGeneration;try{let r=await this._apiClient.getMessages(e,this._currentSeq),o=Array.isArray(r)?r:r.messages||[];if(e!==this._thread.id||t!==this._loadGeneration)return;let n=[];for(let i of o)i.seq>this._currentSeq&&(n.push(i),this._currentSeq=i.seq);n.length>0&&this._panel.webview.postMessage({command:"appendMessages",messages:n})}catch(r){console.error(`Failed to load new messages: ${r.message}`)}}_pushMessage(e){this._panel.webview.postMessage({command:"newMessage",message:e})}_reloadForSourceChange(){this._update(),this._loadInitialMessages()}async _handleSendMessage(e){if(!e?.content?.trim()&&(!e?.images||e.images.length===0)){this._panel.webview.postMessage({command:"sendResult",ok:!1,error:"Message content is empty."});return}try{let t=await this._apiClient.getSyncContext(this._thread.id),r=await this._apiClient.sendMessage(this._thread.id,e,t);r&&r.seq>this._currentSeq&&(this._currentSeq=r.seq,this._pushMessage(r)),this._panel.webview.postMessage({command:"sendResult",ok:!0})}catch(t){let r=t?.message||String(t);this._panel.webview.postMessage({command:"sendResult",ok:!1,error:r})}}async _handleUploadImage(e,t){if(e)try{let r=typeof t?.name=="string"?t.name:"image",o=typeof t?.type=="string"?t.type:"application/octet-stream",n=Array.isArray(t?.data)?Uint8Array.from(t.data):void 0;if(!n||n.length===0)throw new Error("Image payload is empty.");let i=await this._apiClient.uploadImage(r,o,n);this._panel.webview.postMessage({command:"uploadResult",requestId:e,ok:!0,image:i})}catch(r){this._panel.webview.postMessage({command:"uploadResult",requestId:e,ok:!1,error:r?.message||String(r)})}}async _handleLoadAgents(e){if(e)try{let t=await this._apiClient.getThreadAgents(this._thread.id);this._panel.webview.postMessage({command:"agentsResult",requestId:e,ok:!0,agents:t})}catch(t){this._panel.webview.postMessage({command:"agentsResult",requestId:e,ok:!1,error:t?.message||String(t)})}}async _handleCreateThread(e){let t=String(e||"").trim()||`New Thread ${new Date().toLocaleString()}`;try{let r=await this._apiClient.createThread(t);this._switchThread(r),m.commands.executeCommand("agentchatbus.refreshThreads")}catch(r){this._panel.webview.postMessage({command:"createThreadResult",ok:!1,error:r?.message||String(r)})}}async _handleServerIndicators(e){if(e)try{let t=await this._apiClient.getMetrics();this._panel.webview.postMessage({command:"serverIndicatorsResult",requestId:e,ok:!0,connected:!0,engine:String(t?.engine||"node")})}catch(t){this._panel.webview.postMessage({command:"serverIndicatorsResult",requestId:e,ok:!1,connected:!1,error:t?.message||String(t)})}}dispose(){for(s.currentPanel===this&&(s.currentPanel=void 0),this._panel.dispose();this._disposables.length;){let e=this._disposables.pop();e&&e.dispose()}}_update(){this._panel.webview.html=this._getHtmlForWebview()}_getHtmlForWebview(){let e=this._panel.webview,t=m.Uri.file(s._extensionPath),r=s._workspaceDevWebUiRoot?m.Uri.file(s._workspaceDevWebUiRoot):m.Uri.joinPath(t,"resources","web-ui"),o=e.asWebviewUri(m.Uri.joinPath(r,"extension","media","messageRenderer.js")),n=e.asWebviewUri(m.Uri.joinPath(r,"extension","media","messageRenderer.css")),i=e.asWebviewUri(m.Uri.joinPath(r,"extension","media","chatPanel.js")),a=e.asWebviewUri(m.Uri.joinPath(r,"extension","media","chatPanel.css")),l={threadId:this._thread.id,threadTopic:this._thread.topic||this._thread.id.substring(0,8),threadStatus:this._thread.status,baseUrl:this._apiClient.getBaseUrl(),mermaidScriptUrl:e.asWebviewUri(m.Uri.joinPath(r,"extension","media","mermaid.min.js")).toString(),theme:m.window.activeColorTheme.kind===m.ColorThemeKind.Dark?"dark":"light"};if(s._workspaceDevWebUiRoot){let d=this._buildWorkspaceDevTemplateHtml({templatePath:bt.join(s._workspaceDevWebUiRoot,"extension","index.html"),rendererScriptUri:o.toString(),rendererStyleUri:n.toString(),panelScriptUri:i.toString(),panelStyleUri:a.toString(),mermaidScriptUri:l.mermaidScriptUrl,config:l});if(d)return d}return mt({rendererScriptUri:o.toString(),rendererStyleUri:n.toString(),panelScriptUri:i.toString(),panelStyleUri:a.toString()},l)}_buildWorkspaceDevTemplateHtml(e){try{let t=ft.readFileSync(e.templatePath,"utf8");return t=t.replace(/ -`}_getStartupModeSummary(e){let t=String(e||"").trim().toLowerCase();return t==="workspace-dev-service"?{label:"workspace-dev-service",description:"Launched directly from the current AgentChatBus workspace using local agentchatbus-ts and local web-ui sources.",icon:"🛠️",managedBy:"🧪 This extension (workspace-dev runtime)"}:t==="bundled-ts-service"?{label:"bundled-ts-service",description:"Launched directly by this extension using bundled agentchatbus-ts runtime.",icon:"✅",managedBy:"🧩 This extension (owner-managed launch)"}:t==="external-service"?{label:"external-service",description:"External backend process detected by health probe and attached by this extension.",icon:"🔗",managedBy:"🌐 External service"}:t==="external-service-extension-managed"?{label:"external-service-extension-managed",description:"External backend detected with ownership assignable=true (extension-managed bootstrap).",icon:"🧩",managedBy:"🔌 External bootstrap (extension-assigned ownership)"}:t==="external-service-manual"?{label:"external-service-manual",description:"External backend detected with ownership assignable=false (manual command startup).",icon:"👤",managedBy:"👤 Manual external process"}:t==="external-service-unknown"?{label:"external-service-unknown",description:"External backend detected but ownership metadata is unavailable from /health.",icon:"📡",managedBy:"❓ External service (owner metadata unavailable)"}:{label:t||"N/A",description:"Unknown mode. Extension currently documents workspace-dev-service, bundled-ts-service, and the external-service variants.",icon:"🔎",managedBy:"❔ Unknown"}}_getBackendSummary(e,t){let r=String(e||"").trim().toLowerCase();if(r==="node")return"Node.js";if(r==="python")return"Python";let o=String(t||"").trim().toLowerCase();return o==="workspace-dev-service"||o==="bundled-ts-service"?"Node.js":"Unknown"}_escapeHtml(e){return String(e??"").replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}_renderLmNotes(e){return!Array.isArray(e)||e.length===0?"":`
${e.map(r=>`
• ${this._escapeHtml(r)}
`).join("")}
`}_renderLmModels(e){return!Array.isArray(e)||e.length===0?'
No models detected.
':e.map(t=>{let r=this._escapeHtml(t?.name||"model"),o=this._escapeHtml(t?.id||""),n=this._escapeHtml(t?.vendor||""),i=this._escapeHtml(t?.family||""),a=this._escapeHtml(t?.version||""),l=this._escapeHtml(t?.maxInputTokens??""),d=this._escapeHtml(t?.canSendRequest||"unknown"),v=`${n}/${i}/${a} • maxInputTokens=${l} • canSendRequest=${d}`;return` +`}_getStartupModeSummary(e){let t=String(e||"").trim().toLowerCase();return t==="workspace-dev-service"?{label:"workspace-dev-service",description:"Launched directly from the current AgentChatBus workspace using local agentchatbus-ts and local web-ui sources.",icon:"🛠️",managedBy:"🧪 This extension (workspace-dev runtime)"}:t==="bundled-ts-service"?{label:"bundled-ts-service",description:"Launched directly by this extension using bundled agentchatbus-ts runtime.",icon:"✅",managedBy:"🧩 This extension (owner-managed launch)"}:t==="external-service"?{label:"external-service",description:"External backend process detected by health probe and attached by this extension.",icon:"🔗",managedBy:"🌐 External service"}:t==="external-service-extension-managed"?{label:"external-service-extension-managed",description:"External backend detected with ownership assignable=true (extension-managed bootstrap).",icon:"🧩",managedBy:"🔌 External bootstrap (extension-assigned ownership)"}:t==="external-service-manual"?{label:"external-service-manual",description:"External backend detected with ownership assignable=false (manual command startup).",icon:"👤",managedBy:"👤 Manual external process"}:t==="external-service-unknown"?{label:"external-service-unknown",description:"External backend detected but ownership metadata is unavailable from /health.",icon:"📡",managedBy:"❓ External service (owner metadata unavailable)"}:{label:t||"N/A",description:"Unknown mode. Extension currently documents workspace-dev-service, bundled-ts-service, and the external-service variants.",icon:"🔎",managedBy:"❔ Unknown"}}_getBackendSummary(e,t){let r=String(e||"").trim().toLowerCase();if(r==="node")return"Node.js";if(r==="python")return"Python";let o=String(t||"").trim().toLowerCase();return o==="workspace-dev-service"||o==="bundled-ts-service"?"Node.js":"Unknown"}_escapeHtml(e){return String(e??"").replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}_renderLmNotes(e){return!Array.isArray(e)||e.length===0?"":`
${e.map(r=>`
• ${this._escapeHtml(r)}
`).join("")}
`}_renderLmModels(e){return!Array.isArray(e)||e.length===0?'
No models detected.
':e.map(t=>{let r=this._escapeHtml(t?.name||"model"),o=this._escapeHtml(t?.id||""),n=this._escapeHtml(t?.vendor||""),a=this._escapeHtml(t?.family||""),i=this._escapeHtml(t?.version||""),d=this._escapeHtml(t?.maxInputTokens??""),l=this._escapeHtml(t?.canSendRequest||"unknown"),v=`${n}/${a}/${i} • maxInputTokens=${d} • canSendRequest=${l}`;return`
${r} ${v} @@ -307,7 +307,7 @@ Activity: ${s.last_activity||"None"}`,description:s.is_online?"Online":`Last see step ${t}
- `).join("")}_getUptime(e){let t=Date.now()-e.getTime(),r=Math.floor(t/1e3),o=Math.floor(r/60),n=Math.floor(o/60),i=Math.floor(n/24);return i>0?`${i}d ${n%24}h ${o%60}m`:n>0?`${n}h ${o%60}m ${r%60}s`:o>0?`${o}m ${r%60}s`:`${r}s`}_formatUptimeSeconds(e){let t=Math.max(0,Math.floor(e)),r=Math.floor(t/60),o=Math.floor(r/60),n=Math.floor(o/24);return n>0?`${n}d ${o%24}h ${r%60}m`:o>0?`${o}h ${r%60}m ${t%60}s`:r>0?`${r}m ${t%60}s`:`${t}s`}};var y=h(require("vscode")),ke=class s{static currentPanel;panel;disposables=[];constructor(e){this.panel=e,this.panel.onDidDispose(()=>this.dispose(),null,this.disposables),this.panel.webview.onDidReceiveMessage(t=>{this.handleMessage(t)},null,this.disposables),this.render()}static createOrShow(){let e=y.window.activeTextEditor?.viewColumn??y.ViewColumn.One;if(s.currentPanel){s.currentPanel.panel.reveal(e),s.currentPanel.render();return}let t=y.window.createWebviewPanel("acbSettings","AgentChatBus: Server Settings",e,{enableScripts:!0});s.currentPanel=new s(t)}dispose(){for(s.currentPanel=void 0,this.panel.dispose();this.disposables.length>0;)this.disposables.pop()?.dispose()}async handleMessage(e){if(e.command==="openVscodeSettings"){await y.commands.executeCommand("workbench.action.openSettings","AgentChatBus");return}if(e.command!=="saveSettings")return;let t=e.payload||{},r=String(t.serverUrl||"").trim();if(!r){y.window.showErrorMessage("Server URL must not be empty.");return}let o=y.workspace.getConfiguration("agentchatbus"),n=this.resolvePreferredConfigurationTarget(),i=t.msgWaitMinTimeoutMs,a=Number(String(i??"").trim());if(!Number.isFinite(a)||a<0){y.window.showErrorMessage("msg_wait minimum timeout must be a non-negative number.");return}await o.update("serverUrl",r,n),await o.update("autoStartBusServer",!!t.autoStartBusServer,n),await o.update("msgWaitMinTimeoutMs",Math.floor(a),n);let l=!!t.enforceMsgWaitMinTimeout;await o.update("enforceMsgWaitMinTimeout",l,n),y.window.showInformationMessage(`AgentChatBus settings saved. Strict minimum wait enforcement: ${l?"ON":"OFF"}.`),await this.render()}resolveConfigurationTarget(e){let r=y.workspace.getConfiguration("agentchatbus").inspect(e);return r?.workspaceFolderValue!==void 0?y.ConfigurationTarget.WorkspaceFolder:r?.workspaceValue!==void 0?y.ConfigurationTarget.Workspace:y.ConfigurationTarget.Global}resolvePreferredConfigurationTarget(){let e=this.getSettings();return e.scopeLabel==="Workspace Folder"?y.ConfigurationTarget.WorkspaceFolder:e.scopeLabel==="Workspace"?y.ConfigurationTarget.Workspace:y.ConfigurationTarget.Global}getSettings(){let e=y.workspace.getConfiguration("agentchatbus"),t=e.inspect("serverUrl"),r=e.inspect("autoStartBusServer"),o=e.inspect("msgWaitMinTimeoutMs"),n=e.inspect("enforceMsgWaitMinTimeout"),i=t?.workspaceFolderValue!==void 0||r?.workspaceFolderValue!==void 0||o?.workspaceFolderValue!==void 0||n?.workspaceFolderValue!==void 0?"Workspace Folder":t?.workspaceValue!==void 0||r?.workspaceValue!==void 0||o?.workspaceValue!==void 0||n?.workspaceValue!==void 0?"Workspace":"User";return{serverUrl:e.get("serverUrl","http://127.0.0.1:39765"),autoStartBusServer:e.get("autoStartBusServer",!0),msgWaitMinTimeoutMs:Math.max(0,Math.floor(e.get("msgWaitMinTimeoutMs",6e4))),enforceMsgWaitMinTimeout:e.get("enforceMsgWaitMinTimeout",!1),scopeLabel:i}}async render(){let e=this.getSettings();this.panel.webview.html=this.getHtml(e)}getHtml(e){let t=this.escapeHtml(e.serverUrl),r=e.autoStartBusServer?"checked":"",o=this.escapeHtml(String(e.msgWaitMinTimeoutMs)),n=e.enforceMsgWaitMinTimeout?"checked":"";return` + `).join("")}_getUptime(e){let t=Date.now()-e.getTime(),r=Math.floor(t/1e3),o=Math.floor(r/60),n=Math.floor(o/60),a=Math.floor(n/24);return a>0?`${a}d ${n%24}h ${o%60}m`:n>0?`${n}h ${o%60}m ${r%60}s`:o>0?`${o}m ${r%60}s`:`${r}s`}_formatUptimeSeconds(e){let t=Math.max(0,Math.floor(e)),r=Math.floor(t/60),o=Math.floor(r/60),n=Math.floor(o/24);return n>0?`${n}d ${o%24}h ${r%60}m`:o>0?`${o}h ${r%60}m ${t%60}s`:r>0?`${r}m ${t%60}s`:`${t}s`}};var y=h(require("vscode")),Te=class s{static currentPanel;panel;disposables=[];constructor(e){this.panel=e,this.panel.onDidDispose(()=>this.dispose(),null,this.disposables),this.panel.webview.onDidReceiveMessage(t=>{this.handleMessage(t)},null,this.disposables),this.render()}static createOrShow(){let e=y.window.activeTextEditor?.viewColumn??y.ViewColumn.One;if(s.currentPanel){s.currentPanel.panel.reveal(e),s.currentPanel.render();return}let t=y.window.createWebviewPanel("acbSettings","AgentChatBus: Server Settings",e,{enableScripts:!0});s.currentPanel=new s(t)}dispose(){for(s.currentPanel=void 0,this.panel.dispose();this.disposables.length>0;)this.disposables.pop()?.dispose()}async handleMessage(e){if(e.command==="openVscodeSettings"){await y.commands.executeCommand("workbench.action.openSettings","AgentChatBus");return}if(e.command!=="saveSettings")return;let t=e.payload||{},r=String(t.serverUrl||"").trim();if(!r){y.window.showErrorMessage("Server URL must not be empty.");return}let o=y.workspace.getConfiguration("agentchatbus"),n=this.resolvePreferredConfigurationTarget(),a=t.msgWaitMinTimeoutMs,i=Number(String(a??"").trim());if(!Number.isFinite(i)||i<0){y.window.showErrorMessage("msg_wait minimum timeout must be a non-negative number.");return}await o.update("serverUrl",r,n),await o.update("autoStartBusServer",!!t.autoStartBusServer,n),await o.update("msgWaitMinTimeoutMs",Math.floor(i),n);let d=!!t.enforceMsgWaitMinTimeout;await o.update("enforceMsgWaitMinTimeout",d,n),y.window.showInformationMessage(`AgentChatBus settings saved. Strict minimum wait enforcement: ${d?"ON":"OFF"}.`),await this.render()}resolveConfigurationTarget(e){let r=y.workspace.getConfiguration("agentchatbus").inspect(e);return r?.workspaceFolderValue!==void 0?y.ConfigurationTarget.WorkspaceFolder:r?.workspaceValue!==void 0?y.ConfigurationTarget.Workspace:y.ConfigurationTarget.Global}resolvePreferredConfigurationTarget(){let e=this.getSettings();return e.scopeLabel==="Workspace Folder"?y.ConfigurationTarget.WorkspaceFolder:e.scopeLabel==="Workspace"?y.ConfigurationTarget.Workspace:y.ConfigurationTarget.Global}getSettings(){let e=y.workspace.getConfiguration("agentchatbus"),t=e.inspect("serverUrl"),r=e.inspect("autoStartBusServer"),o=e.inspect("msgWaitMinTimeoutMs"),n=e.inspect("enforceMsgWaitMinTimeout"),a=t?.workspaceFolderValue!==void 0||r?.workspaceFolderValue!==void 0||o?.workspaceFolderValue!==void 0||n?.workspaceFolderValue!==void 0?"Workspace Folder":t?.workspaceValue!==void 0||r?.workspaceValue!==void 0||o?.workspaceValue!==void 0||n?.workspaceValue!==void 0?"Workspace":"User";return{serverUrl:e.get("serverUrl","http://127.0.0.1:39765"),autoStartBusServer:e.get("autoStartBusServer",!0),msgWaitMinTimeoutMs:Math.max(0,Math.floor(e.get("msgWaitMinTimeoutMs",6e4))),enforceMsgWaitMinTimeout:e.get("enforceMsgWaitMinTimeout",!1),scopeLabel:a}}async render(){let e=this.getSettings();this.panel.webview.html=this.getHtml(e)}getHtml(e){let t=this.escapeHtml(e.serverUrl),r=e.autoStartBusServer?"checked":"",o=this.escapeHtml(String(e.msgWaitMinTimeoutMs)),n=e.enforceMsgWaitMinTimeout?"checked":"";return` @@ -444,5 +444,5 @@ Activity: ${s.last_activity||"None"}`,description:s.is_online?"Online":`Last see }); -`}escapeHtml(e){return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}};var Rt=h(require("net"));function We(s){return s.trim().toLowerCase().split("%")[0]}function ws(s){let e=new Set;for(let t of s){let r=We(t);r&&(e.add(r),r.startsWith("::ffff:")&&e.add(r.slice(7)))}return e}function $t(s){try{let e=new URL(s);return(e.hostname==="0.0.0.0"||e.hostname==="::"||e.hostname==="[::]")&&(e.hostname="127.0.0.1"),e.toString()}catch{return s}}function Dt(s,e){try{let t=new URL(s),r=We(t.hostname),o=We(e.localHostName);if(!r)return!1;if(r==="localhost"||r==="127.0.0.1"||r==="::1"||r==="0.0.0.0"||r==="::"||r==="::ffff:127.0.0.1"||r.startsWith("127.")||r===o)return!0;if(!Rt.isIP(r))return!1;let n=ws(e.localIps);return!!(n.has(r)||r.startsWith("::ffff:")&&n.has(r.slice(7)))}catch{return!1}}function Lt(s){if(!s)return"Unknown error";if(s instanceof Error)return s.message;if(typeof s=="object"){let e=s,t=typeof e.message=="string"?e.message:"",r=typeof e.code=="string"?e.code:"",o=typeof e.name=="string"?e.name:"";if(r&&t)return`${r}: ${t}`;if(r)return r;if(o&&t)return`${o}: ${t}`;if(t)return t}return String(s)}var f,ie,Bt,Fe,He,Nt=!1,Oe=!1;function Ss(s){let e=[],t=Te.networkInterfaces();for(let r of Object.values(t))for(let o of r||[]){let n=String(o.address||"").trim().toLowerCase();n&&e.push(n)}return Dt(s,{localHostName:Te.hostname(),localIps:e})}function ys(){return c.lm}async function Cs(s){let e=new Date().toISOString(),t=[],r=ys(),o=typeof r?.selectChatModels=="function",n={apiAvailable:!!r,selectChatModelsAvailable:o,supportedForProactiveInvocation:!1,supportedForCopilotVendor:!1,probeAt:e,models:[],copilotModels:[],notes:t};if(!r)return t.push("vscode.lm is not available in this IDE build."),t.push("This extension cannot proactively invoke an IDE coding agent via runNewCopilotSession."),n;if(!o)return t.push("vscode.lm.selectChatModels is not available."),t.push("This extension cannot probe or invoke IDE chat models in the current environment."),n;try{let i=await r.selectChatModels(),a=s.languageModelAccessInformation;return n.models=i.map(l=>{let d;try{d=a?.canSendRequest(l)}catch{d=void 0}return{name:l.name,id:l.id,vendor:l.vendor,family:l.family,version:l.version,maxInputTokens:l.maxInputTokens,canSendRequest:d===!0?"yes":d===!1?"no":"unknown"}}),n.copilotModels=n.models.filter(l=>l.vendor==="copilot"),n.supportedForProactiveInvocation=n.models.length>0,n.supportedForCopilotVendor=n.copilotModels.length>0,n.models.length===0?(t.push("No chat models were exposed to extensions via vscode.lm."),t.push("This extension cannot proactively invoke an IDE coding agent (runNewCopilotSession) here.")):(t.push("Models were discovered via vscode.lm.selectChatModels()."),t.push("canSendRequest=unknown usually means consent has not been asked yet; invoking will prompt the user."),n.copilotModels.length===0&&t.push("No vendor=copilot models were found. If you expected Copilot, check that it is installed and signed in.")),n}catch(i){let a=Lt(i);return{...n,supportedForProactiveInvocation:!1,supportedForCopilotVendor:!1,notes:[...t,"Language model probing threw an exception.","This extension cannot treat IDE agent invocation as available in the current environment."],error:a}}}function Ps(s){console.log("[AgentChatBus] Activating extension..."),I.setExtensionPath(s.extensionPath),s.subscriptions.push(c.window.registerWebviewPanelSerializer(I.VIEW_TYPE,{async deserializeWebviewPanel(n,i){try{console.log("[AgentChatBus] Deserializing webview panel (v2)..."),I.reviveRecoveredPanel(n)}catch(a){console.error("[AgentChatBus] Failed to deserialize panel (v2):",a),n.dispose()}}}),c.window.registerWebviewPanelSerializer(I.LEGACY_VIEW_TYPE,{async deserializeWebviewPanel(n,i){try{console.log("[AgentChatBus] Deserializing webview panel (legacy)..."),I.reviveRecoveredPanel(n)}catch(a){console.error("[AgentChatBus] Failed to deserialize panel (legacy):",a),n.dispose()}}}));let e=new Se(s),t=e.getWorkspaceDevContext();I.setWorkspaceDevWebUiRoot(t?.webUiRoot),He=e,Fe=new ye;let r=new Ce;ie=new Pe,e.setSetupProvider(r),e.setMcpLogProvider(ie),s.subscriptions.push(e),s.subscriptions.push(r),s.subscriptions.push(ie),s.subscriptions.push(c.window.registerTreeDataProvider("agentchatbus.setup",r),c.window.registerTreeDataProvider("agentchatbus.mcpLogs",ie)),c.commands.executeCommand("setContext","agentchatbus:agentsFilterActive",!0);let o=async()=>{try{console.log("[AgentChatBus] Starting setup process..."),await e.ensureServerRunning()?(console.log("[AgentChatBus] Server is ready, initializing main views."),Fe&&xs(s,e,Fe)):console.warn("[AgentChatBus] Server failed to start.")}catch(n){console.error("[AgentChatBus] Fatal error during setup:",n),e.log(`Fatal error: ${n}`,"error")}};s.subscriptions.push(c.commands.registerCommand("agentchatbus.retrySetup",()=>{console.log("[AgentChatBus] Retry command triggered."),r.reset(),o()})),s.subscriptions.push(c.workspace.onDidChangeConfiguration(n=>{!n.affectsConfiguration("agentchatbus.serverUrl")&&!n.affectsConfiguration("agentchatbus.autoStartBusServer")&&!n.affectsConfiguration("agentchatbus.msgWaitMinTimeoutMs")&&!n.affectsConfiguration("agentchatbus.enforceMsgWaitMinTimeout")||e.notifyMcpDefinitionsChanged()})),e.registerMcpProvider(s),Promise.resolve().then(()=>{setTimeout(()=>{o()},500)})}function xs(s,e,t){if(Nt)return;Nt=!0,console.log("[AgentChatBus] Initializing main views..."),I.setWorkspaceDevWebUiRoot(e.getWorkspaceDevContext()?.webUiRoot),ks(s,e),f=new me,f.connectSSE();let r=new be(f),o=new we(f);Bt=new xe,s.subscriptions.push(c.window.registerTreeDataProvider("agentchatbus.threads",r),c.window.registerTreeDataProvider("agentchatbus.settings",Bt),c.window.registerTreeDataProvider("agentchatbus.agents",o)),s.subscriptions.push(c.commands.registerCommand("agentchatbus.refreshThreads",()=>r.refresh()),c.commands.registerCommand("agentchatbus.refreshAgents",()=>o.refresh()),c.commands.registerCommand("agentchatbus.toggleAgentFilter",()=>o.toggleRecentFilter()),c.commands.registerCommand("agentchatbus.clearMcpLogs",()=>{ie?.clear()}),c.commands.registerCommand("agentchatbus.stopServer",async()=>{let n=e.getRestartActionMessages(),i=e.getStatusMetadata(),a=String(i.startupMode||"").startsWith("external-service");if(await c.window.showWarningMessage(n.mode==="force-restart"&&a?"Force restart the externally managed AgentChatBus service? The extension will try force-shutdown via API, verify process exit, then kill the process if needed before starting a fresh service.":n.confirmMessage,{modal:!0},n.confirmLabel)===n.confirmLabel&&!await e.stopServer()){let v=e.getLastStopFailureMessage();c.window.showWarningMessage(v||(n.mode==="force-restart"&&a?"The external AgentChatBus service did not accept the shutdown request.":n.failureMessage))}}),c.commands.registerCommand("agentchatbus.switchToManagedDevService",async()=>{await c.commands.executeCommand("agentchatbus.stopServer")}),c.commands.registerCommand("agentchatbus.restartManagedDevService",async()=>{await c.commands.executeCommand("agentchatbus.stopServer")}),c.commands.registerCommand("agentchatbus.stopServerPending",()=>{c.window.showInformationMessage(e.getRestartActionMessages().pendingMessage)}),c.commands.registerCommand("agentchatbus.switchToManagedDevServicePending",()=>{c.window.showInformationMessage(e.getRestartActionMessages().pendingMessage)}),c.commands.registerCommand("agentchatbus.restartManagedDevServicePending",()=>{c.window.showInformationMessage(e.getRestartActionMessages().pendingMessage)}),c.commands.registerCommand("agentchatbus.openFullLog",()=>{let n=ie?.getLogs()||[],i=c.window.createWebviewPanel("agentchatbusLogs","AgentChatBus Full Logs",c.ViewColumn.One,{});i.webview.html=`
${n.join(`
-`)}
`}),c.commands.registerCommand("agentchatbus.openWebConsole",()=>{let i=c.workspace.getConfiguration("agentchatbus").get("serverUrl","http://127.0.0.1:39765"),a=$t(i);c.env.openExternal(c.Uri.parse(a))}),c.commands.registerCommand("agentchatbus.serverSettings",()=>{ke.createOrShow()}),c.commands.registerCommand("agentchatbus.filterThreads",async()=>{let n=["discuss","implement","review","done","closed","archived"],i=r.getStatusFilter(),a=n.map(d=>({label:d.charAt(0).toUpperCase()+d.slice(1),status:d,picked:i.includes(d),description:d==="archived"?"(archived threads are hidden by default)":void 0})),l=await c.window.showQuickPick(a,{canPickMany:!0,placeHolder:"Select thread statuses to display",ignoreFocusOut:!0});if(l){let d=l.map(v=>v.status);r.setStatusFilter(d)}}),c.commands.registerCommand("agentchatbus.openThread",n=>{console.log("[AgentChatBus] openThread called, thread:",n?.id,"apiClient:",!!f),n&&f?I.createOrShow(n,f):console.error("[AgentChatBus] openThread: missing thread or apiClient",{thread:n,apiClient:!!f})}),c.commands.registerCommand("agentchatbus.showMcpStatus",async()=>{let n=e.getStatusMetadata(),i=String(n.backendEngine||"unknown").trim().toLowerCase()||"unknown",a=n.backendEngine?"startup-probe":"startup-heuristic",l=String(n.backendVersion||"").trim()||void 0,d=String(n.backendRuntime||"").trim()||void 0,v,P,k=!1,W=String(n?.mcp?.serverUrl||f?.getBaseUrl()||"").trim(),U=Ss(W),ae=U?"local":"remote",g=await Cs(s);try{if(f){try{let $=await f.getHealth();k=!0;let x=String($?.engine||"").trim().toLowerCase();(x==="node"||x==="python")&&(i=x,a="health");let M=String($?.version||"").trim();M&&(l=M);let D=String($?.runtime||"").trim();D&&(d=D)}catch{}let C=await f.getMetrics();k=!0;let b=String(C?.engine||"").trim().toLowerCase();(b==="node"||b==="python")&&(i=b,a="api/metrics");let T=String(C?.started_at||"").trim();T&&(v=T);let R=Number(C?.uptime_seconds);!Number.isNaN(R)&&R>=0&&(P=R)}}catch{}if(i==="unknown"){let C=String(n.command||"").toLowerCase(),b=Array.isArray(n.args)?n.args.map(T=>String(T||"").toLowerCase()).join(" "):"";n.startupMode==="bundled-ts-service"||n.startupMode==="workspace-dev-service"?(i="node",a="startup-mode"):C.includes("python")||b.includes("python")||b.includes("uvicorn")||b.includes("src.main")?(i="python",a="command-heuristic"):(C.includes("node")||b.includes("node"))&&(i="node",a="command-heuristic")}Me.createOrShow({...n,backendEngine:i,backendEngineSource:a,backendVersion:l,backendRuntime:d,backendStartedAt:v,backendUptimeSeconds:P,serverReachable:k,serverScope:ae,lmProbe:g,privacyWarning:U?"":"Remote server detected. Sensitive host/process fields are hidden for safety."})}),c.commands.registerCommand("agentchatbus.configureCursorMcp",async()=>{let i=c.workspace.getConfiguration("agentchatbus").get("serverUrl","http://127.0.0.1:39765"),a=t.getGlobalConfigPath(),d=`${i.replace(/\/+$/,"")}/mcp/sse`;if(await c.window.showInformationMessage(`Update Cursor global MCP config at ${a} to point agentchatbus at ${d}?`,{modal:!0},"Configure Cursor")==="Configure Cursor")try{let P=await t.configureGlobalAgentChatBus(i),k=P.changed?"configured":"already up to date";await c.window.showInformationMessage(`Cursor MCP ${k}: ${P.serverName} -> ${P.serverUrl}`,"Open Config")==="Open Config"&&await t.openGlobalConfig()}catch(P){let k=P instanceof Error?P.message:String(P);await c.window.showErrorMessage(`Failed to configure Cursor MCP: ${k}`,"Open Config")==="Open Config"&&await t.openGlobalConfig()}}),c.commands.registerCommand("agentchatbus.openCursorMcpConfig",async()=>{await t.openGlobalConfig()}),c.commands.registerCommand("agentchatbus.copyThreadId",n=>{n?.thread?.id&&(c.env.clipboard.writeText(n.thread.id),c.window.showInformationMessage(`Copied Thread ID: ${n.thread.id}`))}),c.commands.registerCommand("agentchatbus.deleteThread",async n=>{if(!n?.thread?.id||!f)return;await c.window.showWarningMessage(`Are you sure you want to PERMANENTLY delete thread "${n.thread.topic}"? This cannot be undone.`,{modal:!0},"Delete")==="Delete"&&(await f.deleteThread(n.thread.id)?(c.window.showInformationMessage("Thread deleted."),r.refresh()):c.window.showErrorMessage("Failed to delete thread."))}),c.commands.registerCommand("agentchatbus.archiveThread",async n=>{if(!n?.thread?.id||!f)return;await f.archiveThread(n.thread.id)?r.refresh():c.window.showErrorMessage("Failed to archive thread.")}),c.commands.registerCommand("agentchatbus.unarchiveThread",async n=>{if(!n?.thread?.id||!f)return;await f.unarchiveThread(n.thread.id)?r.refresh():c.window.showErrorMessage("Failed to unarchive thread.")}),c.commands.registerCommand("agentchatbus.changeThreadStatus",async n=>{if(!n?.thread?.id||!f)return;let i=["discuss","implement","review","done","closed"],a=await c.window.showQuickPick(i,{placeHolder:`Change status for "${n.thread.topic}" (current: ${n.thread.status})`});a&&a!==n.thread.status&&(await f.setThreadState(n.thread.id,a)?r.refresh():c.window.showErrorMessage("Failed to change thread status."))})),s.subscriptions.push({dispose:()=>f?.disconnectSSE()})}async function Ms(){He&&await He.handleIdeDeactivate(),f&&f.disconnectSSE()}function ks(s,e){if(Oe)return;let t=e.getWorkspaceDevContext();if(!t)return;Oe=!0;let r=new c.RelativePattern(c.Uri.file(t.webUiRoot),"extension/**/*"),o=c.workspace.createFileSystemWatcher(r),n,i=()=>{I.setWorkspaceDevWebUiRoot(e.getWorkspaceDevContext()?.webUiRoot),n&&clearTimeout(n),n=setTimeout(()=>{I.reloadCurrentPanelForSourceChange()},150)};o.onDidChange(i,void 0,s.subscriptions),o.onDidCreate(i,void 0,s.subscriptions),o.onDidDelete(i,void 0,s.subscriptions),s.subscriptions.push(o),s.subscriptions.push({dispose:()=>{Oe=!1,n&&clearTimeout(n)}})}0&&(module.exports={activate,deactivate}); +`}escapeHtml(e){return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}};var Lt=h(require("net"));function Fe(s){return s.trim().toLowerCase().split("%")[0]}function Ps(s){let e=new Set;for(let t of s){let r=Fe(t);r&&(e.add(r),r.startsWith("::ffff:")&&e.add(r.slice(7)))}return e}function Bt(s){try{let e=new URL(s);return(e.hostname==="0.0.0.0"||e.hostname==="::"||e.hostname==="[::]")&&(e.hostname="127.0.0.1"),e.toString()}catch{return s}}function Nt(s,e){try{let t=new URL(s),r=Fe(t.hostname),o=Fe(e.localHostName);if(!r)return!1;if(r==="localhost"||r==="127.0.0.1"||r==="::1"||r==="0.0.0.0"||r==="::"||r==="::ffff:127.0.0.1"||r.startsWith("127.")||r===o)return!0;if(!Lt.isIP(r))return!1;let n=Ps(e.localIps);return!!(n.has(r)||r.startsWith("::ffff:")&&n.has(r.slice(7)))}catch{return!1}}function Wt(s){if(!s)return"Unknown error";if(s instanceof Error)return s.message;if(typeof s=="object"){let e=s,t=typeof e.message=="string"?e.message:"",r=typeof e.code=="string"?e.code:"",o=typeof e.name=="string"?e.name:"";if(r&&t)return`${r}: ${t}`;if(r)return r;if(o&&t)return`${o}: ${t}`;if(t)return t}return String(s)}var f,ie,Ft,Oe,je,Ot=!1,He=!1;function Ms(s){let e=[],t=Ae.networkInterfaces();for(let r of Object.values(t))for(let o of r||[]){let n=String(o.address||"").trim().toLowerCase();n&&e.push(n)}return Nt(s,{localHostName:Ae.hostname(),localIps:e})}function xs(){return c.lm}async function ks(s){let e=new Date().toISOString(),t=[],r=xs(),o=typeof r?.selectChatModels=="function",n={apiAvailable:!!r,selectChatModelsAvailable:o,supportedForProactiveInvocation:!1,supportedForCopilotVendor:!1,probeAt:e,models:[],copilotModels:[],notes:t};if(!r)return t.push("vscode.lm is not available in this IDE build."),t.push("This extension cannot proactively invoke an IDE coding agent via runNewCopilotSession."),n;if(!o)return t.push("vscode.lm.selectChatModels is not available."),t.push("This extension cannot probe or invoke IDE chat models in the current environment."),n;try{let a=await r.selectChatModels(),i=s.languageModelAccessInformation;return n.models=a.map(d=>{let l;try{l=i?.canSendRequest(d)}catch{l=void 0}return{name:d.name,id:d.id,vendor:d.vendor,family:d.family,version:d.version,maxInputTokens:d.maxInputTokens,canSendRequest:l===!0?"yes":l===!1?"no":"unknown"}}),n.copilotModels=n.models.filter(d=>d.vendor==="copilot"),n.supportedForProactiveInvocation=n.models.length>0,n.supportedForCopilotVendor=n.copilotModels.length>0,n.models.length===0?(t.push("No chat models were exposed to extensions via vscode.lm."),t.push("This extension cannot proactively invoke an IDE coding agent (runNewCopilotSession) here.")):(t.push("Models were discovered via vscode.lm.selectChatModels()."),t.push("canSendRequest=unknown usually means consent has not been asked yet; invoking will prompt the user."),n.copilotModels.length===0&&t.push("No vendor=copilot models were found. If you expected Copilot, check that it is installed and signed in.")),n}catch(a){let i=Wt(a);return{...n,supportedForProactiveInvocation:!1,supportedForCopilotVendor:!1,notes:[...t,"Language model probing threw an exception.","This extension cannot treat IDE agent invocation as available in the current environment."],error:i}}}function Ts(s){console.log("[AgentChatBus] Activating extension..."),I.setExtensionPath(s.extensionPath),s.subscriptions.push(c.window.registerWebviewPanelSerializer(I.VIEW_TYPE,{async deserializeWebviewPanel(n,a){try{console.log("[AgentChatBus] Deserializing webview panel (v2)..."),I.reviveRecoveredPanel(n)}catch(i){console.error("[AgentChatBus] Failed to deserialize panel (v2):",i),n.dispose()}}}),c.window.registerWebviewPanelSerializer(I.LEGACY_VIEW_TYPE,{async deserializeWebviewPanel(n,a){try{console.log("[AgentChatBus] Deserializing webview panel (legacy)..."),I.reviveRecoveredPanel(n)}catch(i){console.error("[AgentChatBus] Failed to deserialize panel (legacy):",i),n.dispose()}}}));let e=new ye(s),t=e.getWorkspaceDevContext();I.setWorkspaceDevWebUiRoot(t?.webUiRoot),je=e,Oe=new Ce;let r=new Pe;ie=new Me,e.setSetupProvider(r),e.setMcpLogProvider(ie),s.subscriptions.push(e),s.subscriptions.push(r),s.subscriptions.push(ie),s.subscriptions.push(c.window.registerTreeDataProvider("agentchatbus.setup",r),c.window.registerTreeDataProvider("agentchatbus.mcpLogs",ie)),c.commands.executeCommand("setContext","agentchatbus:agentsFilterActive",!0);let o=async()=>{try{console.log("[AgentChatBus] Starting setup process..."),await e.ensureServerRunning()?(console.log("[AgentChatBus] Server is ready, initializing main views."),Oe&&As(s,e,Oe)):console.warn("[AgentChatBus] Server failed to start.")}catch(n){console.error("[AgentChatBus] Fatal error during setup:",n),e.log(`Fatal error: ${n}`,"error")}};s.subscriptions.push(c.commands.registerCommand("agentchatbus.retrySetup",()=>{console.log("[AgentChatBus] Retry command triggered."),r.reset(),o()})),s.subscriptions.push(c.workspace.onDidChangeConfiguration(n=>{!n.affectsConfiguration("agentchatbus.serverUrl")&&!n.affectsConfiguration("agentchatbus.autoStartBusServer")&&!n.affectsConfiguration("agentchatbus.msgWaitMinTimeoutMs")&&!n.affectsConfiguration("agentchatbus.enforceMsgWaitMinTimeout")||e.notifyMcpDefinitionsChanged()})),e.registerMcpProvider(s),Promise.resolve().then(()=>{setTimeout(()=>{o()},500)})}function As(s,e,t){if(Ot)return;Ot=!0,console.log("[AgentChatBus] Initializing main views..."),I.setWorkspaceDevWebUiRoot(e.getWorkspaceDevContext()?.webUiRoot),Is(s,e),f=new me,f.connectSSE();let r=new be(f),o=new we(f);Ft=new xe,s.subscriptions.push(c.window.registerTreeDataProvider("agentchatbus.threads",r),c.window.registerTreeDataProvider("agentchatbus.settings",Ft),c.window.registerTreeDataProvider("agentchatbus.agents",o)),s.subscriptions.push(c.commands.registerCommand("agentchatbus.refreshThreads",()=>r.refresh()),c.commands.registerCommand("agentchatbus.refreshAgents",()=>o.refresh()),c.commands.registerCommand("agentchatbus.toggleAgentFilter",()=>o.toggleRecentFilter()),c.commands.registerCommand("agentchatbus.clearMcpLogs",()=>{ie?.clear()}),c.commands.registerCommand("agentchatbus.stopServer",async()=>{let n=e.getRestartActionMessages(),a=e.getStatusMetadata(),i=String(a.startupMode||"").startsWith("external-service");if(await c.window.showWarningMessage(n.mode==="force-restart"&&i?"Force restart the externally managed AgentChatBus service? The extension will try force-shutdown via API, verify process exit, then kill the process if needed before starting a fresh service.":n.confirmMessage,{modal:!0},n.confirmLabel)===n.confirmLabel&&!await e.stopServer()){let v=e.getLastStopFailureMessage();c.window.showWarningMessage(v||(n.mode==="force-restart"&&i?"The external AgentChatBus service did not accept the shutdown request.":n.failureMessage))}}),c.commands.registerCommand("agentchatbus.switchToManagedDevService",async()=>{await c.commands.executeCommand("agentchatbus.stopServer")}),c.commands.registerCommand("agentchatbus.restartManagedDevService",async()=>{await c.commands.executeCommand("agentchatbus.stopServer")}),c.commands.registerCommand("agentchatbus.stopServerPending",()=>{c.window.showInformationMessage(e.getRestartActionMessages().pendingMessage)}),c.commands.registerCommand("agentchatbus.switchToManagedDevServicePending",()=>{c.window.showInformationMessage(e.getRestartActionMessages().pendingMessage)}),c.commands.registerCommand("agentchatbus.restartManagedDevServicePending",()=>{c.window.showInformationMessage(e.getRestartActionMessages().pendingMessage)}),c.commands.registerCommand("agentchatbus.openFullLog",()=>{let n=ie?.getLogs()||[],a=c.window.createWebviewPanel("agentchatbusLogs","AgentChatBus Full Logs",c.ViewColumn.One,{});a.webview.html=`
${n.join(`
+`)}
`}),c.commands.registerCommand("agentchatbus.openWebConsole",()=>{let a=c.workspace.getConfiguration("agentchatbus").get("serverUrl","http://127.0.0.1:39765"),i=Bt(a);c.env.openExternal(c.Uri.parse(i))}),c.commands.registerCommand("agentchatbus.serverSettings",()=>{Te.createOrShow()}),c.commands.registerCommand("agentchatbus.filterThreads",async()=>{let n=["discuss","implement","review","done","closed","archived"],a=r.getStatusFilter(),i=n.map(l=>({label:l.charAt(0).toUpperCase()+l.slice(1),status:l,picked:a.includes(l),description:l==="archived"?"(archived threads are hidden by default)":void 0})),d=await c.window.showQuickPick(i,{canPickMany:!0,placeHolder:"Select thread statuses to display",ignoreFocusOut:!0});if(d){let l=d.map(v=>v.status);r.setStatusFilter(l)}}),c.commands.registerCommand("agentchatbus.openThread",n=>{console.log("[AgentChatBus] openThread called, thread:",n?.id,"apiClient:",!!f),n&&f?I.createOrShow(n,f):console.error("[AgentChatBus] openThread: missing thread or apiClient",{thread:n,apiClient:!!f})}),c.commands.registerCommand("agentchatbus.showMcpStatus",async()=>{let n=e.getStatusMetadata(),a=String(n.backendEngine||"unknown").trim().toLowerCase()||"unknown",i=n.backendEngine?"startup-probe":"startup-heuristic",d=String(n.backendVersion||"").trim()||void 0,l=String(n.backendRuntime||"").trim()||void 0,v,P,k=!1,W=String(n?.mcp?.serverUrl||f?.getBaseUrl()||"").trim(),R=Ms(W),ce=R?"local":"remote",g=await ks(s);try{if(f){try{let $=await f.getHealth();k=!0;let M=String($?.engine||"").trim().toLowerCase();(M==="node"||M==="python")&&(a=M,i="health");let x=String($?.version||"").trim();x&&(d=x);let D=String($?.runtime||"").trim();D&&(l=D)}catch{}let C=await f.getMetrics();k=!0;let b=String(C?.engine||"").trim().toLowerCase();(b==="node"||b==="python")&&(a=b,i="api/metrics");let T=String(C?.started_at||"").trim();T&&(v=T);let U=Number(C?.uptime_seconds);!Number.isNaN(U)&&U>=0&&(P=U)}}catch{}if(a==="unknown"){let C=String(n.command||"").toLowerCase(),b=Array.isArray(n.args)?n.args.map(T=>String(T||"").toLowerCase()).join(" "):"";n.startupMode==="bundled-ts-service"||n.startupMode==="workspace-dev-service"?(a="node",i="startup-mode"):C.includes("python")||b.includes("python")||b.includes("uvicorn")||b.includes("src.main")?(a="python",i="command-heuristic"):(C.includes("node")||b.includes("node"))&&(a="node",i="command-heuristic")}ke.createOrShow({...n,backendEngine:a,backendEngineSource:i,backendVersion:d,backendRuntime:l,backendStartedAt:v,backendUptimeSeconds:P,serverReachable:k,serverScope:ce,lmProbe:g,privacyWarning:R?"":"Remote server detected. Sensitive host/process fields are hidden for safety."})}),c.commands.registerCommand("agentchatbus.configureCursorMcp",async()=>{let a=c.workspace.getConfiguration("agentchatbus").get("serverUrl","http://127.0.0.1:39765"),i=t.getGlobalConfigPath(),l=`${a.replace(/\/+$/,"")}/mcp/sse`;if(await c.window.showInformationMessage(`Update Cursor global MCP config at ${i} to point agentchatbus at ${l}?`,{modal:!0},"Configure Cursor")==="Configure Cursor")try{let P=await t.configureGlobalAgentChatBus(a),k=P.changed?"configured":"already up to date";await c.window.showInformationMessage(`Cursor MCP ${k}: ${P.serverName} -> ${P.serverUrl}`,"Open Config")==="Open Config"&&await t.openGlobalConfig()}catch(P){let k=P instanceof Error?P.message:String(P);await c.window.showErrorMessage(`Failed to configure Cursor MCP: ${k}`,"Open Config")==="Open Config"&&await t.openGlobalConfig()}}),c.commands.registerCommand("agentchatbus.openCursorMcpConfig",async()=>{await t.openGlobalConfig()}),c.commands.registerCommand("agentchatbus.copyThreadId",n=>{n?.thread?.id&&(c.env.clipboard.writeText(n.thread.id),c.window.showInformationMessage(`Copied Thread ID: ${n.thread.id}`))}),c.commands.registerCommand("agentchatbus.deleteThread",async n=>{if(!n?.thread?.id||!f)return;await c.window.showWarningMessage(`Are you sure you want to PERMANENTLY delete thread "${n.thread.topic}"? This cannot be undone.`,{modal:!0},"Delete")==="Delete"&&(await f.deleteThread(n.thread.id)?(c.window.showInformationMessage("Thread deleted."),r.refresh()):c.window.showErrorMessage("Failed to delete thread."))}),c.commands.registerCommand("agentchatbus.archiveThread",async n=>{if(!n?.thread?.id||!f)return;await f.archiveThread(n.thread.id)?r.refresh():c.window.showErrorMessage("Failed to archive thread.")}),c.commands.registerCommand("agentchatbus.unarchiveThread",async n=>{if(!n?.thread?.id||!f)return;await f.unarchiveThread(n.thread.id)?r.refresh():c.window.showErrorMessage("Failed to unarchive thread.")}),c.commands.registerCommand("agentchatbus.changeThreadStatus",async n=>{if(!n?.thread?.id||!f)return;let a=["discuss","implement","review","done","closed"],i=await c.window.showQuickPick(a,{placeHolder:`Change status for "${n.thread.topic}" (current: ${n.thread.status})`});i&&i!==n.thread.status&&(await f.setThreadState(n.thread.id,i)?r.refresh():c.window.showErrorMessage("Failed to change thread status."))})),s.subscriptions.push({dispose:()=>f?.disconnectSSE()})}async function Es(){je&&await je.handleIdeDeactivate(),f&&f.disconnectSSE()}function Is(s,e){if(He)return;let t=e.getWorkspaceDevContext();if(!t)return;He=!0;let r=new c.RelativePattern(c.Uri.file(t.webUiRoot),"extension/**/*"),o=c.workspace.createFileSystemWatcher(r),n,a=()=>{I.setWorkspaceDevWebUiRoot(e.getWorkspaceDevContext()?.webUiRoot),n&&clearTimeout(n),n=setTimeout(()=>{I.reloadCurrentPanelForSourceChange()},150)};o.onDidChange(a,void 0,s.subscriptions),o.onDidCreate(a,void 0,s.subscriptions),o.onDidDelete(a,void 0,s.subscriptions),s.subscriptions.push(o),s.subscriptions.push({dispose:()=>{He=!1,n&&clearTimeout(n)}})}0&&(module.exports={activate,deactivate}); diff --git a/vscode-agentchatbus/out/logic/testExports.js b/vscode-agentchatbus/out/logic/testExports.js index 73d4c6f..c7def40 100644 --- a/vscode-agentchatbus/out/logic/testExports.js +++ b/vscode-agentchatbus/out/logic/testExports.js @@ -1,5 +1,5 @@ -"use strict";var ie=Object.create;var g=Object.defineProperty;var ae=Object.getOwnPropertyDescriptor;var ce=Object.getOwnPropertyNames;var le=Object.getPrototypeOf,ue=Object.prototype.hasOwnProperty;var ge=(e,t)=>{for(var r in t)g(e,r,{get:t[r],enumerable:!0})},T=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of ce(t))!ue.call(e,o)&&o!==r&&g(e,o,{get:()=>t[o],enumerable:!(n=ae(t,o))||n.enumerable});return e};var d=(e,t,r)=>(r=e!=null?ie(le(e)):{},T(t||!e||!e.__esModule?g(r,"default",{value:e,enumerable:!0}):r,e)),de=e=>T(g({},"__esModule",{value:!0}),e);var he={};ge(he,{BUNDLED_RUNTIME_RESOLVED_BY:()=>v,MIN_HOST_NODE_VERSION:()=>x,WORKSPACE_DEV_RUNTIME_RESOLVED_BY:()=>_,appendLogLines:()=>Q,appendSetupLogStep:()=>ne,applyServerUrlChange:()=>I,buildAgentItemViewModel:()=>B,buildBundledLaunchSpec:()=>K,buildCursorMcpConfig:()=>j,buildEventsUrl:()=>R,buildSendMessageRequestBody:()=>w,buildThreadItemViewModel:()=>H,buildWorkspaceDevLaunchSpec:()=>Y,classifyDetectedStartupMode:()=>q,classifyExternalStartupMode:()=>M,createInitialSetupSteps:()=>te,ensureSupportedHostNodeVersion:()=>J,extractOwnershipAssignable:()=>A,filterAndSortAgents:()=>D,filterAndSortThreads:()=>L,formatLmError:()=>z,formatSetupStepLabel:()=>oe,getAgentActivityTimestamp:()=>l,getBrowserOpenUrl:()=>G,getCursorMcpUrl:()=>S,getMcpLogPresentation:()=>y,getMcpLogRows:()=>X,getRelativeTimeString:()=>f,getSettingsDefinitions:()=>$,getThreadStatusIconFileName:()=>m,isLocalServerUrlWithContext:()=>V,normalizeHealthString:()=>E,normalizeSendMessagePayload:()=>p,normalizeServerUrl:()=>h,parseSseEventData:()=>N,parseUiAgentRegistrationPayload:()=>U,replaceSetupSteps:()=>re,resolveWorkspaceDevContext:()=>ee,shouldIncludeArchivedThreadStatus:()=>O,shouldRefreshAgentsForEventType:()=>k,shouldRefreshThreadsForEventType:()=>W,shouldRetrySendMessage:()=>C});module.exports=de(he);function p(e){return typeof e=="string"?{content:e}:e}function w(e,t){let r=p(e);return{author:r.author||"human",content:r.content,mentions:r.mentions,metadata:r.metadata,images:r.images,reply_to_msg_id:r.reply_to_msg_id,expected_last_seq:t.current_seq,reply_token:t.reply_token}}function C(e,t){return e!==409||!t||typeof t!="object"?!1:t.action==="READ_MESSAGES_THEN_CALL_MSG_WAIT"}function I(e,t,r){let n=t!==e;return{baseUrl:t,uiAgentAuth:n?null:r,shouldReconnectSse:n}}function U(e){if(!e||typeof e!="object")throw new Error("Invalid UI agent registration payload");let t=e;if(!t.agent_id||!t.token)throw new Error("Invalid UI agent registration payload");return{agent_id:String(t.agent_id),token:String(t.token)}}function R(e){return`${e}/events`}function N(e){try{return{ok:!0,data:JSON.parse(e)}}catch(t){return{ok:!1,error:t}}}function pe(e){let t=Date.parse(String(e||""));return Number.isFinite(t)?t:0}function l(e){return pe(e.last_activity_time||e.last_heartbeat)}function D(e,t){let r=t?.showOnlyRecent??!0,o=(t?.nowMs??Date.now())-3600*1e3;return(r?e.filter(i=>l(i)>o||i.is_online):e.slice()).sort((i,c)=>i.is_online!==c.is_online?i.is_online?-1:1:l(c)-l(i))}function f(e,t=Date.now()){let r=Math.round((t-e.getTime())/1e3);return r<60?"just now":r<3600?`${Math.floor(r/60)}m ago`:r<86400?`${Math.floor(r/3600)}h ago`:`${Math.floor(r/86400)}d ago`}function k(e){return!!(e&&(e.startsWith("agent.")||e==="msg.new"))}function B(e,t){let r=e.display_name||e.name||e.id,n=e.last_activity_time||e.last_heartbeat,o=n?f(new Date(n),t?.nowMs):"Never";return{label:r,tooltip:`IDE: ${e.ide||"N/A"} +"use strict";var le=Object.create;var d=Object.defineProperty;var ue=Object.getOwnPropertyDescriptor;var de=Object.getOwnPropertyNames;var ge=Object.getPrototypeOf,pe=Object.prototype.hasOwnProperty;var fe=(e,t)=>{for(var n in t)d(e,n,{get:t[n],enumerable:!0})},w=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of de(t))!pe.call(e,o)&&o!==n&&d(e,o,{get:()=>t[o],enumerable:!(r=ue(t,o))||r.enumerable});return e};var g=(e,t,n)=>(n=e!=null?le(ge(e)):{},w(t||!e||!e.__esModule?d(n,"default",{value:e,enumerable:!0}):n,e)),me=e=>w(d({},"__esModule",{value:!0}),e);var ve={};fe(ve,{BUNDLED_RUNTIME_RESOLVED_BY:()=>y,MIN_HOST_NODE_VERSION:()=>v,WORKSPACE_DEV_RUNTIME_RESOLVED_BY:()=>_,appendLogLines:()=>ee,appendSetupLogStep:()=>se,applyServerUrlChange:()=>P,buildAgentItemViewModel:()=>B,buildBundledLaunchSpec:()=>X,buildCursorMcpConfig:()=>j,buildEventsUrl:()=>U,buildSendMessageRequestBody:()=>C,buildThreadItemViewModel:()=>$,buildWorkspaceDevLaunchSpec:()=>Z,classifyDetectedStartupMode:()=>J,classifyExternalStartupMode:()=>A,createInitialSetupSteps:()=>oe,createSingleFlightRunner:()=>Q,describeStartupProbeFailure:()=>p,ensureSupportedHostNodeVersion:()=>K,extractOwnershipAssignable:()=>M,filterAndSortAgents:()=>k,filterAndSortThreads:()=>O,formatLmError:()=>q,formatSetupStepLabel:()=>ae,getAgentActivityTimestamp:()=>l,getBrowserOpenUrl:()=>V,getCursorMcpUrl:()=>b,getMcpLogPresentation:()=>T,getMcpLogRows:()=>te,getRelativeTimeString:()=>m,getSettingsDefinitions:()=>F,getThreadStatusIconFileName:()=>h,isLocalServerUrlWithContext:()=>z,normalizeHealthString:()=>E,normalizeSendMessagePayload:()=>f,normalizeServerUrl:()=>S,parseSseEventData:()=>N,parseUiAgentRegistrationPayload:()=>R,replaceSetupSteps:()=>ie,resolveStartupProbeResult:()=>Y,resolveWorkspaceDevContext:()=>re,shouldIncludeArchivedThreadStatus:()=>H,shouldRefreshAgentsForEventType:()=>D,shouldRefreshThreadsForEventType:()=>W,shouldRetrySendMessage:()=>I});module.exports=me(ve);function f(e){return typeof e=="string"?{content:e}:e}function C(e,t){let n=f(e);return{author:n.author||"human",content:n.content,mentions:n.mentions,metadata:n.metadata,images:n.images,reply_to_msg_id:n.reply_to_msg_id,expected_last_seq:t.current_seq,reply_token:t.reply_token}}function I(e,t){return e!==409||!t||typeof t!="object"?!1:t.action==="READ_MESSAGES_THEN_CALL_MSG_WAIT"}function P(e,t,n){let r=t!==e;return{baseUrl:t,uiAgentAuth:r?null:n,shouldReconnectSse:r}}function R(e){if(!e||typeof e!="object")throw new Error("Invalid UI agent registration payload");let t=e;if(!t.agent_id||!t.token)throw new Error("Invalid UI agent registration payload");return{agent_id:String(t.agent_id),token:String(t.token)}}function U(e){return`${e}/events`}function N(e){try{return{ok:!0,data:JSON.parse(e)}}catch(t){return{ok:!1,error:t}}}function he(e){let t=Date.parse(String(e||""));return Number.isFinite(t)?t:0}function l(e){return he(e.last_activity_time||e.last_heartbeat)}function k(e,t){let n=t?.showOnlyRecent??!0,o=(t?.nowMs??Date.now())-3600*1e3;return(n?e.filter(i=>l(i)>o||i.is_online):e.slice()).sort((i,c)=>i.is_online!==c.is_online?i.is_online?-1:1:l(c)-l(i))}function m(e,t=Date.now()){let n=Math.round((t-e.getTime())/1e3);return n<60?"just now":n<3600?`${Math.floor(n/60)}m ago`:n<86400?`${Math.floor(n/3600)}h ago`:`${Math.floor(n/86400)}d ago`}function D(e){return!!(e&&(e.startsWith("agent.")||e==="msg.new"))}function B(e,t){let n=e.display_name||e.name||e.id,r=e.last_activity_time||e.last_heartbeat,o=r?m(new Date(r),t?.nowMs):"Never";return{label:n,tooltip:`IDE: ${e.ide||"N/A"} Model: ${e.model||"N/A"} Last Seen: ${o} -Activity: ${e.last_activity||"None"}`,description:e.is_online?"Online":`Last seen ${o}`,iconId:e.is_online?"circle-filled":"circle-outline",colorId:e.is_online?"testing.iconPassed":"testing.iconUntested",contextValue:"agent"}}function P(e){let t=Date.parse(String(e||""));return Number.isFinite(t)?t:0}function L(e,t){let r=new Set(t);return e.filter(n=>r.has(n.status)).sort((n,o)=>P(o.created_at)-P(n.created_at))}function m(e){switch(e){case"implement":return"thread-implement.svg";case"review":return"thread-review.svg";case"done":return"thread-done.svg";case"closed":return"thread-closed.svg";case"archived":return"thread-archived.svg";default:return"thread-discuss.svg"}}function W(e){return!!(e&&(e.startsWith("thread.")||e==="msg.new"))}function O(e){return new Set(e).has("archived")}function H(e){return{label:e.topic||"Untitled Thread",tooltip:`ID: ${e.id} -Status: ${e.status}`,description:e.status,iconFile:m(e.status),contextValue:`thread:${e.status}`,commandId:"agentchatbus.openThread",commandArguments:[e]}}function h(e){return String(e||"").replace(/\/+$/,"")}function S(e){return`${h(e)}/mcp/sse`}function j(e,t,r="agentchatbus"){let n=S(t),o={...e,mcpServers:{...e.mcpServers||{},[r]:{url:n,type:"sse"}}};return{nextConfig:o,changed:JSON.stringify(e)!==JSON.stringify(o),serverName:r,serverUrl:n}}function $(){return[{label:"MCP Integration Status",tooltip:"Inspect MCP provider registration, transport, and target endpoint",iconFile:"mgmt-mcp-status.svg",commandId:"agentchatbus.showMcpStatus"},{label:"Configure Cursor MCP",tooltip:"Update Cursor's global mcp.json with an AgentChatBus SSE entry",iconFile:"mgmt-cursor-configure.svg",commandId:"agentchatbus.configureCursorMcp"},{label:"Open Cursor MCP Config",tooltip:"Open Cursor's global mcp.json for inspection",iconFile:"mgmt-cursor-open.svg",commandId:"agentchatbus.openCursorMcpConfig"},{label:"Open Web Console",tooltip:"Open the AgentChatBus dashboard in your browser",iconFile:"mgmt-web-console.svg",commandId:"agentchatbus.openWebConsole"},{label:"Server Settings",tooltip:"Configure AgentChatBus server parameters",iconFile:"mgmt-server-settings.svg",commandId:"agentchatbus.serverSettings"}]}var F=d(require("net"));function b(e){return e.trim().toLowerCase().split("%")[0]}function fe(e){let t=new Set;for(let r of e){let n=b(r);n&&(t.add(n),n.startsWith("::ffff:")&&t.add(n.slice(7)))}return t}function G(e){try{let t=new URL(e);return(t.hostname==="0.0.0.0"||t.hostname==="::"||t.hostname==="[::]")&&(t.hostname="127.0.0.1"),t.toString()}catch{return e}}function V(e,t){try{let r=new URL(e),n=b(r.hostname),o=b(t.localHostName);if(!n)return!1;if(n==="localhost"||n==="127.0.0.1"||n==="::1"||n==="0.0.0.0"||n==="::"||n==="::ffff:127.0.0.1"||n.startsWith("127.")||n===o)return!0;if(!F.isIP(n))return!1;let a=fe(t.localIps);return!!(a.has(n)||n.startsWith("::ffff:")&&a.has(n.slice(7)))}catch{return!1}}function z(e){if(!e)return"Unknown error";if(e instanceof Error)return e.message;if(typeof e=="object"){let t=e,r=typeof t.message=="string"?t.message:"",n=typeof t.code=="string"?t.code:"",o=typeof t.name=="string"?t.name:"";if(n&&r)return`${n}: ${r}`;if(n)return n;if(o&&r)return`${o}: ${r}`;if(r)return r}return String(e)}var u=d(require("path")),x={major:20,minor:0,patch:0},v="Bundled agentchatbus-ts runtime packaged with the VS Code extension.",_="Workspace-dev agentchatbus-ts runtime and local web-ui sources from the current AgentChatBus repo.";function E(e){if(typeof e!="string")return;let t=e.trim();return t.length>0?t:void 0}function A(e){return!e?.management||typeof e.management.ownership_assignable!="boolean"?null:e.management.ownership_assignable}function M(e){let t=A(e);return t===!0?"external-service-extension-managed":t===!1?"external-service-manual":"external-service-unknown"}function q(e){let t=E(e?.startup_mode)?.toLowerCase();return t==="bundled-ts-service"?"bundled-ts-service":t==="workspace-dev-service"?"workspace-dev-service":t==="external-service-extension-managed"?"external-service-extension-managed":t==="external-service-manual"?"external-service-manual":t==="external-service-unknown"?"external-service-unknown":t==="external-service"?"external-service":M(e)}function J(e,t=x){let r=/^v?(\d+)\.(\d+)\.(\d+)$/.exec(e.trim());if(!r)return{ok:!1,message:`Unable to parse IDE host Node version '${e}'. Bundled MCP requires Node ${t.major}.${t.minor}.${t.patch}+ from the IDE host runtime.`};let[,n,o,a]=r,i=Number(n),c=Number(o),se=Number(a);return i>t.major||i===t.major&&c>t.minor||i===t.major&&c===t.minor&&se>=t.patch?{ok:!0,message:`IDE host Node version ${e} satisfies bundled MCP requirement ${t.major}.${t.minor}.${t.patch}+ .`}:{ok:!1,message:`IDE host Node version ${e} is too old for bundled MCP. AgentChatBus requires the IDE host runtime to provide Node ${t.major}.${t.minor}.${t.patch}+ .`}}function K(e){let t=new URL(e.serverUrl),r=Number(t.port||(t.protocol==="https:"?"443":"80")),n=u.join(e.globalStoragePath,"bus-ts.db"),o=u.join(e.globalStoragePath,"config.json");return{command:e.hostNodeExecutable,args:[e.serverEntry,"serve"],cwd:e.extensionRoot,env:{...e.processEnv||{},AGENTCHATBUS_HOST:t.hostname,AGENTCHATBUS_PORT:String(r),AGENTCHATBUS_DB:n,AGENTCHATBUS_APP_DIR:e.globalStoragePath,AGENTCHATBUS_CONFIG_FILE:o,AGENTCHATBUS_WEB_UI_DIR:e.webUiDir,...e.cliWorkspacePath?{AGENTCHATBUS_CLI_WORKSPACE:e.cliWorkspacePath}:{},AGENTCHATBUS_WAIT_MIN_TIMEOUT_MS:String(e.msgWaitMinTimeoutMs),AGENTCHATBUS_ENFORCE_MSG_WAIT_MIN_TIMEOUT:e.enforceMsgWaitMinTimeout?"1":"0"},launchMode:"bundled-ts-service",resolvedBy:v}}function Y(e){let t=new URL(e.serverUrl),r=Number(t.port||(t.protocol==="https:"?"443":"80")),n=u.join(e.globalStoragePath,"bus-ts.db"),o=u.join(e.globalStoragePath,"config.json");return{command:e.hostNodeExecutable,args:[e.tsxCliEntrypoint,"watch","src/cli/index.ts","serve"],cwd:e.tsServerRoot,env:{...e.processEnv||{},AGENTCHATBUS_HOST:t.hostname,AGENTCHATBUS_PORT:String(r),AGENTCHATBUS_DB:n,AGENTCHATBUS_APP_DIR:e.globalStoragePath,AGENTCHATBUS_CONFIG_FILE:o,AGENTCHATBUS_WEB_UI_DIR:e.webUiDir,...e.cliWorkspacePath?{AGENTCHATBUS_CLI_WORKSPACE:e.cliWorkspacePath}:{},AGENTCHATBUS_WAIT_MIN_TIMEOUT_MS:String(e.msgWaitMinTimeoutMs),AGENTCHATBUS_ENFORCE_MSG_WAIT_MIN_TIMEOUT:e.enforceMsgWaitMinTimeout?"1":"0",AGENTCHATBUS_RELOAD:"1",AGENTCHATBUS_WORKSPACE_DEV:"1"},launchMode:"workspace-dev-service",resolvedBy:_}}function Q(e,t,r=500){let n=[...e],o=t.split(/\r?\n/).filter(a=>a.trim().length>0);for(let a of o)n.push(a),n.length>r&&n.shift();return n}function X(e,t,r){return!t&&e.length===0?[{message:r||"Ready (Managed Externally)",index:-1,description:"Extension is reading logs from the shared AgentChatBus log API.",iconId:"info",colorId:"descriptionForeground"}]:e.length===0?[{message:"Waiting for logs...",index:-2,iconId:"sync~spin"}]:e.map((n,o)=>({message:n,index:o,...y(n,o)}))}function y(e,t){return t===-1?{description:"Extension is reading logs from the shared AgentChatBus log API.",iconId:"info",colorId:"descriptionForeground"}:t===-2?{iconId:"sync~spin"}:e.includes("ERROR")||e.includes("Exception")||e.includes("failed")?{iconId:"error",colorId:"errorForeground"}:e.includes("WARNING")?{iconId:"warning",colorId:"problemsWarningIcon.foreground"}:e.includes("Exec:")||e.includes("Starting")?{iconId:"terminal"}:{}}var Z=d(require("fs")),s=d(require("path"));function me(e,t){return[s.join(e,"agentchatbus-ts","package.json"),s.join(e,"agentchatbus-ts","src","cli","index.ts"),s.join(e,"agentchatbus-ts","node_modules","tsx","dist","cli.mjs"),s.join(e,"web-ui","index.html"),s.join(e,"web-ui","extension","index.html"),s.join(e,"vscode-agentchatbus","package.json")].every(n=>t(n))}function ee(e,t=Z.existsSync){for(let r of e){let n=s.resolve(String(r||"").trim());if(n&&me(n,t))return{repoRoot:n,tsServerRoot:s.join(n,"agentchatbus-ts"),tsxCliEntrypoint:s.join(n,"agentchatbus-ts","node_modules","tsx","dist","cli.mjs"),webUiRoot:s.join(n,"web-ui"),webUiExtensionRoot:s.join(n,"web-ui","extension"),vscodeExtensionRoot:s.join(n,"vscode-agentchatbus")}}return null}function te(){return[{label:"Starting AgentChatBus...",icon:"play"}]}function ne(e,t,r,n){return[...e,{label:t,icon:r,description:n}]}function re(e){return e.map(t=>({label:t.label,...t.icon!==void 0?{icon:t.icon}:{},...t.description!==void 0?{description:t.description}:{}}))}function oe(e,t,r=Date.now()){return`[${((r-t)/1e3).toFixed(1)}s] ${e}`}0&&(module.exports={BUNDLED_RUNTIME_RESOLVED_BY,MIN_HOST_NODE_VERSION,WORKSPACE_DEV_RUNTIME_RESOLVED_BY,appendLogLines,appendSetupLogStep,applyServerUrlChange,buildAgentItemViewModel,buildBundledLaunchSpec,buildCursorMcpConfig,buildEventsUrl,buildSendMessageRequestBody,buildThreadItemViewModel,buildWorkspaceDevLaunchSpec,classifyDetectedStartupMode,classifyExternalStartupMode,createInitialSetupSteps,ensureSupportedHostNodeVersion,extractOwnershipAssignable,filterAndSortAgents,filterAndSortThreads,formatLmError,formatSetupStepLabel,getAgentActivityTimestamp,getBrowserOpenUrl,getCursorMcpUrl,getMcpLogPresentation,getMcpLogRows,getRelativeTimeString,getSettingsDefinitions,getThreadStatusIconFileName,isLocalServerUrlWithContext,normalizeHealthString,normalizeSendMessagePayload,normalizeServerUrl,parseSseEventData,parseUiAgentRegistrationPayload,replaceSetupSteps,resolveWorkspaceDevContext,shouldIncludeArchivedThreadStatus,shouldRefreshAgentsForEventType,shouldRefreshThreadsForEventType,shouldRetrySendMessage}); +Activity: ${e.last_activity||"None"}`,description:e.is_online?"Online":`Last seen ${o}`,iconId:e.is_online?"circle-filled":"circle-outline",colorId:e.is_online?"testing.iconPassed":"testing.iconUntested",contextValue:"agent"}}function L(e){let t=Date.parse(String(e||""));return Number.isFinite(t)?t:0}function O(e,t){let n=new Set(t);return e.filter(r=>n.has(r.status)).sort((r,o)=>L(o.created_at)-L(r.created_at))}function h(e){switch(e){case"implement":return"thread-implement.svg";case"review":return"thread-review.svg";case"done":return"thread-done.svg";case"closed":return"thread-closed.svg";case"archived":return"thread-archived.svg";default:return"thread-discuss.svg"}}function W(e){return!!(e&&(e.startsWith("thread.")||e==="msg.new"))}function H(e){return new Set(e).has("archived")}function $(e){return{label:e.topic||"Untitled Thread",tooltip:`ID: ${e.id} +Status: ${e.status}`,description:e.status,iconFile:h(e.status),contextValue:`thread:${e.status}`,commandId:"agentchatbus.openThread",commandArguments:[e]}}function S(e){return String(e||"").replace(/\/+$/,"")}function b(e){return`${S(e)}/mcp/sse`}function j(e,t,n="agentchatbus"){let r=b(t),o={...e,mcpServers:{...e.mcpServers||{},[n]:{url:r,type:"sse"}}};return{nextConfig:o,changed:JSON.stringify(e)!==JSON.stringify(o),serverName:n,serverUrl:r}}function F(){return[{label:"MCP Integration Status",tooltip:"Inspect MCP provider registration, transport, and target endpoint",iconFile:"mgmt-mcp-status.svg",commandId:"agentchatbus.showMcpStatus"},{label:"Configure Cursor MCP",tooltip:"Update Cursor's global mcp.json with an AgentChatBus SSE entry",iconFile:"mgmt-cursor-configure.svg",commandId:"agentchatbus.configureCursorMcp"},{label:"Open Cursor MCP Config",tooltip:"Open Cursor's global mcp.json for inspection",iconFile:"mgmt-cursor-open.svg",commandId:"agentchatbus.openCursorMcpConfig"},{label:"Open Web Console",tooltip:"Open the AgentChatBus dashboard in your browser",iconFile:"mgmt-web-console.svg",commandId:"agentchatbus.openWebConsole"},{label:"Server Settings",tooltip:"Configure AgentChatBus server parameters",iconFile:"mgmt-server-settings.svg",commandId:"agentchatbus.serverSettings"}]}var G=g(require("net"));function x(e){return e.trim().toLowerCase().split("%")[0]}function Se(e){let t=new Set;for(let n of e){let r=x(n);r&&(t.add(r),r.startsWith("::ffff:")&&t.add(r.slice(7)))}return t}function V(e){try{let t=new URL(e);return(t.hostname==="0.0.0.0"||t.hostname==="::"||t.hostname==="[::]")&&(t.hostname="127.0.0.1"),t.toString()}catch{return e}}function z(e,t){try{let n=new URL(e),r=x(n.hostname),o=x(t.localHostName);if(!r)return!1;if(r==="localhost"||r==="127.0.0.1"||r==="::1"||r==="0.0.0.0"||r==="::"||r==="::ffff:127.0.0.1"||r.startsWith("127.")||r===o)return!0;if(!G.isIP(r))return!1;let a=Se(t.localIps);return!!(a.has(r)||r.startsWith("::ffff:")&&a.has(r.slice(7)))}catch{return!1}}function q(e){if(!e)return"Unknown error";if(e instanceof Error)return e.message;if(typeof e=="object"){let t=e,n=typeof t.message=="string"?t.message:"",r=typeof t.code=="string"?t.code:"",o=typeof t.name=="string"?t.name:"";if(r&&n)return`${r}: ${n}`;if(r)return r;if(o&&n)return`${o}: ${n}`;if(n)return n}return String(e)}var u=g(require("path")),v={major:20,minor:0,patch:0},y="Bundled agentchatbus-ts runtime packaged with the VS Code extension.",_="Workspace-dev agentchatbus-ts runtime and local web-ui sources from the current AgentChatBus repo.";function be(e){return e==="health"?"/health":"/api/metrics"}function E(e){if(typeof e!="string")return;let t=e.trim();return t.length>0?t:void 0}function M(e){return!e?.management||typeof e.management.ownership_assignable!="boolean"?null:e.management.ownership_assignable}function A(e){let t=M(e);return t===!0?"external-service-extension-managed":t===!1?"external-service-manual":"external-service-unknown"}function J(e){let t=E(e?.startup_mode)?.toLowerCase();return t==="bundled-ts-service"?"bundled-ts-service":t==="workspace-dev-service"?"workspace-dev-service":t==="external-service-extension-managed"?"external-service-extension-managed":t==="external-service-manual"?"external-service-manual":t==="external-service-unknown"?"external-service-unknown":t==="external-service"?"external-service":A(e)}function K(e,t=v){let n=/^v?(\d+)\.(\d+)\.(\d+)$/.exec(e.trim());if(!n)return{ok:!1,message:`Unable to parse IDE host Node version '${e}'. Bundled MCP requires Node ${t.major}.${t.minor}.${t.patch}+ from the IDE host runtime.`};let[,r,o,a]=n,i=Number(r),c=Number(o),ce=Number(a);return i>t.major||i===t.major&&c>t.minor||i===t.major&&c===t.minor&&ce>=t.patch?{ok:!0,message:`IDE host Node version ${e} satisfies bundled MCP requirement ${t.major}.${t.minor}.${t.patch}+ .`}:{ok:!1,message:`IDE host Node version ${e} is too old for bundled MCP. AgentChatBus requires the IDE host runtime to provide Node ${t.major}.${t.minor}.${t.patch}+ .`}}function p(e,t){let n=be(e);return t.timedOut?`Startup probe ${n} timed out after ${t.timeoutMs??"unknown"}ms.`:typeof t.status=="number"?`Startup probe ${n} returned HTTP ${t.status}.`:t.error?`Startup probe ${n} failed: ${t.error}.`:`Startup probe ${n} failed.`}function Y(e){if(e.health.ok)return{ok:!0,source:"health",payload:e.health.payload,failureMessages:[]};let t=[p("health",e.health)];return e.metrics?.ok?{ok:!0,source:"metrics",payload:e.metrics.payload,failureMessages:t}:(e.metrics&&!e.metrics.ok&&t.push(p("metrics",e.metrics)),{ok:!1,source:null,payload:void 0,failureMessages:t})}function Q(e){let t=null;return()=>{if(t)return t;let n;try{n=Promise.resolve(e())}catch(o){n=Promise.reject(o)}let r=n.finally(()=>{t===r&&(t=null)});return t=r,r}}function X(e){let t=new URL(e.serverUrl),n=Number(t.port||(t.protocol==="https:"?"443":"80")),r=u.join(e.globalStoragePath,"bus-ts.db"),o=u.join(e.globalStoragePath,"config.json");return{command:e.hostNodeExecutable,args:[e.serverEntry,"serve"],cwd:e.extensionRoot,env:{...e.processEnv||{},AGENTCHATBUS_HOST:t.hostname,AGENTCHATBUS_PORT:String(n),AGENTCHATBUS_DB:r,AGENTCHATBUS_APP_DIR:e.globalStoragePath,AGENTCHATBUS_CONFIG_FILE:o,AGENTCHATBUS_WEB_UI_DIR:e.webUiDir,...e.cliWorkspacePath?{AGENTCHATBUS_CLI_WORKSPACE:e.cliWorkspacePath}:{},AGENTCHATBUS_WAIT_MIN_TIMEOUT_MS:String(e.msgWaitMinTimeoutMs),AGENTCHATBUS_ENFORCE_MSG_WAIT_MIN_TIMEOUT:e.enforceMsgWaitMinTimeout?"1":"0"},launchMode:"bundled-ts-service",resolvedBy:y}}function Z(e){let t=new URL(e.serverUrl),n=Number(t.port||(t.protocol==="https:"?"443":"80")),r=u.join(e.globalStoragePath,"bus-ts.db"),o=u.join(e.globalStoragePath,"config.json");return{command:e.hostNodeExecutable,args:[e.tsxCliEntrypoint,"watch","src/cli/index.ts","serve"],cwd:e.tsServerRoot,env:{...e.processEnv||{},AGENTCHATBUS_HOST:t.hostname,AGENTCHATBUS_PORT:String(n),AGENTCHATBUS_DB:r,AGENTCHATBUS_APP_DIR:e.globalStoragePath,AGENTCHATBUS_CONFIG_FILE:o,AGENTCHATBUS_WEB_UI_DIR:e.webUiDir,...e.cliWorkspacePath?{AGENTCHATBUS_CLI_WORKSPACE:e.cliWorkspacePath}:{},AGENTCHATBUS_WAIT_MIN_TIMEOUT_MS:String(e.msgWaitMinTimeoutMs),AGENTCHATBUS_ENFORCE_MSG_WAIT_MIN_TIMEOUT:e.enforceMsgWaitMinTimeout?"1":"0",AGENTCHATBUS_RELOAD:"1",AGENTCHATBUS_WORKSPACE_DEV:"1"},launchMode:"workspace-dev-service",resolvedBy:_}}function ee(e,t,n=500){let r=[...e],o=t.split(/\r?\n/).filter(a=>a.trim().length>0);for(let a of o)r.push(a),r.length>n&&r.shift();return r}function te(e,t,n){return!t&&e.length===0?[{message:n||"Ready (Managed Externally)",index:-1,description:"Extension is reading logs from the shared AgentChatBus log API.",iconId:"info",colorId:"descriptionForeground"}]:e.length===0?[{message:"Waiting for logs...",index:-2,iconId:"sync~spin"}]:e.map((r,o)=>({message:r,index:o,...T(r,o)}))}function T(e,t){return t===-1?{description:"Extension is reading logs from the shared AgentChatBus log API.",iconId:"info",colorId:"descriptionForeground"}:t===-2?{iconId:"sync~spin"}:e.includes("ERROR")||e.includes("Exception")||e.includes("failed")?{iconId:"error",colorId:"errorForeground"}:e.includes("WARNING")?{iconId:"warning",colorId:"problemsWarningIcon.foreground"}:e.includes("Exec:")||e.includes("Starting")?{iconId:"terminal"}:{}}var ne=g(require("fs")),s=g(require("path"));function xe(e,t){return[s.join(e,"agentchatbus-ts","package.json"),s.join(e,"agentchatbus-ts","src","cli","index.ts"),s.join(e,"agentchatbus-ts","node_modules","tsx","dist","cli.mjs"),s.join(e,"web-ui","index.html"),s.join(e,"web-ui","extension","index.html"),s.join(e,"vscode-agentchatbus","package.json")].every(r=>t(r))}function re(e,t=ne.existsSync){for(let n of e){let r=s.resolve(String(n||"").trim());if(r&&xe(r,t))return{repoRoot:r,tsServerRoot:s.join(r,"agentchatbus-ts"),tsxCliEntrypoint:s.join(r,"agentchatbus-ts","node_modules","tsx","dist","cli.mjs"),webUiRoot:s.join(r,"web-ui"),webUiExtensionRoot:s.join(r,"web-ui","extension"),vscodeExtensionRoot:s.join(r,"vscode-agentchatbus")}}return null}function oe(){return[{label:"Starting AgentChatBus...",icon:"play"}]}function se(e,t,n,r){return[...e,{label:t,icon:n,description:r}]}function ie(e){return e.map(t=>({label:t.label,...t.icon!==void 0?{icon:t.icon}:{},...t.description!==void 0?{description:t.description}:{}}))}function ae(e,t,n=Date.now()){return`[${((n-t)/1e3).toFixed(1)}s] ${e}`}0&&(module.exports={BUNDLED_RUNTIME_RESOLVED_BY,MIN_HOST_NODE_VERSION,WORKSPACE_DEV_RUNTIME_RESOLVED_BY,appendLogLines,appendSetupLogStep,applyServerUrlChange,buildAgentItemViewModel,buildBundledLaunchSpec,buildCursorMcpConfig,buildEventsUrl,buildSendMessageRequestBody,buildThreadItemViewModel,buildWorkspaceDevLaunchSpec,classifyDetectedStartupMode,classifyExternalStartupMode,createInitialSetupSteps,createSingleFlightRunner,describeStartupProbeFailure,ensureSupportedHostNodeVersion,extractOwnershipAssignable,filterAndSortAgents,filterAndSortThreads,formatLmError,formatSetupStepLabel,getAgentActivityTimestamp,getBrowserOpenUrl,getCursorMcpUrl,getMcpLogPresentation,getMcpLogRows,getRelativeTimeString,getSettingsDefinitions,getThreadStatusIconFileName,isLocalServerUrlWithContext,normalizeHealthString,normalizeSendMessagePayload,normalizeServerUrl,parseSseEventData,parseUiAgentRegistrationPayload,replaceSetupSteps,resolveStartupProbeResult,resolveWorkspaceDevContext,shouldIncludeArchivedThreadStatus,shouldRefreshAgentsForEventType,shouldRefreshThreadsForEventType,shouldRetrySendMessage}); diff --git a/vscode-agentchatbus/src/busServerManager.ts b/vscode-agentchatbus/src/busServerManager.ts index 95dcbdc..04b8773 100644 --- a/vscode-agentchatbus/src/busServerManager.ts +++ b/vscode-agentchatbus/src/busServerManager.ts @@ -9,12 +9,18 @@ import { buildBundledLaunchSpec, buildWorkspaceDevLaunchSpec, classifyDetectedStartupMode, + createSingleFlightRunner, ensureSupportedHostNodeVersion, extractOwnershipAssignable, normalizeHealthString, + resolveStartupProbeResult, type BundledLaunchSpec as LaunchSpec, type HealthPayload, + type MetricsPayload, type LaunchMode, + type StartupProbeEndpoint, + type StartupProbeOutcome, + type StartupProbeResolution, } from './logic/busServerManager'; import { resolveWorkspaceDevContext, @@ -54,6 +60,7 @@ export class BusServerManager { private static readonly MCP_PROVIDER_ID = 'agentchatbus.provider'; private static readonly MCP_PROVIDER_LABEL = 'AgentChatBus Local Server'; private static readonly MAX_ATTEMPTS = 40; + private static readonly STARTUP_PROBE_TIMEOUT_MS = 2000; private outputChannel: vscode.OutputChannel; private serverProcess: child_process.ChildProcess | null = null; @@ -72,6 +79,7 @@ export class BusServerManager { private readonly globalStoragePath: string; private readonly hostNodeExecutable: string; private readonly extensionVersion: string; + private readonly ensureServerRunningSingleFlight: () => Promise; private ideSessionToken: string | null = null; private ownerBootToken: string | null = null; private ideSessionState: IdeSessionApiState = { @@ -89,6 +97,7 @@ export class BusServerManager { this.globalStoragePath = context.globalStorageUri.fsPath; this.hostNodeExecutable = process.execPath; this.extensionVersion = String(context.extension.packageJSON?.version || 'unknown'); + this.ensureServerRunningSingleFlight = createSingleFlightRunner(() => this.ensureServerRunningInternal()); this.outputChannel = vscode.window.createOutputChannel('AgentChatBus Server'); void vscode.commands.executeCommand('setContext', 'agentchatbus:serverStopping', false); this.updateRestartContexts(); @@ -219,6 +228,10 @@ export class BusServerManager { } async ensureServerRunning(): Promise { + return this.ensureServerRunningSingleFlight(); + } + + private async ensureServerRunningInternal(): Promise { const workspaceDevContext = this.getWorkspaceDevContext(); if (workspaceDevContext) { this.log( @@ -242,17 +255,27 @@ export class BusServerManager { try { const probe = await this.probeServer(serverUrl); if (probe.ok) { - const startupMode = classifyDetectedStartupMode(probe.health); + if (probe.source === 'metrics' && probe.failureMessages.length > 0) { + for (const message of probe.failureMessages) { + this.recordResolutionAttempt(message); + } + this.recordResolutionAttempt('Readiness fell back to /api/metrics because /health was unavailable.'); + } + const startupMode = classifyDetectedStartupMode(probe.payload as HealthPayload); this.serverMetadata.startupMode = startupMode; - this.serverMetadata.resolvedBy = 'Existing service detected via /health'; - this.serverMetadata.backendEngine = normalizeHealthString(probe.health?.engine); - this.serverMetadata.backendVersion = normalizeHealthString(probe.health?.version); - this.serverMetadata.backendRuntime = normalizeHealthString(probe.health?.runtime); + this.serverMetadata.resolvedBy = probe.source === 'metrics' + ? 'Existing service detected via /api/metrics fallback' + : 'Existing service detected via /health'; + this.serverMetadata.backendEngine = normalizeHealthString(probe.payload?.engine); + this.serverMetadata.backendVersion = normalizeHealthString(probe.payload?.version); + this.serverMetadata.backendRuntime = normalizeHealthString(probe.payload?.runtime); this.serverMetadata.externalOwnershipAssignable = - extractOwnershipAssignable(probe.health); + extractOwnershipAssignable(probe.payload as HealthPayload); this.ownerBootToken = null; this.recordResolutionAttempt( - `Detected an already-running AgentChatBus service via /health probe (mode=${startupMode}).` + probe.source === 'metrics' + ? `Detected an already-running AgentChatBus service via /api/metrics fallback (mode=${startupMode}).` + : `Detected an already-running AgentChatBus service via /health probe (mode=${startupMode}).` ); if (this.serverMetadata.backendEngine || this.serverMetadata.backendVersion) { this.recordResolutionAttempt( @@ -281,6 +304,9 @@ export class BusServerManager { this.setServerReady(true); return true; } + for (const message of probe.failureMessages) { + this.recordResolutionAttempt(message); + } } catch (error) { const message = error instanceof Error ? error.message : String(error); this.log(`Probe failed: ${message}`, 'warning'); @@ -880,27 +906,65 @@ export class BusServerManager { ); } - private async probeServer(url: string): Promise<{ ok: boolean; health?: HealthPayload }> { + private async probeJsonEndpoint( + url: string, + endpoint: StartupProbeEndpoint, + ): Promise> { + const probePath = endpoint === 'health' ? '/health' : '/api/metrics'; + const controller = new AbortController(); + const timeoutMs = BusServerManager.STARTUP_PROBE_TIMEOUT_MS; + const timeoutId = setTimeout(() => controller.abort(), timeoutMs); + try { - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), 1000); - const response = await fetch(`${url}/health`, { signal: controller.signal }); - clearTimeout(timeoutId); + const response = await fetch(`${url}${probePath}`, { signal: controller.signal }); if (!response.ok) { - return { ok: false }; + return { + ok: false, + status: response.status, + timeoutMs, + }; } - let health: HealthPayload | undefined; + + let payload: T | undefined; try { - health = await response.json() as HealthPayload; + payload = await response.json() as T; } catch { - // Allow legacy health endpoints that return non-JSON. + // Allow legacy or partial probe endpoints that return non-JSON. } - return { ok: true, health }; - } catch { - return { ok: false }; + + return { + ok: true, + payload, + }; + } catch (error) { + if (error instanceof Error && error.name === 'AbortError') { + return { + ok: false, + timedOut: true, + timeoutMs, + }; + } + + return { + ok: false, + error: error instanceof Error ? error.message : String(error), + timeoutMs, + }; + } finally { + clearTimeout(timeoutId); } } + private async probeServer(url: string): Promise> { + const health = await this.probeJsonEndpoint(url, 'health'); + if (health.ok) { + return resolveStartupProbeResult({ health }); + } + + const metrics = await this.probeJsonEndpoint(url, 'metrics'); + return resolveStartupProbeResult({ health, metrics }); + } + private async checkServer(url: string): Promise { const probe = await this.probeServer(url); return probe.ok; @@ -1201,13 +1265,43 @@ export class BusServerManager { this.log('Waiting for health check response...', 'sync~spin'); const serverUrl = this.getServerUrl(); let retries = 20; + let lastProbeFailureSignature = ''; while (retries > 0) { await new Promise(resolve => setTimeout(resolve, 1000)); - if (await this.checkServer(serverUrl)) { - this.log('Server is online and ready.', 'check'); + const probe = await this.probeServer(serverUrl); + if (probe.ok) { + if (probe.source === 'metrics' && probe.failureMessages.length > 0) { + for (const message of probe.failureMessages) { + this.log(message, 'warning'); + } + this.log('Readiness fell back to /api/metrics because /health was unavailable.', 'info'); + } + this.serverMetadata.backendEngine = + normalizeHealthString(probe.payload?.engine) || this.serverMetadata.backendEngine; + this.serverMetadata.backendVersion = + normalizeHealthString(probe.payload?.version) || this.serverMetadata.backendVersion; + this.serverMetadata.backendRuntime = + normalizeHealthString(probe.payload?.runtime) || this.serverMetadata.backendRuntime; + this.serverMetadata.externalOwnershipAssignable = + extractOwnershipAssignable(probe.payload as HealthPayload) ?? this.serverMetadata.externalOwnershipAssignable; + this.log( + probe.source === 'metrics' + ? 'Server is online and ready via /api/metrics fallback.' + : 'Server is online and ready via /health.', + 'check', + ); await this.ensureIdeSessionRegistered(true); return true; } + + const failureSignature = probe.failureMessages.join(' | '); + if (failureSignature && failureSignature !== lastProbeFailureSignature) { + for (const message of probe.failureMessages) { + this.log(message, 'warning'); + } + lastProbeFailureSignature = failureSignature; + } + retries--; } diff --git a/vscode-agentchatbus/src/logic/busServerManager.ts b/vscode-agentchatbus/src/logic/busServerManager.ts index 162dd54..942d9ec 100644 --- a/vscode-agentchatbus/src/logic/busServerManager.ts +++ b/vscode-agentchatbus/src/logic/busServerManager.ts @@ -32,6 +32,33 @@ export type HealthPayload = { }; }; +export type MetricsPayload = HealthPayload & Record; + +export type StartupProbeEndpoint = 'health' | 'metrics'; + +export type StartupProbeFailure = { + status?: number; + timedOut?: boolean; + timeoutMs?: number; + error?: string; +}; + +export type StartupProbeOutcome = + | { + ok: true; + payload?: T; + } + | ({ + ok: false; + } & StartupProbeFailure); + +export type StartupProbeResolution = { + ok: boolean; + source: StartupProbeEndpoint | null; + payload?: T; + failureMessages: string[]; +}; + export const MIN_HOST_NODE_VERSION = { major: 20, minor: 0, @@ -43,6 +70,10 @@ export const BUNDLED_RUNTIME_RESOLVED_BY = export const WORKSPACE_DEV_RUNTIME_RESOLVED_BY = 'Workspace-dev agentchatbus-ts runtime and local web-ui sources from the current AgentChatBus repo.'; +function getStartupProbePath(endpoint: StartupProbeEndpoint): string { + return endpoint === 'health' ? '/health' : '/api/metrics'; +} + export function normalizeHealthString(value: unknown): string | undefined { if (typeof value !== 'string') { return undefined; @@ -127,6 +158,85 @@ export function ensureSupportedHostNodeVersion( }; } +export function describeStartupProbeFailure( + endpoint: StartupProbeEndpoint, + failure: StartupProbeFailure, +): string { + const probePath = getStartupProbePath(endpoint); + + if (failure.timedOut) { + return `Startup probe ${probePath} timed out after ${failure.timeoutMs ?? 'unknown'}ms.`; + } + if (typeof failure.status === 'number') { + return `Startup probe ${probePath} returned HTTP ${failure.status}.`; + } + if (failure.error) { + return `Startup probe ${probePath} failed: ${failure.error}.`; + } + return `Startup probe ${probePath} failed.`; +} + +export function resolveStartupProbeResult(input: { + health: StartupProbeOutcome; + metrics?: StartupProbeOutcome; +}): StartupProbeResolution { + if (input.health.ok) { + return { + ok: true, + source: 'health', + payload: input.health.payload, + failureMessages: [], + }; + } + + const failureMessages = [describeStartupProbeFailure('health', input.health)]; + + if (input.metrics?.ok) { + return { + ok: true, + source: 'metrics', + payload: input.metrics.payload, + failureMessages, + }; + } + + if (input.metrics && !input.metrics.ok) { + failureMessages.push(describeStartupProbeFailure('metrics', input.metrics)); + } + + return { + ok: false, + source: null, + payload: undefined, + failureMessages, + }; +} + +export function createSingleFlightRunner(operation: () => Promise): () => Promise { + let inFlight: Promise | null = null; + + return () => { + if (inFlight) { + return inFlight; + } + + let execution: Promise; + try { + execution = Promise.resolve(operation()); + } catch (error) { + execution = Promise.reject(error); + } + const wrapped = execution.finally(() => { + if (inFlight === wrapped) { + inFlight = null; + } + }); + + inFlight = wrapped; + return wrapped; + }; +} + export function buildBundledLaunchSpec(input: { serverEntry: string; webUiDir: string; diff --git a/vscode-agentchatbus/src/logic/testExports.ts b/vscode-agentchatbus/src/logic/testExports.ts index 14c65fd..9a2c9fa 100644 --- a/vscode-agentchatbus/src/logic/testExports.ts +++ b/vscode-agentchatbus/src/logic/testExports.ts @@ -43,9 +43,12 @@ export { buildWorkspaceDevLaunchSpec, classifyDetectedStartupMode, classifyExternalStartupMode, + createSingleFlightRunner, + describeStartupProbeFailure, ensureSupportedHostNodeVersion, extractOwnershipAssignable, normalizeHealthString, + resolveStartupProbeResult, WORKSPACE_DEV_RUNTIME_RESOLVED_BY, } from './busServerManager'; export { diff --git a/vscode-agentchatbus/test/logicBusServerManager.test.js b/vscode-agentchatbus/test/logicBusServerManager.test.js index f47d861..b5cb82c 100644 --- a/vscode-agentchatbus/test/logicBusServerManager.test.js +++ b/vscode-agentchatbus/test/logicBusServerManager.test.js @@ -9,9 +9,12 @@ const { buildWorkspaceDevLaunchSpec, classifyDetectedStartupMode, classifyExternalStartupMode, + createSingleFlightRunner, + describeStartupProbeFailure, ensureSupportedHostNodeVersion, extractOwnershipAssignable, normalizeHealthString, + resolveStartupProbeResult, WORKSPACE_DEV_RUNTIME_RESOLVED_BY, } = require('../out/logic/testExports'); @@ -146,3 +149,101 @@ test('buildWorkspaceDevLaunchSpec wires local tsx watcher and dev env', () => { path.join('C:\\Users\\me\\AppData\\Roaming\\Code\\AgentChatBus', 'bus-ts.db'), ); }); + +test('describeStartupProbeFailure reports timeout, HTTP, and generic probe errors', () => { + assert.equal( + describeStartupProbeFailure('health', { timedOut: true, timeoutMs: 1000 }), + 'Startup probe /health timed out after 1000ms.', + ); + assert.equal( + describeStartupProbeFailure('metrics', { status: 503 }), + 'Startup probe /api/metrics returned HTTP 503.', + ); + assert.equal( + describeStartupProbeFailure('health', { error: 'socket hang up' }), + 'Startup probe /health failed: socket hang up.', + ); +}); + +test('resolveStartupProbeResult prefers /health when both probes succeed', () => { + const health = { engine: 'node', version: '0.2.14', runtime: 'node v22.22.1' }; + const metrics = { engine: 'node', version: '0.2.14', runtime: 'node v22.22.1' }; + + assert.deepEqual( + resolveStartupProbeResult({ + health: { ok: true, payload: health }, + metrics: { ok: true, payload: metrics }, + }), + { + ok: true, + source: 'health', + payload: health, + failureMessages: [], + }, + ); +}); + +test('resolveStartupProbeResult falls back to /api/metrics when /health is unavailable', () => { + const metrics = { engine: 'node', version: '0.2.14', runtime: 'node v22.22.1' }; + + assert.deepEqual( + resolveStartupProbeResult({ + health: { ok: false, timedOut: true, timeoutMs: 1000 }, + metrics: { ok: true, payload: metrics }, + }), + { + ok: true, + source: 'metrics', + payload: metrics, + failureMessages: ['Startup probe /health timed out after 1000ms.'], + }, + ); +}); + +test('resolveStartupProbeResult reports both probe failures when no readiness signal is available', () => { + assert.deepEqual( + resolveStartupProbeResult({ + health: { ok: false, status: 404 }, + metrics: { ok: false, error: 'connect ECONNREFUSED 127.0.0.1:39765' }, + }), + { + ok: false, + source: null, + payload: undefined, + failureMessages: [ + 'Startup probe /health returned HTTP 404.', + 'Startup probe /api/metrics failed: connect ECONNREFUSED 127.0.0.1:39765.', + ], + }, + ); +}); + +test('createSingleFlightRunner coalesces concurrent calls and resets after completion', async () => { + let invocations = 0; + let releaseFirstRun; + const firstRunReleased = new Promise((resolve) => { + releaseFirstRun = resolve; + }); + + const runOnce = createSingleFlightRunner(async () => { + invocations += 1; + await firstRunReleased; + return invocations; + }); + + const first = runOnce(); + const second = runOnce(); + + assert.equal(invocations, 1); + assert.strictEqual(first, second); + + releaseFirstRun(); + + assert.equal(await first, 1); + assert.equal(await second, 1); + + const third = runOnce(); + assert.notStrictEqual(third, first); + assert.equal(await third, 2); + assert.equal(invocations, 2); +});