@keepur/hive 0.2.6 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/pkg/cli.min.js +122 -122
- package/pkg/server.min.js +4 -4
- package/service/deploy-check.sh +15 -1
- package/service/deploy.sh +129 -72
- package/service/instances.conf +19 -4
package/pkg/server.min.js
CHANGED
|
@@ -168,7 +168,7 @@ You have ${g.length} reference file(s) in your memory directory:
|
|
|
168
168
|
`)}buildServerConfig(e,t){return this.buildAllServerConfigs(t)[e]}buildAllServerConfigs(e){let t={};if(p.slack.localMcpServer)t.slack={type:"stdio",command:"node",args:[Y("slack/slack-mcp-server.js")],env:{HIVE_INTERNAL_URL:`http://127.0.0.1:${p.slackInternal.port}`,HIVE_INTERNAL_TOKEN:p.slackInternal.authToken,HIVE_AGENT_ID:this.agentConfig.id}};else{let c=p.slack.mcpToken;c&&(t.slack={type:"http",url:"https://mcp.slack.com/mcp",headers:{Authorization:`Bearer ${c}`}})}let s=[{id:"self",backing:"mongo"}],n=this.getArchetypeDef();if(n&&this.agentConfig.archetypeConfig)try{let c=n.memoryScopes({agentConfig:this.agentConfig,archetypeConfig:this.agentConfig.archetypeConfig});for(let d of c)d.id!=="self"&&s.push(d)}catch(c){R.error("Archetype memoryScopes threw \u2014 using self-only",{agent:this.agentConfig.id,archetype:this.agentConfig.archetype,error:String(c)})}t.memory={type:"stdio",command:"node",args:[Y("memory/memory-mcp-server.js")],env:{AGENT_ID:this.agentConfig.id,MONGODB_URI:p.mongo.uri,MONGODB_DB:p.mongo.dbName,MEMORY_SCOPES_JSON:JSON.stringify(s)}},t["structured-memory"]={type:"stdio",command:"node",args:[Y("memory/structured-memory-mcp-server.js")],env:{AGENT_ID:this.agentConfig.id,MONGODB_URI:p.mongo.uri,MONGODB_DB:p.mongo.dbName,CHANNEL_ID:e?.channelId??"",THREAD_ID:e?.threadId??"",QDRANT_URL:process.env.QDRANT_URL??"http://localhost:6333",OLLAMA_URL:process.env.OLLAMA_URL??"http://localhost:11434"}},t.keychain={type:"stdio",command:"node",args:[Y("keychain/keychain-mcp-server.js")],env:{KEYCHAIN_SERVICE:`hive/${p.instance.id}`}};let i=p.google.accounts[this.agentConfig.id]||p.google.account,o=p.google.client;if(t.google={type:"stdio",command:"node",args:[Y("google/google-mcp-server.js")],env:{...i?{GOG_ACCOUNT:i}:{},...o?{GOG_CLIENT:o}:{},DRIVE_SHARED_FOLDER:p.google.sharedFolder,INSTANCE_ID:p.instance.id,PATH:process.env.PATH??""}},p.quo.apiKey&&(t.quo={type:"stdio",command:"node",args:[Y("quo/quo-mcp-server.js")],env:{QUO_API_KEY:p.quo.apiKey,...p.quo.phoneNumberId?{QUO_PHONE_NUMBER_ID:p.quo.phoneNumberId}:{},QUO_LINES_JSON:JSON.stringify(p.quo.lines)}}),p.voice.enabled&&p.voice.apiKey){let c=Object.entries(p.voice.assistants).find(([d,u])=>u===this.agentConfig.id)?.[0]??"";t.voice={type:"stdio",command:"node",args:[Y("voice/voice-mcp-server.js")],env:{VAPI_API_KEY:p.voice.apiKey,VAPI_PHONE_NUMBER_ID:p.voice.phoneNumberId,VAPI_ASSISTANT_ID:c,AGENT_ID:this.agentConfig.id,AGENT_NAME:this.agentConfig.name}}}t.contacts={type:"stdio",command:"node",args:[Y("contacts/contacts-mcp-server.js")],env:{MONGODB_URI:p.mongo.uri,MONGODB_DB:p.mongo.dbName}};let a=p.taskLedger.agentKeys[this.agentConfig.id]??p.taskLedger.apiKey;if(a&&(t.tasks={type:"stdio",command:"node",args:[Y("tasks/task-mcp-server.js")],env:{TASK_LEDGER_API_URL:p.taskLedger.apiUrl,TASK_LEDGER_API_KEY:a}}),p.brave.apiKey&&(t["brave-search"]={type:"stdio",command:"node",args:[q0(import.meta.url).resolve("brave-search-mcp/dist/index.js")],env:{BRAVE_API_KEY:p.brave.apiKey}}),p.resend.apiKey){let c=this.agentConfig.name.toLowerCase(),d=p.resend.emailDomain,u=p.resend.businessName?` (${p.resend.businessName})`:"",h=d?`${this.agentConfig.name}${u} <${c}@${d}>`:p.resend.fromAddress;t.resend={type:"stdio",command:"node",args:[Y("resend/resend-mcp-server.js")],env:{RESEND_API_KEY:p.resend.apiKey,RESEND_FROM_ADDRESS:h,RESEND_DEFAULT_CC:p.resend.defaultCc,RESEND_DEFAULT_BCC:p.resend.defaultBcc}}}if(p.linear.apiKey){let c={LINEAR_API_KEY:p.linear.apiKey};p.linear.teamId&&(c.LINEAR_TEAM_ID=p.linear.teamId),t.linear={type:"stdio",command:"node",args:[Y("linear/linear-mcp-server.js")],env:c}}if(p.github.repo){let c={GITHUB_REPO:p.github.repo,PATH:process.env.PATH??""};p.github.token&&(c.GH_TOKEN=p.github.token),t["github-issues"]={type:"stdio",command:"node",args:[Y("github/github-issues-mcp-server.js")],env:c}}if(p.clickup.apiToken&&(t.clickup={type:"stdio",command:"node",args:[Y("clickup/clickup-mcp-server.js")],env:{CLICKUP_API_TOKEN:p.clickup.apiToken}}),p.recall.apiKey&&(t.recall={type:"stdio",command:"node",args:[Y("recall/recall-mcp-server.js")],env:{RECALL_API_KEY:p.recall.apiKey,RECALL_API_REGION:p.recall.region,RECALL_WEBHOOK_SECRET:p.recall.webhookSecret,MEETING_MONITOR_API:`http://127.0.0.1:${p.recall.monitorPort}`,MEETING_MONITOR_PUBLIC_URL:p.recall.monitorPublicUrl,RECALL_AGENT_ID:this.agentConfig.id,RECALL_ADAPTER_ID:e?.adapterId??"",RECALL_CHANNEL_ID:e?.channelId??"",RECALL_CHANNEL_KIND:e?.channelKind??"internal",RECALL_CHANNEL_LABEL:e?.channelLabel??"",RECALL_THREAD_ID:e?.threadId??"",RECALL_SLACK_TS:e?.slackTs??"",RECALL_SLACK_THREAD_TS:e?.slackThreadTs??""}}),p.browser.cdpEndpoint){let c=Ec(this.agentConfig.id,z);Ch(c,{recursive:!0}),t.browser={type:"stdio",command:"npx",args:["@playwright/mcp@latest","--cdp-endpoint",p.browser.cdpEndpoint,"--output-dir",c,"--user-data-dir",Oe(c,"user-data")],env:{PATH:process.env.PATH??"",HOME:process.env.HOME??""}}}t.background={type:"stdio",command:"node",args:[Y("background/background-task-mcp-server.js")],env:{BG_TASK_API:`http://127.0.0.1:${p.background.port}`,BG_AUTH_TOKEN:p.background.authToken,BG_AGENT_ID:this.agentConfig.id,BG_ADAPTER_ID:e?.adapterId??"",BG_CHANNEL_ID:e?.channelId??"",BG_CHANNEL_KIND:e?.channelKind??"internal",BG_CHANNEL_LABEL:e?.channelLabel??"",BG_THREAD_ID:e?.threadId??"",BG_SLACK_TS:e?.slackTs??"",BG_SLACK_THREAD_TS:e?.slackThreadTs??""}},t.callback={type:"stdio",command:"node",args:[Y("callback/callback-mcp-server.js")],env:{CB_AGENT_ID:this.agentConfig.id,CB_ADAPTER_ID:e?.adapterId??"",CB_CHANNEL_ID:e?.channelId??"",CB_CHANNEL_KIND:e?.channelKind??"internal",CB_CHANNEL_LABEL:e?.channelLabel??"",CB_THREAD_ID:e?.threadId??"",CB_SLACK_TS:e?.slackTs??"",CB_SLACK_THREAD_TS:e?.slackThreadTs??"",MONGODB_URI:p.mongo.uri,MONGODB_DB:p.mongo.dbName}},t["code-task"]={type:"stdio",command:"node",args:[Y("code-task/code-task-mcp-server.js")],env:{CT_TASK_API:`http://127.0.0.1:${p.codeTask.port}`,CT_AUTH_TOKEN:p.codeTask.authToken,CT_AGENT_ID:this.agentConfig.id,CT_ADAPTER_ID:e?.adapterId??"",CT_CHANNEL_ID:e?.channelId??"",CT_CHANNEL_KIND:e?.channelKind??"internal",CT_CHANNEL_LABEL:e?.channelLabel??"",CT_THREAD_ID:e?.threadId??"",CT_SLACK_TS:e?.slackTs??"",CT_SLACK_THREAD_TS:e?.slackThreadTs??""}};let l={OLLAMA_URL:process.env.OLLAMA_URL??"http://localhost:11434",QDRANT_URL:process.env.QDRANT_URL??"http://localhost:6333"};process.env.KB_EMBED_MODEL&&(l.KB_EMBED_MODEL=process.env.KB_EMBED_MODEL),t["conversation-search"]={type:"stdio",command:"node",args:[Y("search/conversation-search-mcp-server.js")],env:{...l,AGENT_ID:this.agentConfig.id,DEFAULT_AGENT:p.defaultAgent}},t["code-search"]={type:"stdio",command:"node",args:[Y("code-index/code-search-mcp-server.js")],env:{MONGODB_URI:p.mongo.uri,MONGODB_DB:p.mongo.dbName,QDRANT_URL:process.env.QDRANT_URL??"http://localhost:6333",OLLAMA_URL:process.env.OLLAMA_URL??"http://localhost:11434"}};for(let c of this.plugins)for(let[d,u]of Object.entries(c.manifest.mcpServers)){if(t[d]){R.warn("Plugin server name conflicts with core server, skipping",{plugin:c.name,server:d});continue}if(c.brokenServers[d])continue;let h=Ks(c.name,u.entry,{hiveHome:z,distDir:Ke});if("reason"in h){R.error("Plugin MCP server unresolvable at spawn time",{plugin:c.name,server:d,reason:h.reason,pathsChecked:h.pathsChecked});continue}let m=h.path;Ah(c.dir);let g=p.taskLedger.agentKeys[this.agentConfig.id]??p.taskLedger.apiKey,f={AGENT_ID:this.agentConfig.id,AGENT_NAME:this.agentConfig.name,MONGODB_URI:p.mongo.uri,MONGODB_DB:p.mongo.dbName,TASK_LEDGER_API_URL:p.taskLedger.apiUrl,...g?{TASK_LEDGER_API_KEY:g}:{},PATH:process.env.PATH??"",HOME:process.env.HOME??""};for(let y of u.env??[])process.env[y]&&(f[y]=process.env[y]);for(let y of u.secretEnv??[]){let w=process.env[y]||Jt(p.instance.id,y);w&&(f[y]=w)}for(let[y,w]of Object.entries(u.envMap??{}))f[w]&&(f[y]=f[w]);for(let[y,w]of Object.entries(u.agentEnv??{}))f[y]=r.resolveAgentEnvPath(this.agentConfig,w);t[d]={type:"stdio",command:"node",args:[m],env:f}}return t["event-bus"]={type:"stdio",command:"node",args:[Y("events/event-bus-mcp-server.js")],env:{AGENT_ID:this.agentConfig.id,MONGODB_URI:p.mongo.uri,MONGODB_DB:p.mongo.dbName,EVENT_SUBSCRIBERS:this.eventSubscribersJson}},r.registryRef||R.warn("registryRef not set \u2014 agents will get empty AGENT_IDS for team server"),t.team={type:"stdio",command:"node",args:[Y("team/team-mcp-server.js")],env:{AGENT_ID:this.agentConfig.id,MONGODB_URI:p.mongo.uri,MONGODB_DB:p.mongo.dbName,AGENT_IDS:JSON.stringify(r.registryRef?.getAll().map(c=>c.id)??[])}},p.workflow.enabled&&(t.workflow={type:"stdio",command:"node",args:[Y("workflow/workflow-mcp-server.js")],env:{AGENT_ID:this.agentConfig.id,MONGODB_URI:p.mongo.uri,MONGODB_DB:p.mongo.dbName,EVENT_SUBSCRIBERS:this.eventSubscribersJson}}),t.schedule={type:"stdio",command:"node",args:[Y("schedule/schedule-mcp-server.js")],env:{AGENT_ID:this.agentConfig.id,MONGODB_URI:p.mongo.uri,MONGODB_DB:p.mongo.dbName}},t.admin={type:"stdio",command:"node",args:[Y("admin/admin-mcp-server.js")],env:{MONGODB_URI:p.mongo.uri,MONGODB_DB:p.mongo.dbName,AGENT_ID:this.agentConfig.id,INSTANCE_CAPABILITIES:F0(this.plugins)}},t}filterCoreServers(e){let t={...e},s=new Set(this.agentConfig.coreServers);s.has("memory")&&s.add("structured-memory"),s.add("schedule"),s.add("team"),s.add("slack"),p.workflow.enabled&&s.add("workflow");for(let n of Object.keys(t))s.has(n)||delete t[n];if(!this.agentConfig.autonomy.externalComms)for(let n of["resend","quo"])t[n]&&(R.debug("Autonomy: externalComms disabled \u2014 removing server",{server:n,agent:this.agentConfig.id}),delete t[n]);return this.agentConfig.autonomy.codeTask||t["code-task"]&&(R.debug("Autonomy: codeTask disabled \u2014 removing server",{server:"code-task",agent:this.agentConfig.id}),delete t["code-task"]),this.agentConfig.autonomy.codeAccess||t["code-search"]&&(R.debug("Autonomy: codeAccess disabled \u2014 removing server",{server:"code-search",agent:this.agentConfig.id}),delete t["code-search"]),t}static resolveAgentEnvPath(e,t){let s=t.split("."),n=e;for(let i of s){if(n==null||typeof n!="object")return"";n=n[i]}return n==null?"":String(n)}static INFRASTRUCTURE_SERVERS=new Set(["schedule","structured-memory","team"]);static CONTEXT_DEPENDENT_SERVERS=new Set(["callback","background","code-task","recall","structured-memory","memory"]);buildDelegateAgents(e){let t=this.agentConfig.delegateServers;if(t.length===0)return{};let s={},n=new Set;this.agentConfig.autonomy.externalComms||(n.add("resend"),n.add("quo")),this.agentConfig.autonomy.codeTask||n.add("code-task"),this.agentConfig.autonomy.codeAccess||n.add("code-search");for(let i of t){if(n.has(i)){R.debug("Autonomy gate \u2014 skipping delegate server",{server:i,agent:this.agentConfig.id});continue}r.CONTEXT_DEPENDENT_SERVERS.has(i)&&R.warn("Context-dependent server in delegateServers \u2014 subagent won't have channel context",{agent:this.agentConfig.id,server:i});let o=e[i];if(!o){R.warn("Delegate server not found in configs, skipping",{agent:this.agentConfig.id,server:i});continue}let a=this.getServerCatalogEntry(i).description,l=this.agentConfig.delegatePrompts?.[i],c=l||`You are a tool specialist for ${i}. Execute the requested task using your available tools. Return results concisely. Do not add commentary or explanation beyond what was asked.`;s[i]={description:a,prompt:c,mcpServers:[{[i]:o}],model:"inherit",maxTurns:l?7:10,disallowedTools:["Agent"]},l&&R.info("Intent-aware delegate prompt loaded",{agent:this.agentConfig.id,server:i,promptLength:l.length})}return s}getServerCatalogEntry(e){if(Hs[e])return Hs[e];for(let t of this.plugins){let s=t.manifest.mcpServers[e];if(s?.description)return{description:s.description,usage:s.usage,notFor:s.notFor}}return{description:e}}buildSdkPlugins(){let e=this.agentConfig.plugins;if(!e?.length)return[];let t=[],s=Oe(Ke,"..","plugins","claude-code");for(let n of e){if(n.includes("/")||n.includes("\\")||n===".."||n.startsWith(".")){R.warn("Invalid plugin name, skipping",{plugin:n,agent:this.agentConfig.id});continue}let i=Oe(s,n);if(!Ws(i)){R.warn("Plugin not found, skipping",{plugin:n,expected:i,agent:this.agentConfig.id});continue}t.push({type:"local",path:i})}return t.length>0&&R.debug("Loaded plugins for agent",{agent:this.agentConfig.id,plugins:t.map(n=>n.path)}),t}buildNativeSkills(){return vh(this.skillIndex,this.agentConfig.id)}getArchetypeDef(){return this._archetypeDef===void 0&&(this._archetypeDef=this.agentConfig.archetype?pr(this.agentConfig.archetype)??null:null,this.agentConfig.archetype&&!this._archetypeDef&&R.warn("Archetype referenced by agent not registered \u2014 running unstructured",{agent:this.agentConfig.id,archetype:this.agentConfig.archetype})),this._archetypeDef}buildHooks(e){let t={PreCompact:this.buildPreCompactMatcher()},s=this.getArchetypeDef();if(s&&this.agentConfig.archetypeConfig)try{let n=s.preToolUseHooks({agentConfig:this.agentConfig,archetypeConfig:this.agentConfig.archetypeConfig,workItemContext:e});n.length>0&&(t.PreToolUse=n)}catch(n){R.error("Archetype preToolUseHooks threw \u2014 installing deny-all PreToolUse hook",{agent:this.agentConfig.id,archetype:this.agentConfig.archetype,error:String(n)}),t.PreToolUse=[{hooks:[async()=>({hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:`Archetype hook initialization failed (${String(n)}). All tool calls blocked until the archetype is fixed.`}})]}]}return t}buildPreCompactMatcher(){let e=this.agentConfig.name,t=this.agentConfig.id,s=this.prefetcher;return[{hooks:[async(n,i,o)=>{R.info("PreCompact hook fired",{agent:t});let a=[`You are ${e} (agent ID: ${t}). When summarizing this conversation for compaction:`,"- Preserve your identity, role, and any behavioral instructions from your system prompt","- Keep all customer/contact names, deal details, and reference numbers","- Retain every decision made and commitment given \u2014 who decided what, and why","- Preserve active workflows: what's in progress, what's pending, next steps","- Keep tool call results that informed decisions (not raw API responses)","- Discard pleasantries, thinking-out-loud, and intermediate failed attempts"].join(`
|
|
169
169
|
`),l="";if(s&&n?.transcript_path)try{let d=await j0(n.transcript_path,"utf-8");if(d.length>0){let h=d.length>2e5?d.slice(-2e5):d;l=await s.getCompactionContext(h,t)}}catch(d){R.warn("Code context extraction failed during compaction \u2014 proceeding without",{agent:t,error:String(d)})}return{continue:!0,systemMessage:l?`${a}
|
|
170
170
|
|
|
171
|
-
${l}`:a}}]}]}async send(e,t,s,n,i,o){let a=i??this.agentConfig.model;R.info("Sending prompt to agent",{agent:this.agentConfig.id,model:a,modelOverride:!!i,resumeSession:t??"new",promptLength:e.length,streaming:!!s});let l=this.buildAllServerConfigs(n),c=this.filterCoreServers(l),d=this.buildDelegateAgents(l),u=await this.buildSystemPrompt(Object.keys(c),Object.keys(d)),h=[...this.buildSdkPlugins(),...this.buildNativeSkills()];Object.keys(d).length>0&&R.info("Delegate subagents configured",{agent:this.agentConfig.id,delegates:Object.keys(d)});let m={},g=this.getArchetypeDef();if(g&&this.agentConfig.archetypeConfig)try{m=g.sessionOptions({agentConfig:this.agentConfig,archetypeConfig:this.agentConfig.archetypeConfig,workItemContext:n})}catch(V){R.error("Archetype sessionOptions threw \u2014 ignoring",{agent:this.agentConfig.id,archetype:this.agentConfig.archetype,error:String(V)})}let f=typeof m.cwd=="string"?"archetype":"default",y=f==="archetype"?m.cwd:Ac(this.agentConfig.id,z);if(f==="default")Ch(y,{recursive:!0});else{let V;try{V=R0(y)}catch(L){let P=`Archetype cwd unavailable at session start \u2014 refusing to run: ${y} (${String(L)})`;throw R.error(P,{agent:this.agentConfig.id}),new Error(P)}if(!V.isDirectory()){let L=`Archetype cwd is not a directory: ${y}`;throw R.error(L,{agent:this.agentConfig.id}),new Error(L)}}let w=$0({prompt:e,options:{model:a,systemPrompt:u,permissionMode:"bypassPermissions",allowDangerouslySkipPermissions:!0,maxTurns:o?.maxTurns??this.agentConfig.maxTurns,maxBudgetUsd:o?.budgetUsd??this.agentConfig.budgetUsd,
|
|
171
|
+
${l}`:a}}]}]}async send(e,t,s,n,i,o){let a=i??this.agentConfig.model;R.info("Sending prompt to agent",{agent:this.agentConfig.id,model:a,modelOverride:!!i,resumeSession:t??"new",promptLength:e.length,streaming:!!s});let l=this.buildAllServerConfigs(n),c=this.filterCoreServers(l),d=this.buildDelegateAgents(l),u=await this.buildSystemPrompt(Object.keys(c),Object.keys(d)),h=[...this.buildSdkPlugins(),...this.buildNativeSkills()];Object.keys(d).length>0&&R.info("Delegate subagents configured",{agent:this.agentConfig.id,delegates:Object.keys(d)});let m={},g=this.getArchetypeDef();if(g&&this.agentConfig.archetypeConfig)try{m=g.sessionOptions({agentConfig:this.agentConfig,archetypeConfig:this.agentConfig.archetypeConfig,workItemContext:n})}catch(V){R.error("Archetype sessionOptions threw \u2014 ignoring",{agent:this.agentConfig.id,archetype:this.agentConfig.archetype,error:String(V)})}let f=typeof m.cwd=="string"?"archetype":"default",y=f==="archetype"?m.cwd:Ac(this.agentConfig.id,z);if(f==="default")Ch(y,{recursive:!0});else{let V;try{V=R0(y)}catch(L){let P=`Archetype cwd unavailable at session start \u2014 refusing to run: ${y} (${String(L)})`;throw R.error(P,{agent:this.agentConfig.id}),new Error(P)}if(!V.isDirectory()){let L=`Archetype cwd is not a directory: ${y}`;throw R.error(L,{agent:this.agentConfig.id}),new Error(L)}}let w=$0({prompt:e,options:{model:a,systemPrompt:u,permissionMode:"bypassPermissions",allowDangerouslySkipPermissions:!0,maxTurns:o?.maxTurns??this.agentConfig.maxTurns,maxBudgetUsd:o?.budgetUsd??this.agentConfig.budgetUsd,cwd:y,settingSources:m.settingSources??[],includePartialMessages:!!s,...t?{resume:t}:{},...Object.keys(c).length>0?{mcpServers:c}:{},...Object.keys(d).length>0?{agents:d}:{},...h.length>0?{plugins:h}:{},hooks:this.buildHooks(n),...this.agentConfig.betas?.length?{betas:this.agentConfig.betas}:{},env:{...process.env,...p.anthropic.apiKey?{ANTHROPIC_API_KEY:p.anthropic.apiKey}:{},CLAUDE_AGENT_SDK_CLIENT_APP:"hive/0.1.0",CLAUDECODE:void 0}}});this.activeQuery=w;let S="",_=t??"",v=0,I=0,N=!1,D;this._aborted=!1;let C=0,E=0,G=0,W=0,$=0,ce=0,Ie,me=[],dn=null,os=null,Qe=o?.timeoutMs??this.agentConfig.timeoutMs??3e5,_i=setTimeout(()=>{R.warn("Agent query timed out, aborting",{agent:this.agentConfig.id,timeoutMs:Qe}),this.abort()},Qe);try{for await(let V of w){let L=V;if(L.type==="system"&&L.subtype==="init"&&(_=L.session_id,R.debug("Session initialized",{sessionId:_})),L.type==="system"&&L.subtype==="compact_boundary"){let P=L.compact_metadata;ce++,Ie=P?.pre_tokens,R.info("Context compacted",{agent:this.agentConfig.id,trigger:P?.trigger,preTokens:P?.pre_tokens,compactionNumber:ce})}if(L.type==="system"&&L.subtype==="status"&&L.status==="compacting"&&R.info("Compaction in progress",{agent:this.agentConfig.id}),L.type==="stream_event"&&s){let P=L.event;P?.type==="content_block_delta"&&P?.delta?.type==="text_delta"&&(s(P.delta.text),N=!0)}if(L.type==="tool_progress"){let P=L;R.info("Tool in progress",{agent:this.agentConfig.id,tool:P.tool_name,elapsed:P.elapsed_time_seconds})}if(L.type==="assistant"){let P=L.message?.content;if(Array.isArray(P))for(let le of P)le.type==="text"?S=le.text:le.type==="tool_use"&&(os&&me.length>0&&(me[me.length-1].endMs=Date.now()),os=le.name,dn=Date.now(),me.push({tool:le.name,startMs:dn}),R.info("Tool call started",{agent:this.agentConfig.id,tool:le.name}));L.session_id&&(_=L.session_id)}if(L.type==="result"){let P=L;v=P.total_cost_usd,I=P.duration_ms,_=P.session_id;let le=P.usage;le&&(C=le.input_tokens??0,E=le.output_tokens??0,G=le.cache_read_input_tokens??0,W=le.cache_creation_input_tokens??0);let hn=P.modelUsage;if(hn)for(let Lt of Object.values(hn))Lt.contextWindow&&Lt.contextWindow>$&&($=Lt.contextWindow);P.subtype==="success"?S=P.result||S:(D=P.subtype,"errors"in P&&Array.isArray(P.errors)&&(D=P.errors.join("; ")))}}}catch(V){let L=String(V);S&&v>0?R.warn("Agent process crashed after producing response \u2014 using response anyway",{agent:this.agentConfig.id,error:L,resultPreview:S.slice(0,200),costUsd:v,durationMs:I}):(D=L,R.error("Agent query failed",{agent:this.agentConfig.id,error:L,costUsd:v,durationMs:I}))}finally{clearTimeout(_i),this.activeQuery=null}os&&me.length>0&&(me[me.length-1].endMs=Date.now());let Xe={};for(let V of me){let L=(V.endMs??Date.now())-V.startMs,P=V.tool.includes("__")?V.tool.split("__")[1]:V.tool;Xe[P]||(Xe[P]={count:0,totalMs:0}),Xe[P].count++,Xe[P].totalMs+=L}let un=Object.entries(Xe).sort((V,L)=>L[1].totalMs-V[1].totalMs).map(([V,L])=>`${V}:${L.count}x/${(L.totalMs/1e3).toFixed(1)}s`).join(", "),Be=me.reduce((V,L)=>V+((L.endMs??Date.now())-L.startMs),0),as=I-Be;return R.info("Agent response complete",{agent:this.agentConfig.id,sessionId:_,costUsd:v,durationMs:I,llmMs:as,toolMs:Be,toolCalls:me.length,toolSummary:un||"none",inputTokens:C,outputTokens:E,cacheReadTokens:G,cacheCreationTokens:W,contextWindow:$,compactions:ce,preCompactTokens:Ie,streamed:N,hasError:!!D}),{text:S,sessionId:_,costUsd:v,durationMs:I,llmMs:as,toolMs:Be,toolCalls:me.length,toolSummary:un||"none",streamed:N,inputTokens:C,outputTokens:E,cacheReadTokens:G,cacheCreationTokens:W,contextWindow:$,compactions:ce,preCompactTokens:Ie,error:D,aborted:this._aborted}}_aborted=!1;get wasAborted(){return this._aborted}abort(){this.activeQuery&&(R.info("Aborting active query",{agent:this.agentConfig.id}),this._aborted=!0,this.activeQuery.close(),this.activeQuery=null)}}});import{writeFileSync as La,mkdirSync as W0}from"node:fs";import{join as _r,extname as Pa}from"node:path";import{tmpdir as G0}from"node:os";function Oh(r){Da=r}async function Mh(r,e){if(!Da)return null;try{let t=r.toString("base64"),s=await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${Nh}:generateContent?key=${Da}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({contents:[{parts:[{inline_data:{mime_type:e,data:t}},{text:"Describe this image in detail. If it contains text, extract all of it. If it's a diagram, architecture drawing, or technical image, describe all labels, relationships, and structure. If it's a screenshot of messages or a conversation, transcribe everything. Be thorough."}]}]})});if(!s.ok){let o=await s.text();return ye.warn("Gemini vision error",{status:s.status,error:o.slice(0,200)}),null}let i=(await s.json()).candidates?.[0]?.content?.parts?.[0]?.text;return i&&ye.info("Image described via Gemini",{model:Nh,chars:i.length}),i||null}catch(t){return ye.warn("Gemini vision failed",{error:t.message}),null}}async function Dh(r,e,t){let s=Pa(e).slice(1).toLowerCase();if(V0.has(s)||t.startsWith("text/"))return{textContent:br(r.toString("utf-8")),isImage:!1};if(s==="pdf"||t==="application/pdf")try{let n=await import("pdf-parse"),o=await(n.default??n)(r);return{textContent:br(o.text),isImage:!1}}catch(n){return ye.warn("PDF parse failed",{name:e,error:n.message}),{textContent:"[PDF \u2014 could not extract text]",isImage:!1}}if(s==="docx"||t==="application/vnd.openxmlformats-officedocument.wordprocessingml.document")try{let i=await(await import("mammoth")).extractRawText({buffer:r});return{textContent:br(i.value),isImage:!1}}catch(n){return ye.warn("DOCX parse failed",{name:e,error:n.message}),{textContent:"[DOCX \u2014 could not extract text]",isImage:!1}}if(s==="xlsx"||s==="xls"||t.includes("spreadsheet"))try{let n=await import("xlsx"),i=n.read(r,{type:"buffer"}),o=i.SheetNames.map(a=>{let l=n.utils.sheet_to_csv(i.Sheets[a]);return`--- Sheet: ${a} ---
|
|
172
172
|
${l}`});return{textContent:br(o.join(`
|
|
173
173
|
|
|
174
174
|
`)),isImage:!1}}catch(n){return ye.warn("XLSX parse failed",{name:e,error:n.message}),{textContent:"[Spreadsheet \u2014 could not extract content]",isImage:!1}}return null}async function Lh(r,e){let t=r.url_private_download||r.url_private;if(!t)return ye.warn("No download URL for file",{id:r.id,name:r.name}),null;try{ye.info("Downloading file",{id:r.id,url:t.slice(0,80),mimetype:r.mimetype});let s=await fetch(t,{headers:{Authorization:`Bearer ${e}`},redirect:"manual"});if(s.status===302||s.status===301){let d=s.headers.get("location");d&&(ye.info("Following redirect",{id:r.id}),s=await fetch(d))}if(!s.ok)return ye.error("Failed to download file",{id:r.id,status:s.status}),null;let n=Buffer.from(await s.arrayBuffer());if(n.length<100&&n.length>0){let d=n.toString("utf-8").trim();if(d.includes("requested")&&d.includes("file")&&d.includes("not found"))return ye.error("Slack file error response",{id:r.id,name:r.name,errorText:d,bufferSize:n.length}),null}let i=r.name.replace(/[^a-zA-Z0-9._-]/g,"_"),o=_r(vr,`${r.id}-${i}`);La(o,n);let a=Pa(r.name).slice(1).toLowerCase();if(xh.has(a)||r.mimetype.startsWith("image/")){let d=await Mh(n,r.mimetype);return{name:r.name,mimetype:r.mimetype,size:r.size,localPath:o,textContent:d??"[Image \u2014 could not extract description]",isImage:!0}}let c=await Dh(n,r.name,r.mimetype);return c?{name:r.name,mimetype:r.mimetype,size:r.size,localPath:o,...c}:(ye.info("Unsupported file type",{name:r.name,ext:a,mimetype:r.mimetype}),{name:r.name,mimetype:r.mimetype,size:r.size,localPath:o,textContent:null,isImage:!1})}catch(s){return ye.error("File processing failed",{id:r.id,name:r.name,error:s.message}),null}}async function Vs(r,e,t){let s=e.replace(/[^a-zA-Z0-9._-]/g,"_"),n=_r(vr,`ws-${Date.now()}-${s}`);La(n,r);let i=await Mh(r,t);return{name:e,mimetype:t,size:r.length,localPath:n,textContent:i??"[Image \u2014 could not extract description]",isImage:!0}}async function Ph(r,e,t){let s=e.replace(/[^a-zA-Z0-9._-]/g,"_"),n=_r(vr,`team-${Date.now()}-${s}`);La(n,r);let i=Pa(e).slice(1).toLowerCase();if(xh.has(i)||t.startsWith("image/"))return Vs(r,e,t);let a=await Dh(r,e,t);return a?{name:e,mimetype:t,size:r.length,localPath:n,...a}:{name:e,mimetype:t,size:r.length,localPath:n,textContent:null,isImage:!1}}function $h(r){return r.length===0?"":`
|
|
@@ -269,7 +269,7 @@ A task is done when ALL of these are true:
|
|
|
269
269
|
- **Always** verify PR + CI status before closing a ticket
|
|
270
270
|
- **Always** file a ticket before delegating work`),s.join(`
|
|
271
271
|
|
|
272
|
-
`)}function d0(r){switch(r.type){case"linear":return`Linear (project: ${r.project})`;case"github":return`GitHub Issues (repo: ${r.repo})`;case"clickup":return`ClickUp (list: ${r.list})`;default:return r.type}}import{resolve as u0}from"node:path";import{normalize as zu,resolve as Qu}from"node:path";function Sa(r){if(typeof r!="string"||!r.startsWith("/"))throw new Error(`claudeProjectSlug: path must be absolute, got ${String(r)}`);return r.replace(/\/+$/,"").replace(/[/_.]/g,"-").replace(/-+/g,"-")}function Xu(r,e){let t=zu(Qu(r));return e.workspaces.find(s=>{let n=zu(Qu(s.path));return t===n||t.startsWith(n+"/")})}function Zu(r){return Sa(r.workshop)}function eh(r){return Sa(r.path)}var h0=new Set(["Edit","Write","MultiEdit","NotebookEdit"]);function f0(r,e){return r==="NotebookEdit"?e.notebook_path??e.file_path:e.file_path}function th(r){let e=r.archetypeConfig;return e.workspaces.length===0?[]:[{hooks:[async t=>{try{let s=t,n=s.tool_name??"";if(!h0.has(n))return{continue:!0};let i=f0(n,s.tool_input??{});if(typeof i!="string"||i.length===0)return{continue:!0};let o=u0(e.workshop,i),a=Xu(o,e);return a?{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:`${n} blocked: \`${o}\` is inside workspace \`${a.name}\`. Code changes inside workspaces flow through \`code_task\`, not direct edits \u2014 this preserves the spec \u2192 plan \u2192 PR \u2192 CI discipline. If you're drafting a prototype, work inside the workshop outside any workspace. If you're ready to implement against a ticket, use \`code_task\` with the ticket ID.`}}:{continue:!0}}catch(s){return{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:`Software-engineer hook internal error: ${String(s)}. All file mutations blocked until the archetype is fixed.`}}}}]}]}import{homedir as p0}from"node:os";import{join as sh}from"node:path";function nh(r){let e=r.archetypeConfig,t=p0(),s=[];s.push({id:"workshop",backing:"filesystem",dir:sh(t,".claude/projects",Zu(e),"memory")});for(let n of e.workspaces)s.push({id:`workspace:${n.name}`,backing:"filesystem",dir:sh(t,".claude/projects",eh(n),"memory")});return s}function rh(r){return{cwd:r.archetypeConfig.workshop,settingSources:["project"]}}Ku({id:"software-engineer",description:"Owns codebases and ships production code through disciplined delivery (ticket \u2192 spec \u2192 PR \u2192 CI \u2192 close).",whenToUse:"Pick this when the agent's core job is writing, reviewing, or shipping production code. For product strategists, marketers, or anyone where code is incidental, use a plain agent.",configSchema:{workshop:{type:"string",required:!0,description:"Absolute filesystem path \u2014 the engineer's bounded root directory (e.g. /Users/you/dev)."},workspaces:{type:"array",required:!1,description:"Registered codebases inside the workshop. Do NOT prompt for these at creation time \u2014 workspace registration is a separate admin flow. Start with an empty array."}},validateConfig:Yu,systemPromptCard:Ju,preToolUseHooks:th,memoryScopes:nh,sessionOptions:rh});var fe=k("agent-registry"),ih=3e4,gr=class{agents=new Map;originToAgent=new Map;disabledAgents=[];agentDefs;changeStream=null;pollTimer=null;lastPollTime=new Date(0);onReload;constructor(e,t){this.agentDefs=e,this.onReload=t}async load(){let e=await this.agentDefs.find().toArray(),t=new Set(this.agents.keys()),s=new Set,n=[],i=[],o=[],a=[];for(let l of e){let c=ju(l,p.autonomy);if(s.add(c.id),c.disabled){a.push(c),this.agents.has(c.id)&&(this.agents.delete(c.id),o.push(c.id),fe.info("Disabled agent removed from active map",{id:c.id}));continue}if(c.archetype){let d=pr(c.archetype);if(!d)fe.warn("Unknown archetype \u2014 loading agent as unstructured",{id:c.id,archetype:c.archetype}),c.archetype=void 0,c.archetypeConfig=void 0;else try{c.archetypeConfig=d.validateConfig(c.archetypeConfig)}catch(u){fe.error("Archetype config validation failed \u2014 agent will not load",{id:c.id,archetype:c.archetype,error:String(u)}),this.agents.has(c.id)&&(this.agents.delete(c.id),o.push(c.id),fe.warn("Evicted previously-loaded agent due to archetype validation failure",{id:c.id}));continue}}t.has(c.id)?i.push(c.id):n.push(c.id),this.agents.set(c.id,c),fe.info("Loaded agent",{id:c.id,name:c.name})}this.disabledAgents=a;for(let l of t)s.has(l)||(this.agents.delete(l),o.push(l),fe.info("Removed agent",{id:l}));return this.lastPollTime=new Date,this.rebuildOriginIndex(),{added:n,updated:i,removed:o}}async startWatching(){try{this.changeStream=this.agentDefs.watch([],{fullDocument:"updateLookup"}),this.changeStream.on("change",()=>{fe.info("Agent definition changed (change stream), triggering reload"),this.onReload?.()}),this.changeStream.on("error",e=>{fe.warn("Change stream error, falling back to polling",{error:String(e)}),this.changeStream=null,this.startPolling()}),fe.info("Agent registry watching via change stream")}catch{fe.info("Change stream not available, using polling fallback"),this.startPolling()}}startPolling(){this.pollTimer=setInterval(async()=>{try{let e=await this.agentDefs.countDocuments({updatedAt:{$gt:this.lastPollTime}});e>0&&(fe.info("Agent definitions changed (poll), triggering reload",{changed:e}),this.onReload?.())}catch(e){fe.error("Poll check failed",{error:String(e)})}},ih),fe.info("Agent registry watching via polling",{intervalMs:ih})}rebuildOriginIndex(){this.originToAgent.clear();let e=[...this.agents.values()].sort((t,s)=>t.id.localeCompare(s.id));for(let t of e)for(let s of t.catches??[]){if(this.originToAgent.has(s)){fe.error("Origin conflict \u2014 first sorted agent wins",{origin:s,winner:this.originToAgent.get(s),loser:t.id});continue}this.originToAgent.set(s,t.id)}}stopWatching(){this.changeStream&&(this.changeStream.close().catch(()=>{}),this.changeStream=null),this.pollTimer&&(clearInterval(this.pollTimer),this.pollTimer=null)}get(e){return this.agents.get(e)}getAll(){return Array.from(this.agents.values())}async getAllDefinitions(){return this.agentDefs.find().toArray()}listIds(){return Array.from(this.agents.keys())}findByChannel(e){return this.getAll().find(t=>!t.disabled&&t.channels.includes(e))}findByOrigin(e){let t=this.originToAgent.get(e);return t?this.agents.get(t):void 0}isPassiveChannel(e){return this.getAll().some(t=>!t.disabled&&t.passiveChannels.includes(e))}findByKeyword(e){let t=e.toLowerCase();return this.getAll().find(s=>!s.disabled&&s.keywords.some(n=>{let i=n.toLowerCase().replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return new RegExp(`\\b${i}\\b`).test(t)}))}findByName(e){return this.findAllByName(e)[0]}findAllByName(e){return this.getAll().filter(t=>{if(t.disabled)return!1;if(this.matchesName(t.name,e))return!0;if(t.name.includes(" ")){let s=t.name.split(" ")[0];if(this.matchesName(s,e))return!0}for(let s of t.aliases)if(this.matchesName(s,e))return!0;return!1})}matchesName(e,t){let s=e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return new RegExp(`(?:^|hey\\s+|@)${s}\\b|\\b${s}[,:]`,"i").test(t)}getDefault(){return this.getAll().find(e=>!e.disabled&&e.isDefault)}getDisabled(){return this.disabledAgents}getSubscriberMap(){let e={};for(let t of this.getAll())if(!t.disabled)for(let s of t.subscribe??[]){let n=s.includes(":")?s.split(":")[0]:s;e[n]||(e[n]=[]),e[n].includes(t.id)||e[n].push(t.id)}return e}};O();Ma();Ys();O();Ne();import{query as Y0}from"@anthropic-ai/claude-agent-sdk";var Js=k("model-router"),J0={haiku:{timeoutMs:12e4,maxTurns:20,budgetUsd:1},sonnet:{timeoutMs:3e5,maxTurns:50,budgetUsd:5},opus:{timeoutMs:6e5,maxTurns:200,budgetUsd:50}};function Tr(r,e){let t=J0[r],s=e?.[r];return s?{timeoutMs:s.timeoutMs??t.timeoutMs,maxTurns:s.maxTurns??t.maxTurns,budgetUsd:s.budgetUsd??t.budgetUsd}:{...t}}var ft={haiku:0,sonnet:1,opus:2},Ir={haiku:"claude-haiku-4-5-20251001",sonnet:"claude-sonnet-4-6",opus:"claude-opus-4-
|
|
272
|
+
`)}function d0(r){switch(r.type){case"linear":return`Linear (project: ${r.project})`;case"github":return`GitHub Issues (repo: ${r.repo})`;case"clickup":return`ClickUp (list: ${r.list})`;default:return r.type}}import{resolve as u0}from"node:path";import{normalize as zu,resolve as Qu}from"node:path";function Sa(r){if(typeof r!="string"||!r.startsWith("/"))throw new Error(`claudeProjectSlug: path must be absolute, got ${String(r)}`);return r.replace(/\/+$/,"").replace(/[/_.]/g,"-").replace(/-+/g,"-")}function Xu(r,e){let t=zu(Qu(r));return e.workspaces.find(s=>{let n=zu(Qu(s.path));return t===n||t.startsWith(n+"/")})}function Zu(r){return Sa(r.workshop)}function eh(r){return Sa(r.path)}var h0=new Set(["Edit","Write","MultiEdit","NotebookEdit"]);function f0(r,e){return r==="NotebookEdit"?e.notebook_path??e.file_path:e.file_path}function th(r){let e=r.archetypeConfig;return e.workspaces.length===0?[]:[{hooks:[async t=>{try{let s=t,n=s.tool_name??"";if(!h0.has(n))return{continue:!0};let i=f0(n,s.tool_input??{});if(typeof i!="string"||i.length===0)return{continue:!0};let o=u0(e.workshop,i),a=Xu(o,e);return a?{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:`${n} blocked: \`${o}\` is inside workspace \`${a.name}\`. Code changes inside workspaces flow through \`code_task\`, not direct edits \u2014 this preserves the spec \u2192 plan \u2192 PR \u2192 CI discipline. If you're drafting a prototype, work inside the workshop outside any workspace. If you're ready to implement against a ticket, use \`code_task\` with the ticket ID.`}}:{continue:!0}}catch(s){return{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:`Software-engineer hook internal error: ${String(s)}. All file mutations blocked until the archetype is fixed.`}}}}]}]}import{homedir as p0}from"node:os";import{join as sh}from"node:path";function nh(r){let e=r.archetypeConfig,t=p0(),s=[];s.push({id:"workshop",backing:"filesystem",dir:sh(t,".claude/projects",Zu(e),"memory")});for(let n of e.workspaces)s.push({id:`workspace:${n.name}`,backing:"filesystem",dir:sh(t,".claude/projects",eh(n),"memory")});return s}function rh(r){return{cwd:r.archetypeConfig.workshop,settingSources:["project"]}}Ku({id:"software-engineer",description:"Owns codebases and ships production code through disciplined delivery (ticket \u2192 spec \u2192 PR \u2192 CI \u2192 close).",whenToUse:"Pick this when the agent's core job is writing, reviewing, or shipping production code. For product strategists, marketers, or anyone where code is incidental, use a plain agent.",configSchema:{workshop:{type:"string",required:!0,description:"Absolute filesystem path \u2014 the engineer's bounded root directory (e.g. /Users/you/dev)."},workspaces:{type:"array",required:!1,description:"Registered codebases inside the workshop. Do NOT prompt for these at creation time \u2014 workspace registration is a separate admin flow. Start with an empty array."}},validateConfig:Yu,systemPromptCard:Ju,preToolUseHooks:th,memoryScopes:nh,sessionOptions:rh});var fe=k("agent-registry"),ih=3e4,gr=class{agents=new Map;originToAgent=new Map;disabledAgents=[];agentDefs;changeStream=null;pollTimer=null;lastPollTime=new Date(0);onReload;constructor(e,t){this.agentDefs=e,this.onReload=t}async load(){let e=await this.agentDefs.find().toArray(),t=new Set(this.agents.keys()),s=new Set,n=[],i=[],o=[],a=[];for(let l of e){let c=ju(l,p.autonomy);if(s.add(c.id),c.disabled){a.push(c),this.agents.has(c.id)&&(this.agents.delete(c.id),o.push(c.id),fe.info("Disabled agent removed from active map",{id:c.id}));continue}if(c.archetype){let d=pr(c.archetype);if(!d)fe.warn("Unknown archetype \u2014 loading agent as unstructured",{id:c.id,archetype:c.archetype}),c.archetype=void 0,c.archetypeConfig=void 0;else try{c.archetypeConfig=d.validateConfig(c.archetypeConfig)}catch(u){fe.error("Archetype config validation failed \u2014 agent will not load",{id:c.id,archetype:c.archetype,error:String(u)}),this.agents.has(c.id)&&(this.agents.delete(c.id),o.push(c.id),fe.warn("Evicted previously-loaded agent due to archetype validation failure",{id:c.id}));continue}}t.has(c.id)?i.push(c.id):n.push(c.id),this.agents.set(c.id,c),fe.info("Loaded agent",{id:c.id,name:c.name})}this.disabledAgents=a;for(let l of t)s.has(l)||(this.agents.delete(l),o.push(l),fe.info("Removed agent",{id:l}));return this.lastPollTime=new Date,this.rebuildOriginIndex(),{added:n,updated:i,removed:o}}async startWatching(){try{this.changeStream=this.agentDefs.watch([],{fullDocument:"updateLookup"}),this.changeStream.on("change",()=>{fe.info("Agent definition changed (change stream), triggering reload"),this.onReload?.()}),this.changeStream.on("error",e=>{fe.warn("Change stream error, falling back to polling",{error:String(e)}),this.changeStream=null,this.startPolling()}),fe.info("Agent registry watching via change stream")}catch{fe.info("Change stream not available, using polling fallback"),this.startPolling()}}startPolling(){this.pollTimer=setInterval(async()=>{try{let e=await this.agentDefs.countDocuments({updatedAt:{$gt:this.lastPollTime}});e>0&&(fe.info("Agent definitions changed (poll), triggering reload",{changed:e}),this.onReload?.())}catch(e){fe.error("Poll check failed",{error:String(e)})}},ih),fe.info("Agent registry watching via polling",{intervalMs:ih})}rebuildOriginIndex(){this.originToAgent.clear();let e=[...this.agents.values()].sort((t,s)=>t.id.localeCompare(s.id));for(let t of e)for(let s of t.catches??[]){if(this.originToAgent.has(s)){fe.error("Origin conflict \u2014 first sorted agent wins",{origin:s,winner:this.originToAgent.get(s),loser:t.id});continue}this.originToAgent.set(s,t.id)}}stopWatching(){this.changeStream&&(this.changeStream.close().catch(()=>{}),this.changeStream=null),this.pollTimer&&(clearInterval(this.pollTimer),this.pollTimer=null)}get(e){return this.agents.get(e)}getAll(){return Array.from(this.agents.values())}async getAllDefinitions(){return this.agentDefs.find().toArray()}listIds(){return Array.from(this.agents.keys())}findByChannel(e){return this.getAll().find(t=>!t.disabled&&t.channels.includes(e))}findByOrigin(e){let t=this.originToAgent.get(e);return t?this.agents.get(t):void 0}isPassiveChannel(e){return this.getAll().some(t=>!t.disabled&&t.passiveChannels.includes(e))}findByKeyword(e){let t=e.toLowerCase();return this.getAll().find(s=>!s.disabled&&s.keywords.some(n=>{let i=n.toLowerCase().replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return new RegExp(`\\b${i}\\b`).test(t)}))}findByName(e){return this.findAllByName(e)[0]}findAllByName(e){return this.getAll().filter(t=>{if(t.disabled)return!1;if(this.matchesName(t.name,e))return!0;if(t.name.includes(" ")){let s=t.name.split(" ")[0];if(this.matchesName(s,e))return!0}for(let s of t.aliases)if(this.matchesName(s,e))return!0;return!1})}matchesName(e,t){let s=e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return new RegExp(`(?:^|hey\\s+|@)${s}\\b|\\b${s}[,:]`,"i").test(t)}getDefault(){return this.getAll().find(e=>!e.disabled&&e.isDefault)}getDisabled(){return this.disabledAgents}getSubscriberMap(){let e={};for(let t of this.getAll())if(!t.disabled)for(let s of t.subscribe??[]){let n=s.includes(":")?s.split(":")[0]:s;e[n]||(e[n]=[]),e[n].includes(t.id)||e[n].push(t.id)}return e}};O();Ma();Ys();O();Ne();import{query as Y0}from"@anthropic-ai/claude-agent-sdk";var Js=k("model-router"),J0={haiku:{timeoutMs:12e4,maxTurns:20,budgetUsd:1},sonnet:{timeoutMs:3e5,maxTurns:50,budgetUsd:5},opus:{timeoutMs:6e5,maxTurns:200,budgetUsd:50}};function Tr(r,e){let t=J0[r],s=e?.[r];return s?{timeoutMs:s.timeoutMs??t.timeoutMs,maxTurns:s.maxTurns??t.maxTurns,budgetUsd:s.budgetUsd??t.budgetUsd}:{...t}}var ft={haiku:0,sonnet:1,opus:2},Ir={haiku:"claude-haiku-4-5-20251001",sonnet:"claude-sonnet-4-6",opus:"claude-opus-4-7"};function z0(r){return r.includes("opus")?"opus":r.includes("haiku")?"haiku":"sonnet"}var Q0=`You are a model router. Your job is to classify the complexity of a user message and decide which AI model tier should handle it.
|
|
273
273
|
|
|
274
274
|
Tiers:
|
|
275
275
|
- **haiku**: Greetings, simple factual questions, acknowledgments, status checks, yes/no answers, brief lookups, routine updates. Fast and cheap.
|
|
@@ -282,7 +282,7 @@ Rules:
|
|
|
282
282
|
- Look at the TASK complexity, not the message length.
|
|
283
283
|
- Scheduled/cron tasks that say "execute your scheduled X task" are routine \u2192 haiku unless the task itself is complex.
|
|
284
284
|
|
|
285
|
-
Respond with ONLY a JSON object: { "tier": "haiku" | "sonnet" | "opus" }`;function X0(r){try{let s=JSON.parse(r);if(s.tier&&ft[s.tier]!==void 0)return s.tier}catch{}let e=r.indexOf("{"),t=r.lastIndexOf("}");if(e!==-1&&t>e)try{let s=JSON.parse(r.slice(e,t+1));if(s.tier&&ft[s.tier]!==void 0)return s.tier}catch{}return null}async function Bh(r,e,t){let s=z0(e),n=p.modelRouter.model;if(s==="haiku")return{tier:"haiku",model:Ir.haiku,costUsd:0,durationMs:0,resourceLimits:Tr("haiku",t)};let i=null,o="",a=0,l=0,c=setTimeout(()=>{i&&(Js.warn("Model router timed out",{timeoutMs:p.modelRouter.timeoutMs}),i.close())},p.modelRouter.timeoutMs);try{i=Y0({prompt:r,options:{model:n,systemPrompt:Q0,permissionMode:"bypassPermissions",allowDangerouslySkipPermissions:!0,maxTurns:1,maxBudgetUsd:.01,persistSession:!1,
|
|
285
|
+
Respond with ONLY a JSON object: { "tier": "haiku" | "sonnet" | "opus" }`;function X0(r){try{let s=JSON.parse(r);if(s.tier&&ft[s.tier]!==void 0)return s.tier}catch{}let e=r.indexOf("{"),t=r.lastIndexOf("}");if(e!==-1&&t>e)try{let s=JSON.parse(r.slice(e,t+1));if(s.tier&&ft[s.tier]!==void 0)return s.tier}catch{}return null}async function Bh(r,e,t){let s=z0(e),n=p.modelRouter.model;if(s==="haiku")return{tier:"haiku",model:Ir.haiku,costUsd:0,durationMs:0,resourceLimits:Tr("haiku",t)};let i=null,o="",a=0,l=0,c=setTimeout(()=>{i&&(Js.warn("Model router timed out",{timeoutMs:p.modelRouter.timeoutMs}),i.close())},p.modelRouter.timeoutMs);try{i=Y0({prompt:r,options:{model:n,systemPrompt:Q0,permissionMode:"bypassPermissions",allowDangerouslySkipPermissions:!0,maxTurns:1,maxBudgetUsd:.01,persistSession:!1,disallowedTools:["Bash","Read","Write","Edit","Glob","Grep","Agent","WebFetch","WebSearch","NotebookEdit"],env:{...process.env,...p.anthropic.apiKey?{ANTHROPIC_API_KEY:p.anthropic.apiKey}:{},CLAUDE_AGENT_SDK_CLIENT_APP:"hive/0.1.0",CLAUDECODE:void 0}}});for await(let h of i){let m=h;if(m.type==="assistant"){let g=m.message?.content;if(Array.isArray(g))for(let f of g)f.type==="text"&&(o=f.text)}if(m.type==="result"){let g=m;a=g.total_cost_usd,l=g.duration_ms,g.subtype==="success"&&g.result&&(o=g.result)}}}catch(h){Js.warn("Model router query failed, defaulting to sonnet",{error:String(h)});let m=ft[s]>=ft.sonnet?"sonnet":s;return{tier:m,model:Ir[m],costUsd:0,durationMs:0,resourceLimits:Tr(m,t)}}finally{clearTimeout(c),i=null}let d=X0(o);if(!d){Js.warn("Model router parse failed, defaulting to sonnet",{rawText:o.slice(0,200)});let h=ft[s]>=ft.sonnet?"sonnet":s;return{tier:h,model:Ir[h],costUsd:0,durationMs:0,resourceLimits:Tr(h,t)}}let u=d;return ft[d]>ft[s]&&(u=s,Js.debug("Model router capped by ceiling",{requested:d,ceiling:s})),Js.info("Model router decision",{tier:u,requested:d,ceiling:s,costUsd:a,durationMs:l,textPreview:r.slice(0,100)}),{tier:u,model:Ir[u],costUsd:a,durationMs:l,resourceLimits:Tr(u,t)}}Ne();Sr();Ea();ls();import{existsSync as Uh,readdirSync as tk,statSync as sk}from"node:fs";import{join as qh}from"node:path";Cr();import{QdrantClient as ek}from"@qdrant/js-client-rest";var Ar="conversations",Er=class{qdrantUrl;ollamaUrl;qdrant=null;collectionReady=!1;constructor(e,t){this.qdrantUrl=e??process.env.QDRANT_URL??"http://localhost:6333",this.ollamaUrl=t??process.env.OLLAMA_URL??"http://localhost:11434"}getClient(){return this.qdrant||(this.qdrant=new ek({url:this.qdrantUrl})),this.qdrant}async ensureCollection(){if(this.collectionReady)return;let e=this.getClient(),{collections:t}=await e.getCollections();if(!t.some(n=>n.name===Ar)){let i=(await He(this.ollamaUrl,"test")).length;await e.createCollection(Ar,{vectors:{size:i,distance:"Cosine"}})}this.collectionReady=!0}async index(e){await this.ensureCollection();let t=this.getClient(),s=e.inbound+`
|
|
286
286
|
|
|
287
287
|
`+e.response,n=await He(this.ollamaUrl,s),i=crypto.randomUUID();await t.upsert(Ar,{points:[{id:i,vector:n,payload:{agentId:e.agentId,threadId:e.threadId,channelId:e.channelId,source:e.source,senderName:e.senderName,timestampUnix:e.timestampUnix,timestamp:e.timestamp,inbound:e.inbound,response:e.response}}]})}async search(e,t,s,n){await this.ensureCollection();let i=this.getClient(),o=await He(this.ollamaUrl,e),a=[{key:"agentId",match:{value:t}}];return n!==void 0&&a.push({key:"timestampUnix",range:{gte:n}}),(await i.search(Ar,{vector:o,limit:s,with_payload:!0,filter:{must:a}})).map(c=>({agentId:c.payload?.agentId,threadId:c.payload?.threadId,channelId:c.payload?.channelId,source:c.payload?.source,senderName:c.payload?.senderName,timestampUnix:c.payload?.timestampUnix,timestamp:c.payload?.timestamp,inbound:c.payload?.inbound,response:c.payload?.response,score:c.score}))}};var ae=k("agent-manager"),nk=new Er;function rk(r){if(!Uh(r))return[];try{return tk(r).map(e=>qh(r,e)).filter(e=>{try{return sk(e).isDirectory()&&Uh(qh(e,"skills"))}catch{return!1}})}catch{return[]}}var Nr=class{states=new Map;queues=new Map;processing=new Set;activeRunners=new Map;activeThreads=new Map;registry;memoryManager;sessionStore;plugins;seedDirs;skillIndex;activityLogger;prefetcher;activeWorkItems=new Map;spawnWindow=new Map;constructor(e,t,s,n,i){this.registry=e,this.memoryManager=t,this.sessionStore=s,this.activityLogger=n,this.prefetcher=i,this.plugins=ba(p.plugins,z,{distDir:Ke}),this.seedDirs=rk(Ic),this.skillIndex=Aa(Ze,this.plugins,this.seedDirs)}getPlugins(){return this.plugins}getActiveWorkItems(e){return this.activeWorkItems.get(e)??[]}createRunner(e){let t=this.registry.get(e);if(!t)throw new Error(`Unknown agent: ${e}`);let s=JSON.stringify(this.registry.getSubscriberMap());return new Gs(t,this.memoryManager,this.plugins,this.skillIndex,s,this.prefetcher)}reloadSkills(){try{this.skillIndex=Aa(Ze,this.plugins,this.seedDirs)}catch(e){ae.warn("Skill reload failed, retaining previous index",{error:String(e)})}}rescanPlugins(){let{rescued:e,stillBroken:t}=_a(this.plugins,z,{distDir:Ke});Object.values(e).reduce((i,o)=>i+o.length,0)>0&&ae.info("Plugin MCP servers rescued after rescan",{rescued:e}),Object.values(t).reduce((i,o)=>i+o.length,0)>0&&ae.warn("Plugin MCP servers still unresolvable after rescan",{stillBroken:t})}recordSpawn(e){let t=Date.now(),s=6e4,n=(this.spawnWindow.get(e)??[]).filter(i=>t-i<s);if(n.push(t),this.spawnWindow.set(e,n),n.length>3&&ae.warn("Session spawn rate exceeded",{channelId:e,count:n.length,windowSec:60}),this.spawnWindow.size>200){let i=this.spawnWindow.keys().next().value;i&&this.spawnWindow.delete(i)}}ensureState(e){this.states.has(e)||this.states.set(e,{id:e,status:"idle",lastActivity:new Date,messagesProcessed:0,errorCount:0,activeThreadCount:0})}async sendMessage(e,t,s){this.ensureState(e);let n=t.threadId??t.id,i=`${e}:${n}`;return new Promise((o,a)=>{let l=this.queues.get(i)??[];l.push({message:t,onStream:s,resolve:o,reject:a}),this.queues.set(i,l),this.processThreadQueue(e,i).catch(c=>{ae.error("processThreadQueue failed unexpectedly",{agentId:e,threadKey:i,error:String(c)}),this.processing.delete(i);let d=this.activeThreads.get(e);d&&(d.delete(i),this.updateThreadCount(e),d.size===0&&this.updateStatus(e,"idle"));let u=this.queues.get(i);if(u){for(let h of u)h.reject(c instanceof Error?c:new Error(String(c)));this.queues.delete(i)}this.retryDeferredThreads(e)})})}async processThreadQueue(e,t){if(this.processing.has(t))return;let s=this.queues.get(t);if(!s||s.length===0)return;let n=this.registry.get(e),i=n?.maxConcurrent??3,o=this.activeThreads.get(e)??new Set;if(o.size>=i){ae.debug("Agent at concurrency limit, deferring",{agentId:e,threadKey:t,active:o.size,limit:i});return}this.processing.add(t),o.add(t),this.activeThreads.set(e,o),this.updateStatus(e,"processing"),this.updateThreadCount(e);let a=this.createRunner(e),l=this.activeRunners.get(e)??new Set;l.add(a),this.activeRunners.set(e,l);let c=0,d,u;for(;s.length>0;){let g=s.shift(),f=this.activeWorkItems.get(e)??[];f.push(g.message),this.activeWorkItems.set(e,f);try{let y=g.message.threadId??g.message.id,w=await this.sessionStore.get(e,y);w||this.recordSpawn(g.message.source.id);let S={adapterId:g.message.source.adapterId??g.message.source.kind,channelId:g.message.source.id,channelKind:g.message.source.kind,channelLabel:g.message.source.label,threadId:g.message.threadId??g.message.id,slackTs:g.message.meta?.slackTs??"",slackThreadTs:g.message.meta?.slackThreadTs??""},_=g.message.senderName??g.message.sender,v=g.message.source.kind==="team"?g.message.meta?.user:void 0,I;if(v)I=`[user:${v} via ${_} in #${g.message.source.label}]: ${g.message.text}`;else if(g.message.senderName){let W=g.message.meta?.slackThreadTs,$=g.message.meta?.slackTs,ce=W??$,Ie=ce?`, thread=${ce}`:"";I=`[${_} in #${g.message.source.label}${Ie}]: ${g.message.text}`}else I=g.message.text;g.message.files?.length&&(I+=$h(g.message.files));let N,D=0,C;if(p.modelRouter.enabled&&g.message.sender!=="system")try{let W=this.registry.get(e);if(W){let $=await Bh(g.message.text,W.model,W.resourceTiers);N=$.model!==W.model?$.model:void 0,D=$.costUsd,C=$.resourceLimits}}catch(W){ae.warn("Model router failed, using default",{agentId:e,error:String(W)})}let E=await a.send(I,w,g.onStream,S,N,C);E.costUsd+=D,E.sessionId&&!E.aborted&&this.sessionStore.set(e,y,E.sessionId,{inputTokens:E.inputTokens,outputTokens:E.outputTokens,cacheReadTokens:E.cacheReadTokens,cacheCreationTokens:E.cacheCreationTokens,contextWindow:E.contextWindow,compactions:E.compactions,preCompactTokens:E.preCompactTokens});let G=this.states.get(e);G.messagesProcessed++,G.lastActivity=new Date,G.currentSessionId=E.sessionId,E.error&&(G.errorCount++,w&&this.sessionStore.delete(e,y)),g.resolve(E),c++,d=g,u=E,E.text&&!E.error&&nk.index({agentId:e,threadId:y,channelId:g.message.source.id,source:g.message.source.kind,senderName:g.message.senderName??"unknown",timestampUnix:Math.floor(Date.now()/1e3),timestamp:new Date().toISOString(),inbound:I,response:E.text}).catch(W=>ae.warn("Conversation indexing failed",{agentId:e,error:String(W)})),this.activityLogger?.record({agentId:e,threadId:y,timestamp:new Date,sender:g.message.sender,senderName:g.message.senderName,channel:g.message.source.label,channelKind:g.message.source.kind,model:N??n?.model??"unknown",modelTier:void 0,costUsd:E.costUsd,durationMs:E.durationMs,inputTokens:E.inputTokens,outputTokens:E.outputTokens,contextWindow:E.contextWindow,toolCalls:E.toolCalls,toolSummary:E.toolSummary,compactions:E.compactions,streamed:E.streamed,error:E.error})}catch(y){let w=this.states.get(e);w&&(w.errorCount++,w.lastActivity=new Date),this.activityLogger?.record({agentId:e,threadId:g.message.threadId??g.message.id,timestamp:new Date,sender:g.message.sender,senderName:g.message.senderName,channel:g.message.source.label,channelKind:g.message.source.kind,model:n?.model??"unknown",costUsd:0,durationMs:0,inputTokens:0,outputTokens:0,contextWindow:0,toolCalls:0,toolSummary:"none",compactions:0,streamed:!1,error:String(y)}),g.reject(y instanceof Error?y:new Error(String(y)))}finally{let y=(this.activeWorkItems.get(e)??[]).filter(w=>w.id!==g.message.id);y.length===0?this.activeWorkItems.delete(e):this.activeWorkItems.set(e,y)}}let h=[...n?.coreServers??[],...n?.delegateServers??[]];if((h.includes("memory")||h.includes("structured-memory"))&&u&&d&&!u.error&&!u.aborted&&c>=p.memory.reflectionMinTurns&&d.message.sender!=="system")try{let g=["[System \u2014 end of conversation reflection]","This conversation is wrapping up. Review what was discussed:","- Were any new facts, decisions, or commitments made?","- Did anything contradict or update what you previously knew?","- Should any existing memories be updated or forgotten?","","If yes, use memory_save, memory_update, or memory_forget now.","If nothing worth saving, do nothing."].join(`
|
|
288
288
|
`),f=await a.send(g,u.sessionId);if(ae.info("Reflection completed",{agentId:e,threadKey:t,turnCount:c,costUsd:f.costUsd,toolCalls:f.toolCalls,toolSummary:f.toolSummary||void 0}),f.sessionId&&!f.aborted){let y=d.message.threadId??d.message.id;this.sessionStore.set(e,y,f.sessionId,{inputTokens:f.inputTokens,outputTokens:f.outputTokens,cacheReadTokens:f.cacheReadTokens,cacheCreationTokens:f.cacheCreationTokens,contextWindow:f.contextWindow,compactions:f.compactions,preCompactTokens:f.preCompactTokens})}}catch(g){ae.warn("Reflection failed, non-critical",{agentId:e,threadKey:t,error:String(g)})}l.delete(a),this.processing.delete(t),o.delete(t),this.queues.delete(t),this.updateThreadCount(e),o.size===0&&this.updateStatus(e,"idle"),this.retryDeferredThreads(e)}updateThreadCount(e){let t=this.states.get(e);t&&(t.activeThreadCount=this.activeThreads.get(e)?.size??0)}retryDeferredThreads(e){let t=`${e}:`;for(let[s,n]of this.queues)if(s.startsWith(t)&&n.length>0&&!this.processing.has(s)){this.processThreadQueue(e,s);break}}updateStatus(e,t){let s=this.states.get(e);s&&(s.status=t,ae.debug("Agent status changed",{agentId:e,status:t}))}getState(e){return this.states.get(e)}getAllStates(){return Array.from(this.states.values())}stopAgent(e){let t=this.activeRunners.get(e);if(t){for(let s of t)s.abort();t.clear()}this.activeRunners.delete(e),this.activeThreads.delete(e),this.updateStatus(e,"stopped")}stopAll(){for(let e of this.states.keys())this.stopAgent(e);ae.info("All agents stopped")}async findAgentForThread(e){return this.sessionStore.findAgentByThread(e)}async findAgentsForThread(e){return this.sessionStore.findAgentsByThread(e)}restartAgent(e){this.stopAgent(e),this.sessionStore.clearAgent(e),this.states.set(e,{id:e,status:"idle",lastActivity:new Date,messagesProcessed:0,errorCount:0,activeThreadCount:0}),ae.info("Agent restarted",{agentId:e})}sweep(){let e=0,t=[];for(let[n,i]of this.states)!this.registry.get(n)&&(i.status==="stopped"||i.status==="idle")&&(this.states.delete(n),this.activeRunners.delete(n),this.activeThreads.delete(n),e++,ae.info("Zombie agent state removed",{agentId:n}));for(let n of this.processing){let i=n.split(":")[0],o=this.activeRunners.get(i);if(!o||o.size===0){this.processing.delete(n);let a=this.activeThreads.get(i);a&&(a.delete(n),this.updateThreadCount(i),a.size===0&&this.updateStatus(i,"idle")),e++,ae.warn("Stuck processing flag cleared",{threadKey:n,agentId:i});let l=this.queues.get(n);l&&l.length>0&&this.processThreadQueue(i,n).catch(c=>{ae.error("Failed to restart stuck queue",{threadKey:n,error:String(c)})})}}let s=new Set;for(let n of this.queues.keys())s.add(n.split(":")[0]);for(let n of s)this.retryDeferredThreads(n);return{component:"agent-manager",pruned:e,retried:0,bytesFreed:0,errors:t}}};O();Ys();import{SocketModeClient as ok}from"@slack/socket-mode";import{WebClient as ak}from"@slack/web-api";O();var ik=k("outbound-ts-cache"),xr=class{entries=new Map;ttlMs;maxSize;constructor(e={}){this.ttlMs=e.ttlMs??12e4,this.maxSize=e.maxSize??1e4}register(e,t){if(this.evictExpired(),this.entries.size>=this.maxSize){let s=this.entries.keys().next().value;s&&this.entries.delete(s)}this.entries.set(this.key(e,t),Date.now()+this.ttlMs)}has(e,t){let s=this.entries.get(this.key(e,t));return s===void 0?!1:s<=Date.now()?(this.entries.delete(this.key(e,t)),!1):!0}size(){return this.entries.size}key(e,t){return`${e}:${t}`}evictExpired(){let e=Date.now(),t=0;for(let[s,n]of this.entries)n<=e&&(this.entries.delete(s),t++);t>0&&ik.debug(`evicted ${t} expired entries`,{evicted:t})}};var K=k("slack-gateway"),Or=class r{socket;web;messageHandler=null;threadStartedHandler=null;threadContextHandler=null;botUserId=null;botId=null;peerBotUserIds=new Set;peerBotIds=new Set;channelNameCache=new Map;channelIdCache=new Map;userNameCache=new Map;outboundTsCache=new xr;integrationChannels=new Set;botToken;constructor(e,t){this.socket=new ok({appToken:e}),this.web=new ak(t),this.botToken=t}addIntegrationChannels(e){for(let t of e)this.integrationChannels.add(t)}addPeerBotIds(e,t){e&&this.peerBotUserIds.add(e),t&&this.peerBotIds.add(t),K.info("Peer bot IDs registered",{peerBotUserIds:[...this.peerBotUserIds],peerBotIds:[...this.peerBotIds]})}get resolvedBotUserId(){return this.botUserId}get resolvedBotId(){return this.botId}onMessage(e){this.messageHandler=e}onThreadStarted(e){this.threadStartedHandler=e}onThreadContextChanged(e){this.threadContextHandler=e}async start(){let e=await this.web.auth.test();this.botUserId=e.user_id,this.botId=e.bot_id??null,K.info("Bot identity resolved",{botUserId:this.botUserId,botId:this.botId}),this.socket.on("message",async({event:t,ack:s})=>{if(await s(),!t||(K.debug("Raw message event",{subtype:t.subtype,bot_id:t.bot_id,user:t.user,channel:t.channel,hasText:!!t.text,hasAttachments:!!t.attachments?.length,hasBlocks:!!t.blocks?.length}),t.user===this.botUserId)||this.peerBotUserIds.has(t.user)||t.bot_id&&t.bot_id===this.botId||t.bot_id&&this.peerBotIds.has(t.bot_id))return;if(t.ts&&t.channel&&this.outboundTsCache.has(t.channel,t.ts)){K.info("Outbound echo suppressed",{channel:t.channel,ts:t.ts});return}if(t.bot_id||t.subtype){let l=await this.resolveChannelName(t.channel);if(!this.integrationChannels.has(l)){K.info("Message filtered (subtype/bot in non-integration channel)",{channel:t.channel,channelName:l,user:t.user,subtype:t.subtype,bot_id:t.bot_id,hasText:!!t.text});return}K.info("Integration message accepted",{channelName:l,subtype:t.subtype,bot_id:t.bot_id})}let n=await this.resolveChannelName(t.channel),i=t.text??"";if(!i){let l=[...t.blocks??[]];for(let c of t.attachments??[])c.blocks&&l.push(...c.blocks),c.text&&(i+=c.text+`
|
|
@@ -326,7 +326,7 @@ Recent thread context:
|
|
|
326
326
|
${e}`),s}async function $a(r,e,t){let s=new Set(e.map(u=>u.agentId));if(e.length===0)return{respondAgentIds:[],costUsd:0,durationMs:0};if(e.length===1)return{respondAgentIds:[e[0].agentId],costUsd:0,durationMs:0};let n=p.modelRouter.model,i=null,o="",a=0,l=0,c=setTimeout(()=>{i&&(Rr.warn("Meeting classifier timed out",{timeoutMs:p.modelRouter.timeoutMs}),i.close())},p.modelRouter.timeoutMs);try{let u=`${Sk(e,t)}
|
|
327
327
|
|
|
328
328
|
Message:
|
|
329
|
-
${r}`;i=gk({prompt:u,options:{model:n,systemPrompt:yk,permissionMode:"bypassPermissions",allowDangerouslySkipPermissions:!0,maxTurns:1,maxBudgetUsd:.01,persistSession:!1,
|
|
329
|
+
${r}`;i=gk({prompt:u,options:{model:n,systemPrompt:yk,permissionMode:"bypassPermissions",allowDangerouslySkipPermissions:!0,maxTurns:1,maxBudgetUsd:.01,persistSession:!1,disallowedTools:["Bash","Read","Write","Edit","Glob","Grep","Agent","WebFetch","WebSearch","NotebookEdit"],env:{...process.env,...p.anthropic.apiKey?{ANTHROPIC_API_KEY:p.anthropic.apiKey}:{},CLAUDE_AGENT_SDK_CLIENT_APP:"hive/0.1.0",CLAUDECODE:void 0}}});for await(let h of i){let m=h;if(m.type==="assistant"){let g=m.message?.content;if(Array.isArray(g))for(let f of g)f.type==="text"&&(o=f.text)}if(m.type==="result"){let g=m;a=g.total_cost_usd,l=g.duration_ms,g.subtype==="success"&&g.result&&(o=g.result)}}}catch(u){return Rr.warn("Meeting classifier query failed, selecting all roster members",{error:String(u)}),{respondAgentIds:[...s],costUsd:0,durationMs:0}}finally{clearTimeout(c),i=null}let d=wk(o,s);return d?(Rr.info("Meeting classifier decision",{respond:d,rosterSize:e.length,costUsd:a,durationMs:l}),{respondAgentIds:d,costUsd:a,durationMs:l}):(Rr.warn("Meeting classifier parse failed, selecting all roster members",{rawText:o.slice(0,200)}),{respondAgentIds:[...s],costUsd:a,durationMs:l})}var q=k("dispatcher"),kk=80,bk=[/^status\??$/i,/^how.{0,20}(everyone|agents?|doing|running)/i,/^health\??$/i,/^system status/i],Fh=[/^no response (requested|needed|required|necessary)\.?$/i,/^\(no response\)$/i,/^n\/a\.?$/i],Br=class r{adapters=new Map;registry;agentManager;healthReporter;defaultAgentId;threadAgentMap=new Map;threadParticipants=new Map;threadAgentLastSeen=new Map;recentMessageIds=new Map;auditAdapter;auditChannelIds;fallbackAuditChannelId;taskLedger;retryQueue;teamStore;slackAdapter;meetingRosters=new Map;meetingReactionTracker=new Map;static DEDUP_TTL_MS=6e4;constructor(e,t,s,n,i){this.registry=e,this.agentManager=t,this.healthReporter=s,this.defaultAgentId=n,this.taskLedger=i}registerAdapter(e){this.adapters.set(e.id,e)}setRetryQueue(e){this.retryQueue=e}setTeamStore(e){this.teamStore=e}setAuditChannel(e,t,s){this.auditAdapter=e,this.auditChannelIds=t,this.fallbackAuditChannelId=s}setSlackAdapter(e){this.slackAdapter=e}async dispatch(e){if(this.recentMessageIds.has(e.id)){q.debug("Duplicate message skipped",{id:e.id,source:e.source.adapterId});return}this.recentMessageIds.set(e.id,Date.now()),this.pruneDedup();let t=e.text.trim();if(t.length<=kk&&bk.some(d=>d.test(t))){q.info("Status query intercepted",{source:e.source.kind,text:t});let d=this.healthReporter.formatForSlack(),u=this.adapters.get(e.source.adapterId??e.source.kind);if(u)try{await u.deliver({text:d,agentId:"system",workItem:e,costUsd:0,durationMs:0})}catch(h){q.warn("Status delivery failed, queuing for retry",{error:String(h)}),this.retryQueue?.enqueue({text:d,agentId:"system",workItem:e,costUsd:0,durationMs:0},u)}return}let s=await this.resolveAgents(e);if(s.length===0){q.warn("No agent found for work item",{source:e.source.kind,label:e.source.label,text:e.text.slice(0,50)});return}let n=s.filter(({agentId:d})=>this.registry.get(d)?.disabled?(q.info("Message dropped \u2014 agent is disabled",{agentId:d,source:e.source.kind}),!1):!0);if(n.length===0)return;if(n.some(d=>d.conferenceMode)){let d=e.threadId??e.id;this.threadAgentLastSeen.set(d,Date.now()),q.info("Conference fan-out",{agents:n.map(u=>u.agentId)}),await Promise.all(n.map(u=>this.dispatchToAgent(e,u)));return}if(n.length>1){let d=e.threadId??e.id;this.threadParticipants.has(d)||this.threadParticipants.set(d,new Set(n.map(u=>u.agentId))),this.threadAgentLastSeen.set(d,Date.now()),q.info("Multi-agent fan-out",{agents:n.map(u=>u.agentId)}),await Promise.all(n.map(u=>this.dispatchToAgent(e,u)));return}let{agentId:o}=n[0],a=e.threadId??e.id;this.threadAgentMap.set(a,o),this.threadAgentLastSeen.set(a,Date.now());let l=this.taskLedger?.shouldTrack(e)??!1;l&&this.taskLedger.onDispatch(e,o).catch(d=>q.warn("Task ledger dispatch failed",{error:String(d)}));let c=this.adapters.get(e.source.adapterId??e.source.kind);await c?.onProcessingStart?.(e,o);try{let d=await this.agentManager.sendMessage(o,e),u=d.text.trim();if(Fh.some(m=>m.test(u)))q.info("Non-response suppressed",{agentId:o,source:e.source.kind,text:u,costUsd:d.costUsd,durationMs:d.durationMs});else{let m={text:d.text||"_No response._",agentId:o,workItem:e,costUsd:d.costUsd,durationMs:d.durationMs,error:d.error};if(c)try{await c.deliver(m)}catch(g){q.warn("Agent response delivery failed, queuing for retry",{error:String(g)}),this.retryQueue?.enqueue(m,c)}l&&this.taskLedger.onComplete(m).catch(g=>q.warn("Task ledger complete failed",{error:String(g)})),this.auditAdapter&&e.source.kind!==this.auditAdapter.kind&&await this.postAuditLog(m),q.info("Work item dispatched",{agentId:o,source:e.source.kind,costUsd:d.costUsd,durationMs:d.durationMs,llmMs:d.llmMs,toolMs:d.toolMs,toolCalls:d.toolCalls,toolSummary:d.toolSummary})}}catch(d){let u={text:`Something went wrong: ${String(d)}`,agentId:o,workItem:e,costUsd:0,durationMs:0,error:String(d)};if(c)try{await c.deliver(u)}catch(h){q.warn("Error delivery failed, queuing for retry",{error:String(h)}),this.retryQueue?.enqueue(u,c)}q.error("Dispatch failed",{agentId:o,error:String(d)})}finally{await c?.onProcessingEnd?.(e,o)}}pruneDedup(){let e=Date.now()-r.DEDUP_TTL_MS;for(let[t,s]of this.recentMessageIds)s<e&&this.recentMessageIds.delete(t)}async resolveAgents(e){let t=e.meta?.targetAgentId;if(t&&this.registry.get(t))return[{agentId:t}];if(e.source.kind==="team")return this.resolveFromTeam(e);let s=e.meta?.origin;if(s){let a=this.registry.findByOrigin(s);return a?[{agentId:a.id}]:(q.warn("Origin not routed",{origin:s,deviceId:e.meta?.deviceId,text:e.text.slice(0,50)}),[])}if(e.source.kind==="slack"&&e.source.label.startsWith("conf-"))return this.resolveConferenceAgents(e);let n=this.registry.findByChannel(e.source.label);if(n)return[{agentId:n.id}];if(e.threadId){let a=this.registry.findAllByName(e.text),l=new Set(a.map(h=>h.id)),c=this.threadParticipants.get(e.threadId);if(c){for(let h of l)c.add(h);return this.threadAgentLastSeen.set(e.threadId,Date.now()),[...c].map(h=>({agentId:h}))}let d=this.threadAgentMap.get(e.threadId);if(d){let h=a.some(m=>m.id!==d);if(l.size>0&&h){let m=new Set([d,...l]);return this.threadParticipants.set(e.threadId,m),this.threadAgentMap.delete(e.threadId),this.threadAgentLastSeen.set(e.threadId,Date.now()),q.info("Thread transitioned to multi-agent",{threadId:e.threadId,participants:[...m]}),[...m].map(g=>({agentId:g}))}return this.threadAgentLastSeen.set(e.threadId,Date.now()),[{agentId:d}]}let u=await this.agentManager.findAgentsForThread(e.threadId);if(u.length>0){let h=u.filter(m=>this.registry.get(m));if(h.length>1){let m=new Set(h);return this.threadParticipants.set(e.threadId,m),this.threadAgentLastSeen.set(e.threadId,Date.now()),[...m].map(g=>({agentId:g}))}if(h.length===1)return this.threadAgentMap.set(e.threadId,h[0]),this.threadAgentLastSeen.set(e.threadId,Date.now()),[{agentId:h[0]}]}}let i=this.registry.findAllByName(e.text);if(i.length>0)return i.map(a=>({agentId:a.id}));let o=e.meta?.defaultAgentId;return o&&this.registry.get(o)?[{agentId:o}]:this.isDirectMessage(e)&&this.registry.get(this.defaultAgentId)?(q.info("DM routed to default agent",{agentId:this.defaultAgentId,channel:e.source.id}),[{agentId:this.defaultAgentId}]):(q.debug("No agent matched \u2014 dropping",{channel:e.source.label}),[])}isDirectMessage(e){return!!(e.source.kind==="slack"&&e.source.id.startsWith("D")||e.meta?.channelType==="im"||e.source.kind==="team"&&typeof e.source.id=="string"&&e.source.id.startsWith("dm:"))}async resolveFromTeam(e){let t=e.meta?.channelId;if(!t||!this.teamStore){let o=e.meta?.defaultAgentId;return o&&this.registry.get(o)?[{agentId:o}]:[]}let s=await this.teamStore.getChannel(t);if(!s)return q.warn("Team channel not found",{channelId:t}),[];if(s.type==="dm"){let o=s.members.find(a=>a!==e.sender);return o&&this.registry.get(o)?[{agentId:o}]:(q.warn("DM agent not found in registry",{channelId:t,members:s.members}),[])}let n=this.registry.findAllByName(e.text);if(n.length>0){let o=new Set(s.members),a=n.filter(l=>o.has(l.id));if(a.length>0)return a.map(l=>({agentId:l.id}))}let i=s.members.filter(o=>this.registry.get(o));return i.length>0?[{agentId:i[0]}]:(q.warn("No agent members in Team channel",{channelId:t}),[])}async dispatchToAgent(e,t){let{agentId:s}=t,n=e;if(t.conferenceMode){let l=[t.meetingPreamble,"",t.threadContext,"","---","[New message]:"].filter(Boolean).join(`
|
|
330
330
|
`);n={...e,text:`${l}
|
|
331
331
|
${e.text}`,meta:{...e.meta,conferenceMode:!0,conferenceHumanTs:t.conferenceHumanTs,conferenceRound:t.conferenceRound}}}let i=n.threadId??n.id;this.threadAgentLastSeen.set(i,Date.now());let o=this.taskLedger?.shouldTrack(n)??!1;o&&this.taskLedger.onDispatch(n,s).catch(l=>q.warn("Task ledger dispatch failed",{error:String(l)}));let a=this.adapters.get(n.source.adapterId??n.source.kind);try{let l=await this.agentManager.sendMessage(s,n),c=l.text.trim(),d=Fh.some(u=>u.test(c));if(d)q.info("Non-response suppressed (fan-out)",{agentId:s});else{let u={text:l.text||"_No response._",agentId:s,workItem:n,costUsd:l.costUsd,durationMs:l.durationMs,error:l.error};if(a)try{await a.deliver(u)}catch{this.retryQueue?.enqueue(u,a)}t.conferenceMode&&t.conferenceRound===0&&!d&&this.triggerConferenceReactions(l.text,e,s,t.conferenceHumanTs).catch(h=>q.warn("Conference reaction trigger failed",{error:String(h)})),o&&this.taskLedger.onComplete(u).catch(h=>q.warn("Task ledger complete failed",{error:String(h)})),this.auditAdapter&&n.source.kind!==this.auditAdapter.kind&&await this.postAuditLog(u),q.info("Fan-out dispatch complete",{agentId:s,costUsd:l.costUsd,durationMs:l.durationMs})}}catch(l){let c={text:`Something went wrong: ${String(l)}`,agentId:s,workItem:n,costUsd:0,durationMs:0,error:String(l)};if(a)try{await a.deliver(c)}catch{this.retryQueue?.enqueue(c,a)}q.error("Fan-out dispatch failed",{agentId:s,error:String(l)})}}sweep(e){let t=Date.now()-e,s=0;for(let[n,i]of this.threadAgentLastSeen)i<t&&(this.threadAgentMap.delete(n),this.threadParticipants.delete(n),this.meetingRosters.delete(n),this.meetingReactionTracker.delete(n),this.threadAgentLastSeen.delete(n),s++);return{component:"dispatcher",pruned:s,retried:0,bytesFreed:0,errors:[]}}async resolveConferenceAgents(e){let t=e.threadId??e.id,s=this.meetingRosters.get(t)??new Set,n=this.registry.findAllByName(e.text);for(let d of n)s.add(d.id);if(this.meetingRosters.set(t,s),this.threadAgentLastSeen.set(t,Date.now()),s.size===0)return q.debug("Conference channel \u2014 no roster yet",{channel:e.source.label,threadId:t}),[];let i=[];for(let d of s){let u=this.registry.get(d);!u||u.disabled||i.push({agentId:u.id,name:u.name,title:u.title,role:u.soul.split(`
|
|
332
332
|
`)[0]})}if(i.length===0)return[];let o="",a="";if(this.slackAdapter){let d=e.source.id,u=e.meta?.slackThreadTs??e.meta?.slackTs??t,h=await this.slackAdapter.fetchThreadHistory(d,u);o=this.formatThreadContext(h,e.source.label,i),a=h.slice(-5).map(m=>`${m.author}: ${m.text.slice(0,200)}`).join(`
|
package/service/deploy-check.sh
CHANGED
|
@@ -16,7 +16,10 @@ set -euo pipefail
|
|
|
16
16
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
17
17
|
BUILD_DIR="${BUILD_DIR:-$HOME/build/hive}"
|
|
18
18
|
DEPLOY_DIR="${DEPLOY_DIR:-$HOME/services/hive}"
|
|
19
|
-
|
|
19
|
+
# HIVE_INSTANCES_CONF (KPR-70) lets multi-instance dev hosts point at a
|
|
20
|
+
# workspace-level conf outside .hive/, so engine swaps don't wipe their
|
|
21
|
+
# registry. The shipped $SCRIPT_DIR/instances.conf is empty by design.
|
|
22
|
+
INSTANCES_CONF="${HIVE_INSTANCES_CONF:-$SCRIPT_DIR/instances.conf}"
|
|
20
23
|
|
|
21
24
|
cd "$BUILD_DIR"
|
|
22
25
|
[[ "$(git branch --show-current)" == "deploy" ]] || { echo "ERROR: Build dir not on deploy branch"; exit 1; }
|
|
@@ -25,6 +28,11 @@ echo "Checking for updates on deploy branch..."
|
|
|
25
28
|
git fetch origin deploy --quiet
|
|
26
29
|
|
|
27
30
|
# --- Load instances ---
|
|
31
|
+
if [[ ! -f "$INSTANCES_CONF" ]]; then
|
|
32
|
+
echo "ERROR: instances.conf not found at $INSTANCES_CONF"
|
|
33
|
+
echo " Set HIVE_INSTANCES_CONF to a workspace-level conf outside .hive/."
|
|
34
|
+
exit 1
|
|
35
|
+
fi
|
|
28
36
|
declare -a INSTANCES=()
|
|
29
37
|
while IFS='|' read -r id config _agents _label logs_dir ports engine_tag; do
|
|
30
38
|
[[ "$id" =~ ^[[:space:]]*# ]] && continue
|
|
@@ -34,6 +42,12 @@ while IFS='|' read -r id config _agents _label logs_dir ports engine_tag; do
|
|
|
34
42
|
INSTANCES+=("$id|${engine_tag:-latest}")
|
|
35
43
|
done < "$INSTANCES_CONF"
|
|
36
44
|
|
|
45
|
+
if [[ ${#INSTANCES[@]} -eq 0 ]]; then
|
|
46
|
+
echo "No instances registered in $INSTANCES_CONF — nothing to check."
|
|
47
|
+
echo "(The shipped service/instances.conf is empty by design; multi-instance dev hosts must use HIVE_INSTANCES_CONF.)"
|
|
48
|
+
exit 0
|
|
49
|
+
fi
|
|
50
|
+
|
|
37
51
|
UPDATES_NEEDED=()
|
|
38
52
|
for inst in "${INSTANCES[@]}"; do
|
|
39
53
|
IFS='|' read -r id tag <<< "$inst"
|
package/service/deploy.sh
CHANGED
|
@@ -9,7 +9,16 @@ set -euo pipefail
|
|
|
9
9
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
10
10
|
BUILD_DIR="${BUILD_DIR:-$HOME/build/hive}"
|
|
11
11
|
DEPLOY_DIR="${DEPLOY_DIR:-$HOME/services/hive}"
|
|
12
|
-
INSTANCES_CONF="$SCRIPT_DIR/instances.conf"
|
|
12
|
+
INSTANCES_CONF="${HIVE_INSTANCES_CONF:-$SCRIPT_DIR/instances.conf}"
|
|
13
|
+
|
|
14
|
+
# Single-instance mode (KPR-70): when invoked by `hive update` for a customer
|
|
15
|
+
# install, the calling instance passes its own facts via env, and we skip the
|
|
16
|
+
# build-from-source phase + the shipped instances.conf entirely. The instance
|
|
17
|
+
# running the update is the only instance to update — no global registry read.
|
|
18
|
+
SINGLE_INSTANCE_MODE=false
|
|
19
|
+
if [[ "${HIVE_SINGLE_INSTANCE:-}" == "1" ]]; then
|
|
20
|
+
SINGLE_INSTANCE_MODE=true
|
|
21
|
+
fi
|
|
13
22
|
|
|
14
23
|
# --- Flags ---
|
|
15
24
|
DRY_RUN=false
|
|
@@ -30,30 +39,48 @@ for arg in "$@"; do
|
|
|
30
39
|
esac
|
|
31
40
|
done
|
|
32
41
|
|
|
33
|
-
# --- Notification config
|
|
34
|
-
#
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
42
|
+
# --- Notification config ---
|
|
43
|
+
# Source $DEPLOY_DIR/.env if present so SLACK_BOT_TOKEN / DEVOPS_CHANNEL_ID
|
|
44
|
+
# can populate from the dev convention. Customer installs have neither file
|
|
45
|
+
# nor tokens — that's fine; notify() degrades to a silent no-op below.
|
|
46
|
+
if [[ -f "$DEPLOY_DIR/.env" ]]; then
|
|
47
|
+
# shellcheck source=/dev/null
|
|
48
|
+
source "$DEPLOY_DIR/.env"
|
|
49
|
+
fi
|
|
38
50
|
|
|
39
51
|
# --- Load instances ---
|
|
40
52
|
declare -a INSTANCES=()
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
if $SINGLE_INSTANCE_MODE; then
|
|
54
|
+
: "${HIVE_SINGLE_ID:?HIVE_SINGLE_ID required in single-instance mode}"
|
|
55
|
+
: "${HIVE_SINGLE_CONFIG:?HIVE_SINGLE_CONFIG required in single-instance mode}"
|
|
56
|
+
: "${HIVE_SINGLE_LOGS:?HIVE_SINGLE_LOGS required in single-instance mode}"
|
|
57
|
+
: "${HIVE_SINGLE_PORTS:?HIVE_SINGLE_PORTS required in single-instance mode}"
|
|
58
|
+
: "${HIVE_SINGLE_ROOT:?HIVE_SINGLE_ROOT required in single-instance mode}"
|
|
59
|
+
INSTANCES+=("$HIVE_SINGLE_ID|$HIVE_SINGLE_CONFIG|com.hive.${HIVE_SINGLE_ID}.agent|$HIVE_SINGLE_LOGS|$HIVE_SINGLE_PORTS|${HIVE_SINGLE_TAG:-}")
|
|
60
|
+
else
|
|
61
|
+
if [[ ! -f "$INSTANCES_CONF" ]]; then
|
|
62
|
+
echo "ERROR: instances.conf not found at $INSTANCES_CONF"
|
|
63
|
+
echo " For single-instance updates, set HIVE_SINGLE_INSTANCE=1 and the HIVE_SINGLE_* env vars."
|
|
64
|
+
echo " For multi-instance dev, point HIVE_INSTANCES_CONF at a workspace-level conf."
|
|
65
|
+
exit 1
|
|
66
|
+
fi
|
|
67
|
+
while IFS='|' read -r id config _agents_path label logs_dir ports engine_tag; do
|
|
68
|
+
[[ "$id" =~ ^[[:space:]]*# ]] && continue # skip comments
|
|
69
|
+
[[ -z "$id" ]] && continue # skip blank lines
|
|
70
|
+
# Trim whitespace
|
|
71
|
+
id=$(echo "$id" | xargs)
|
|
72
|
+
config=$(echo "$config" | xargs)
|
|
73
|
+
label=$(echo "$label" | xargs)
|
|
74
|
+
logs_dir=$(echo "$logs_dir" | xargs)
|
|
75
|
+
ports=$(echo "$ports" | xargs)
|
|
76
|
+
engine_tag=$(echo "${engine_tag:-}" | xargs)
|
|
77
|
+
INSTANCES+=("$id|$config|$label|$logs_dir|$ports|$engine_tag")
|
|
78
|
+
done < "$INSTANCES_CONF"
|
|
79
|
+
|
|
80
|
+
if [[ ${#INSTANCES[@]} -eq 0 ]]; then
|
|
81
|
+
echo "ERROR: No instances found in $INSTANCES_CONF"
|
|
82
|
+
exit 1
|
|
83
|
+
fi
|
|
57
84
|
fi
|
|
58
85
|
|
|
59
86
|
echo "=== Hive Deploy (${#INSTANCES[@]} instances) ==="
|
|
@@ -76,6 +103,11 @@ notify() {
|
|
|
76
103
|
echo "[DRY RUN] notify: $message"
|
|
77
104
|
return
|
|
78
105
|
fi
|
|
106
|
+
# Slack notification is opt-in. Customer installs without devops Slack
|
|
107
|
+
# tokens silently skip — never fail-closed on a missing notification config.
|
|
108
|
+
if [[ -z "${SLACK_BOT_TOKEN:-}" || -z "${DEVOPS_CHANNEL_ID:-}" ]]; then
|
|
109
|
+
return
|
|
110
|
+
fi
|
|
79
111
|
local payload
|
|
80
112
|
payload=$(jq -n --arg channel "$DEVOPS_CHANNEL_ID" --arg text "$message" \
|
|
81
113
|
'{channel: $channel, text: $text}')
|
|
@@ -132,12 +164,17 @@ _normalize_tag() {
|
|
|
132
164
|
}
|
|
133
165
|
|
|
134
166
|
# _instance_root <id>
|
|
135
|
-
# Returns the instance root dir
|
|
136
|
-
#
|
|
137
|
-
#
|
|
138
|
-
#
|
|
167
|
+
# Returns the instance root dir. In single-instance mode (KPR-70) the caller
|
|
168
|
+
# tells us via HIVE_SINGLE_ROOT — we don't probe DEPLOY_DIR. Otherwise:
|
|
169
|
+
# $DEPLOY_DIR/<id> if that dir exists (post-Phase-5 per-instance layout), else
|
|
170
|
+
# $DEPLOY_DIR (today's primary-shared-dir layout). Lets deploy.sh be
|
|
171
|
+
# per-instance-dir aware now so Phase 5's migration is a no-op at the script level.
|
|
139
172
|
_instance_root() {
|
|
140
173
|
local id="$1"
|
|
174
|
+
if $SINGLE_INSTANCE_MODE; then
|
|
175
|
+
echo "$HIVE_SINGLE_ROOT"
|
|
176
|
+
return
|
|
177
|
+
fi
|
|
141
178
|
if [[ -d "$DEPLOY_DIR/$id" ]]; then
|
|
142
179
|
echo "$DEPLOY_DIR/$id"
|
|
143
180
|
else
|
|
@@ -330,62 +367,76 @@ fi
|
|
|
330
367
|
# oscillates between versions on every poll. Compares the configured pins
|
|
331
368
|
# (not the effective tag for this run) so even an explicit --tag override
|
|
332
369
|
# can't sneak past a misconfigured pinning state. Fail fast before any work.
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
370
|
+
# Skipped in single-instance mode: one row can't conflict with itself.
|
|
371
|
+
if ! $SINGLE_INSTANCE_MODE; then
|
|
372
|
+
declare -a _seen_roots=()
|
|
373
|
+
declare -a _seen_pins=()
|
|
374
|
+
for inst in "${INSTANCES[@]}"; do
|
|
375
|
+
IFS='|' read -r _id _config _label _logs _ports _engine_tag <<< "$inst"
|
|
376
|
+
_root=$(_instance_root "$_id")
|
|
377
|
+
_pin="${_engine_tag:-latest}"
|
|
378
|
+
for i in "${!_seen_roots[@]}"; do
|
|
379
|
+
if [[ "${_seen_roots[$i]}" == "$_root" && "${_seen_pins[$i]}" != "$_pin" ]]; then
|
|
380
|
+
echo "ERROR: instances share root '$_root' but pin different ENGINE_TAGs ('${_seen_pins[$i]}' vs '$_pin')." >&2
|
|
381
|
+
echo " Set the same ENGINE_TAG for all instances under one root, or migrate them to per-instance dirs (Phase 5)." >&2
|
|
382
|
+
exit 2
|
|
383
|
+
fi
|
|
384
|
+
done
|
|
385
|
+
_seen_roots+=("$_root")
|
|
386
|
+
_seen_pins+=("$_pin")
|
|
345
387
|
done
|
|
346
|
-
_seen_roots
|
|
347
|
-
|
|
348
|
-
done
|
|
349
|
-
unset _seen_roots _seen_pins _id _config _label _logs _ports _engine_tag _root _pin i
|
|
388
|
+
unset _seen_roots _seen_pins _id _config _label _logs _ports _engine_tag _root _pin i
|
|
389
|
+
fi
|
|
350
390
|
|
|
351
391
|
# =============================================================================
|
|
352
392
|
# Phase 1: Build (once, in $BUILD_DIR)
|
|
353
393
|
# =============================================================================
|
|
394
|
+
# Skipped in single-instance mode (KPR-70): customer installs consume the
|
|
395
|
+
# published @keepur/hive npm tarball via fetch_engine in Phase 2 — there is no
|
|
396
|
+
# $BUILD_DIR, no `deploy` branch, nothing to rebuild from source.
|
|
354
397
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
398
|
+
DEPLOY_SHA=""
|
|
399
|
+
DEPLOY_MSG=""
|
|
400
|
+
|
|
401
|
+
if $SINGLE_INSTANCE_MODE; then
|
|
402
|
+
echo ""
|
|
403
|
+
echo "--- Phase 1: Build (skipped in single-instance mode) ---"
|
|
404
|
+
else
|
|
405
|
+
echo ""
|
|
406
|
+
echo "--- Phase 1: Build ---"
|
|
407
|
+
# Per-instance current versions are reported in Phase 2 after each health check.
|
|
358
408
|
|
|
359
|
-
echo "Pulling latest in build dir..."
|
|
360
|
-
cd "$BUILD_DIR"
|
|
361
|
-
[[ "$(git branch --show-current)" == "deploy" ]] || { echo "ERROR: Build dir not on deploy branch"; exit 1; }
|
|
362
|
-
run_cmd git pull --ff-only
|
|
409
|
+
echo "Pulling latest in build dir..."
|
|
410
|
+
cd "$BUILD_DIR"
|
|
411
|
+
[[ "$(git branch --show-current)" == "deploy" ]] || { echo "ERROR: Build dir not on deploy branch"; exit 1; }
|
|
412
|
+
run_cmd git pull --ff-only
|
|
363
413
|
|
|
364
|
-
DEPLOY_SHA=$(git rev-parse --short HEAD)
|
|
365
|
-
DEPLOY_MSG=$(git log -1 --pretty=%s)
|
|
414
|
+
DEPLOY_SHA=$(git rev-parse --short HEAD)
|
|
415
|
+
DEPLOY_MSG=$(git log -1 --pretty=%s)
|
|
366
416
|
|
|
367
|
-
echo "Installing dependencies..."
|
|
368
|
-
if ! run_cmd npm install; then
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
fi
|
|
417
|
+
echo "Installing dependencies..."
|
|
418
|
+
if ! run_cmd npm install; then
|
|
419
|
+
notify "Deploy aborted. \`npm install\` failed. Commit: \`$DEPLOY_SHA\`."
|
|
420
|
+
exit 1
|
|
421
|
+
fi
|
|
372
422
|
|
|
373
|
-
echo "Running checks..."
|
|
374
|
-
if ! run_cmd npm run check; then
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
fi
|
|
423
|
+
echo "Running checks..."
|
|
424
|
+
if ! run_cmd npm run check; then
|
|
425
|
+
notify "Deploy aborted. \`npm run check\` failed. Commit: \`$DEPLOY_SHA\`."
|
|
426
|
+
exit 1
|
|
427
|
+
fi
|
|
378
428
|
|
|
379
|
-
echo "Building..."
|
|
380
|
-
if ! run_cmd npm run build; then
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
fi
|
|
429
|
+
echo "Building..."
|
|
430
|
+
if ! run_cmd npm run build; then
|
|
431
|
+
notify "Deploy aborted. Build failed. Commit: \`$DEPLOY_SHA\`."
|
|
432
|
+
exit 1
|
|
433
|
+
fi
|
|
384
434
|
|
|
385
|
-
echo "Bundling..."
|
|
386
|
-
if ! run_cmd npm run bundle; then
|
|
387
|
-
|
|
388
|
-
|
|
435
|
+
echo "Bundling..."
|
|
436
|
+
if ! run_cmd npm run bundle; then
|
|
437
|
+
notify "Deploy aborted. Bundle failed. Commit: \`$DEPLOY_SHA\`."
|
|
438
|
+
exit 1
|
|
439
|
+
fi
|
|
389
440
|
fi
|
|
390
441
|
|
|
391
442
|
# =============================================================================
|
|
@@ -467,16 +518,22 @@ done
|
|
|
467
518
|
echo ""
|
|
468
519
|
echo "--- Phase 3: Report ---"
|
|
469
520
|
|
|
521
|
+
# Build-commit clause appears only in multi-instance mode (Phase 1 ran).
|
|
522
|
+
build_clause=""
|
|
523
|
+
if [[ -n "$DEPLOY_SHA" ]]; then
|
|
524
|
+
build_clause=" Build commit \`$DEPLOY_SHA\`: $DEPLOY_MSG."
|
|
525
|
+
fi
|
|
526
|
+
|
|
470
527
|
if [[ ${#FAILED_INSTANCES[@]} -gt 0 ]]; then
|
|
471
528
|
failed_list=$(printf ", %s" "${FAILED_INSTANCES[@]}")
|
|
472
529
|
failed_list=${failed_list:2}
|
|
473
|
-
notify "Deploy partial
|
|
530
|
+
notify "Deploy partial.${build_clause} Failed instances: $failed_list."
|
|
474
531
|
echo "Deploy completed with failures: $failed_list"
|
|
475
532
|
exit 1
|
|
476
533
|
else
|
|
477
534
|
# Count actual deploy targets (respecting --instance filter)
|
|
478
535
|
deployed=${#INSTANCES[@]}
|
|
479
536
|
[[ -n "$FILTER_INSTANCE" ]] && deployed=1
|
|
480
|
-
notify "Deploy succeeded ($deployed instance(s))
|
|
537
|
+
notify "Deploy succeeded ($deployed instance(s)).${build_clause}"
|
|
481
538
|
echo "Deploy complete. $deployed instance(s) running."
|
|
482
539
|
fi
|
package/service/instances.conf
CHANGED
|
@@ -1,4 +1,19 @@
|
|
|
1
|
-
# Hive instance definitions —
|
|
1
|
+
# Hive instance definitions — multi-instance dev deploy registry
|
|
2
|
+
#
|
|
3
|
+
# THIS FILE SHIPS EMPTY in the @keepur/hive npm bundle.
|
|
4
|
+
#
|
|
5
|
+
# Customer installs do NOT use this file: `hive update` uses the
|
|
6
|
+
# single-instance code path (HIVE_SINGLE_INSTANCE=1) and discovers the calling
|
|
7
|
+
# instance from cwd / hive.yaml / HIVE_HOME — no registry read.
|
|
8
|
+
#
|
|
9
|
+
# Multi-instance dev hosts (one machine running several Hive instances) point
|
|
10
|
+
# the cron-driven deploy-check.sh at a workspace-level conf instead, via:
|
|
11
|
+
#
|
|
12
|
+
# HIVE_INSTANCES_CONF=/path/to/instances.conf
|
|
13
|
+
#
|
|
14
|
+
# That conf lives outside .hive/ so engine swaps don't wipe it.
|
|
15
|
+
#
|
|
16
|
+
# --- Format (when this file IS used) ---
|
|
2
17
|
# Fields: INSTANCE_ID | HIVE_CONFIG | (unused) | (unused label — advisory) | LOGS_DIR | PORTS (space-separated) | ENGINE_TAG
|
|
3
18
|
#
|
|
4
19
|
# HIVE_CONFIG is relative to DEPLOY_DIR
|
|
@@ -10,6 +25,6 @@
|
|
|
10
25
|
# The fourth column was historically the LaunchAgent label but is now ignored by
|
|
11
26
|
# deploy.sh — the label is always derived as com.hive.<INSTANCE_ID>.agent to match
|
|
12
27
|
# what service/install.sh produces. The column is retained for format stability.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
28
|
+
#
|
|
29
|
+
# Example row (commented — do not enable in the shipped bundle):
|
|
30
|
+
# example|hive.yaml|-|com.hive.example.agent|logs|3100 3200|latest
|