@gigabuddy/agent 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/agent-cli.js +3 -2
  2. package/package.json +1 -1
package/agent-cli.js CHANGED
@@ -113,7 +113,8 @@ ${s(h)}`}]};let y=i&&i.size===1?[...i][0]:void 0;y&&(t.awarenessManager?.updateT
113
113
  ${s(se)}`}]}):{content:[{type:"text",text:`No ${a} received within ${o}s timeout.`}]}}finally{S&&clearTimeout(S),G&&clearInterval(G);for(let I of F)I();y&&(t.awarenessManager?.updateTopicState(l,{waitingOnThread:null}),t.setNarrowWait(null))}}function XL(t){return t.charAt(0).toUpperCase()+t.slice(1)}async function jP(t,e,r,i){let n=new AbortController,o=setTimeout(()=>n.abort(),r*1e3);try{for await(let s of t.streamWork(e,{signal:n.signal})){if(s.event!=="work")continue;let a=i(s.data);if(a)return clearTimeout(o),a}return null}catch(s){if(s instanceof Error&&s.name==="AbortError")return null;throw s}finally{clearTimeout(o)}}var Jo=(t,e=!1)=>({content:[{type:"text",text:t}],...e?{isError:!0}:{}});function I_(t){let e=(t.content??[]).map(r=>r.type==="text"?r.text:"").join(`
114
114
  `).trim();try{let r=JSON.parse(e);return typeof r=="object"&&r!==null?r:null}catch{return null}}var Ke=t=>typeof t=="string"&&t?t:void 0;function Ko(t){if(!t.placeId)throw new Error("Not connected to a place. Use the connect tool first.");return t.placeId}async function DP(t,e){return(await yf(t.client,Ko(t.state),e)).map(i=>({fileId:i.info.fileId,...i.dims?{width:i.dims.width,height:i.dims.height}:{}}))}var YL=6*60*60*1e3,T_={send_message:{async pre(t,e){let r=Array.isArray(t.attachments)?t.attachments:[],{attachments:i,...n}=t;return{args:{...n,placeId:Ko(e.state),...r.length?{attachments:await DP(e,r)}:{}}}},async post(t,e,r,i,n){if(!t)return e;let o=Ke(t.messageId),s=Ke(t.threadId);return s&&o&&(n.state.subscribeToThread(s),n.state.setReadCursor(s,o),n.state.inboxWriter?.notePosition()),Jo(Ke(t.message)??`Message sent (${o??"unknown"})`)}},log:{async pre(t,e){let r=Ke(t.activityId)??e.state.currentActivity?.activityId;if(!r)return{refuse:Jo("No active activity. Start one with start_activity, or pass an explicit activityId.",!0)};let i=Array.isArray(t.attachments)?t.attachments:[],{attachments:n,...o}=t;return{args:{...o,activityId:r,placeId:Ko(e.state),...i.length?{attachments:await DP(e,i)}:{}}}},async post(t,e){return t?Jo(Ke(t.message)??"Logged."):e}},decide:{async pre(t,e){let r=e.state.currentActivity?.activityId;return{args:{...t,placeId:Ko(e.state),...r&&!t.activityId?{activityId:r}:{}}}},async post(t,e){return t?Jo(Ke(t.message)??"Decided."):e}},get_messages:{async pre(t,e){if(Ke(t.messageId))return{args:{messageId:t.messageId,placeId:e.state.placeId},meta:{pointRead:!0}};let r=t.reset===!0,i=Ke(t.before),n=Ke(t.after),o=!!i,s;Ke(t.parentMessageId)&&(s=await mi(e.state,e.client,t.parentMessageId));let a=r?void 0:s?e.state.getReadCursor(s)??void 0:e.state.lastMessageCursor??void 0,c=i??n??a,l=!i&&!n&&a!=null;return{args:{placeId:Ko(e.state),...s?{parentMessageId:s}:{},...t.limit!=null?{limit:t.limit}:{},...i?{before:i}:c?{after:c}:{}},meta:{convId:s,isHistoryRead:o,usedStoredCursor:l,storedCursor:a,limit:t.limit,localParent:Ke(t.parentMessageId)}}},async post(t,e,r,i,n){if(!t||r.pointRead)return t?Jo(Ke(t.message)??""):e;let o=r.convId,s=o?`thread ${r.localParent??o}`:"the place chat",a=t,c;if(r.usedStoredCursor){let d=typeof a.count=="number"?a.count:0,p={placeId:Ko(n.state),...o?{parentMessageId:o}:{}},f=async m=>I_(await n.callDoor({...p,limit:m}));if(d===0){let m=await f(1),h=m&&Ke(m.lastId);if(h&&h!==r.storedCursor){let y=await f(Number(r.limit??50));y&&(a=y,c=`\u26A0 Your saved read position in ${s} (${r.storedCursor}) no longer resolves \u2014 it likely points at a deleted message, so "no new messages" would have been wrong. Reset to the latest window; your actual gap may be larger.`)}}else if(a.hasMore===!0){let m=Ke(a.lastCreatedAt),h=m?Date.now()-new Date(m).getTime():NaN;if(Number.isFinite(h)&&h>YL){let y=h>=1728e5?`${Math.round(h/864e5)} days`:`${Math.round(h/36e5)}h`,_=Ke(a.firstId)??"(unknown)",k=await f(Number(r.limit??50));if(k){let w=Ke(k.firstId)??"";a=k,c=`\u26A0 Your read position in ${s} was far behind: the next-unread window ended ~${y} ago with more backlog after it. Clamped to the latest ${k.count} message(s); the skipped gap starts at ${_} \u2014 pass before: "${w}" to scroll back into it.`}}}}let l=Ke(a.lastId);!r.isHistoryRead&&l&&(o?(n.state.setReadCursor(o,l),n.state.inboxWriter?.notePosition()):(n.state.lastMessageCursor=l,vf(n.state,n.client,void 0,l)));let u=Ke(a.message)??"";return Jo(c?`${c}
115
115
 
116
- ${u}`:u)}}};var QL=["raise","read_work","read_page","search","send_message","get_messages","decide","log"],UP={read_page:"get_page"},LP=new Set(["await_work","wait_for_reply","listen","subscribe","unsubscribe","connect","disconnect","login","session_status","host_status","recovery_status","upload_file","view_file","set_my_status","set_intent"]);function eZ(){let t=process.env.GIGABUDDY_REMOTE_TOOLS;return t!==void 0?t.trim()===""||t.trim()==="none"?new Set:new Set(t.split(",").map(e=>e.trim()).filter(Boolean)):new Set(QL)}function $c(t){let e={},r=t?.properties??{},i=new Set(t?.required??[]);for(let[n,o]of Object.entries(r)){let s;Array.isArray(o.enum)&&o.enum.length>0?s=V.enum(o.enum):o.anyOf?s=V.string():o.type==="string"?s=V.string():o.type==="number"||o.type==="integer"?s=V.number():o.type==="boolean"?s=V.boolean():o.type==="array"?s=V.array(V.any()):s=V.any(),o.description&&(s=s.describe(o.description)),i.has(n)||(s=s.optional()),e[n]=s}return e}var wf=class{constructor(e,r,i,n={}){this.server=e;this.client=r;this.state=i;this.options=n}door=null;flipped=new Set;registeredDoorOnly=new Set;menuStubs=new Set;connected=!1;menuPhaseClient=!1;refreshing=null;activateMenu(){this.connected||(this.ensureDoor(),this.refresh())}activate(){if(this.connected=!0,this.door&&this.menuPhaseClient&&!this.options.doorClient){let e=this.door;this.door=null,this.menuPhaseClient=!1,e.close().catch(()=>{})}this.ensureDoor(),this.refresh()}ensureDoor(){if(!this.door&&this.options.doorClient&&(this.door=this.options.doorClient),!this.door){let e=this.options.doorUrl??process.env.GIGABUDDY_DOOR_URL??"https://mcp.gigabuddy.com";this.door=new ef({url:e,getToken:()=>this.client.currentToken(),clientInfo:{name:"gigabuddy-agent",version:this.options.version??"0.0.0"},onToolsChanged:()=>void this.refresh()}),this.menuPhaseClient=!this.connected}}async close(){let e=this.door;this.door=null,e&&await e.close().catch(()=>{})}async refresh(){return this.refreshing?this.refreshing:(this.refreshing=this.doRefresh().finally(()=>{this.refreshing=null}),this.refreshing)}async doRefresh(){let e=this.door;if(!e)return;let r;try{r=(await e.listTools()).tools}catch(o){console.error(`[gigabuddy] door proxy: tools/list failed (${o instanceof Error?o.message:o}) \u2014 local only`);return}let i=eZ(),n=this.server._registeredTools;if(!this.connected){for(let o of r){let s=UP[o.name];s&&n[s]||n[o.name]||(this.server.tool(o.name,o.description??"",$c(o.inputSchema),this.menuStubCallback()),this.menuStubs.add(o.name))}this.menuStubs.size>0&&console.error(`[${Be().serverName}] door proxy: ${this.menuStubs.size} door tool(s) on the menu (connect to use them)`);return}for(let o of[...this.menuStubs])r.some(s=>s.name===o)||(n[o]?.remove(),this.menuStubs.delete(o),console.error(`[${Be().serverName}] door proxy: menu tool '${o}' not served to this session \u2014 removed`));for(let o of r){if(this.menuStubs.has(o.name)){n[o.name]?.update({description:o.description,paramsSchema:$c(o.inputSchema),callback:this.doorCallback(o.name,null)}),this.menuStubs.delete(o.name),this.registeredDoorOnly.add(o.name);continue}let s=UP[o.name];if(s&&n[s]){let c=n[s];if(i.has(o.name)&&!LP.has(s)&&!this.flipped.has(o.name)){let l=c.handler;c.update({description:o.description,paramsSchema:$c(o.inputSchema),callback:this.doorCallback(o.name,l)}),this.flipped.add(o.name),console.error(`[gigabuddy] door proxy: '${s}' now remote-preferred via door '${o.name}' (local fallback armed)`)}continue}let a=n[o.name];if(a&&i.has(o.name)&&!LP.has(o.name)&&!this.flipped.has(o.name)){if(T_[o.name]){let u=a.handler;a.update({callback:this.wrappedCallback(o.name,u)}),this.flipped.add(o.name),console.error(`[${Be().serverName}] door proxy: '${o.name}' now remote-preferred (wrapped; local fallback armed)`);continue}let l=a.handler;a.update({description:o.description,paramsSchema:$c(o.inputSchema),callback:this.doorCallback(o.name,l)}),this.flipped.add(o.name),console.error(`[gigabuddy] door proxy: '${o.name}' now remote-preferred (local fallback armed)`);continue}!a&&!this.registeredDoorOnly.has(o.name)&&(this.server.tool(o.name,o.description??"",$c(o.inputSchema),this.doorCallback(o.name,null)),this.registeredDoorOnly.add(o.name),console.error(`[gigabuddy] door proxy: registered door tool '${o.name}'`))}}wrappedCallback(e,r){return async(i,n)=>{let o=this.door;if(!o)return r(i,n);let s=T_[e],a={client:this.client,state:this.state,callDoor:l=>o.callTool(e,l)},c;try{c=await s.pre(i,a)}catch(l){return{content:[{type:"text",text:`Failed to ${e.replace(/_/g," ")}: ${l instanceof Error?l.message:String(l)}`}],isError:!0}}if("refuse"in c)return c.refuse;try{let l=await o.callTool(e,c.args);return l.isError||!s.post?l:await s.post(I_(l),l,c.meta??{},i,a)}catch(l){return console.error(`[${Be().serverName}] door proxy: '${e}' remote call failed (${l instanceof Error?l.message:l}) \u2014 falling back to local`),r(i,n)}}}menuStubCallback(){return async()=>({content:[{type:"text",text:"Not connected to a room yet \u2014 this tool runs once you are. Call `connect` to join a room (`list_places` shows what you can reach); a room this repo has used before joins automatically on your next prompt. Then retry this call."}],isError:!0})}doorCallback(e,r){return async(i,n)=>{let o=this.door;if(o)try{return await o.callTool(e,i)}catch(s){console.error(`[gigabuddy] door proxy: '${e}' remote call failed (${s instanceof Error?s.message:s})`+(r?" \u2014 falling back to local":""))}return r?r(i,n):{content:[{type:"text",text:`The '${e}' tool is served by the Gigabuddy door and it could not be reached. Try again shortly.`}],isError:!0}}}};import{readdir as hZ,readFile as gZ,stat as WP,unlink as A_,writeFile as yZ}from"node:fs/promises";import{join as xc}from"node:path";import{execFile as tZ}from"node:child_process";import{readdir as rZ,readFile as ZP}from"node:fs/promises";import{homedir as nZ}from"node:os";import{join as kf}from"node:path";var $f=kf(nZ(),".claude","sessions");async function kc(t){try{let e=await rZ($f),r=null;for(let i of e)if(i.endsWith(".json"))try{let n=await ZP(kf($f,i),"utf-8"),o=JSON.parse(n);if(o.sessionId!==t||!o.name)continue;let s=o.updatedAt??0;(!r||s>r.updatedAt)&&(r={name:o.name,updatedAt:s})}catch{}return r?.name??null}catch{return null}}function FP(t){return kf($f,`${t}.json`)}async function P_(t){try{let e=await ZP(kf($f,`${t}.json`),"utf-8"),r=JSON.parse(e);return r.sessionId?{claudePid:t,sessionId:r.sessionId,name:r.name}:null}catch{return null}}function iZ(t){return new Promise(e=>{tZ("ps",["-o","ppid=","-p",String(t)],{timeout:2e3},(r,i)=>{if(r)return e(null);let n=Number.parseInt(i.trim(),10);e(Number.isFinite(n)&&n>1?n:null)})})}async function Xo(t=8){let e=process.ppid;for(let r=0;r<t&&e&&e>1;r++){let i=await P_(e);if(i)return i;e=await iZ(e)}return null}import{existsSync as oZ,readFileSync as sZ,statSync as aZ}from"node:fs";import{access as cZ,mkdir as VP,readdir as z_,rm as lZ,stat as C_,unlink as qP,writeFile as uZ}from"node:fs/promises";import{dirname as dZ,join as Rn,sep as R_}from"node:path";function pZ(t){let e=t;for(;;){let r=Rn(e,".git");if(oZ(r))try{if(aZ(r).isDirectory())return e;let n=/^gitdir:\s*(.+)$/m.exec(sZ(r,"utf-8")),o=`${R_}.git${R_}worktrees${R_}`,s=n?.[1]?n[1].indexOf(o):-1;return s===-1?e:n[1].slice(0,s)}catch{return e}let i=dZ(e);if(i===e)return null;e=i}}function fZ(t=process.cwd()){let{scratchEnvVar:e,scratchDirName:r}=Be(),i=process.env[e];return i||Rn(pZ(t)??t,r)}var xf=null;function At(){let t=nf();return(!xf||xf.brand!==t)&&(xf={brand:t,dir:fZ()}),xf.dir}function ur(){return Rn(At(),"sessions")}function Xe(t){return Rn(ur(),t)}async function Br(t){await E_();let e=Xe(t);return await VP(e,{recursive:!0}),e}async function E_(t=At()){await VP(t,{recursive:!0});let e=Rn(t,".gitignore");try{await cZ(e)}catch{await uZ(e,`*
116
+ ${u}`:u)}}};var QL=["raise","read_work","read_page","search","send_message","get_messages","decide","log"],UP={read_page:"get_page"},LP=new Set(["await_work","wait_for_reply","listen","subscribe","unsubscribe","connect","disconnect","login","session_status","host_status","recovery_status","upload_file","view_file","set_my_status","set_intent"]);function eZ(){let t=process.env.GIGABUDDY_REMOTE_TOOLS;return t!==void 0?t.trim()===""||t.trim()==="none"?new Set:new Set(t.split(",").map(e=>e.trim()).filter(Boolean)):new Set(QL)}function $c(t){let e={},r=t?.properties??{},i=new Set(t?.required??[]);for(let[n,o]of Object.entries(r)){let s;Array.isArray(o.enum)&&o.enum.length>0?s=V.enum(o.enum):o.anyOf?s=V.string():o.type==="string"?s=V.string():o.type==="number"||o.type==="integer"?s=V.number():o.type==="boolean"?s=V.boolean():o.type==="array"?s=V.array(V.any()):s=V.any(),o.description&&(s=s.describe(o.description)),i.has(n)||(s=s.optional()),e[n]=s}return e}var wf=class{constructor(e,r,i,n={}){this.server=e;this.client=r;this.state=i;this.options=n}door=null;flipped=new Set;registeredDoorOnly=new Set;menuStubs=new Set;connected=!1;menuPhaseClient=!1;refreshing=null;activateMenu(){this.connected||(this.ensureDoor(),this.refresh())}activate(){if(this.connected=!0,this.door&&this.menuPhaseClient&&!this.options.doorClient){let e=this.door;this.door=null,this.menuPhaseClient=!1,e.close().catch(()=>{})}this.ensureDoor(),this.refresh()}ensureDoor(){if(!this.door&&this.options.doorClient&&(this.door=this.options.doorClient),!this.door){let e=this.options.doorUrl??process.env.GIGABUDDY_DOOR_URL??"https://mcp.gigabuddy.com";this.door=new ef({url:e,getToken:()=>this.client.currentToken(),clientInfo:{name:"gigabuddy-agent",version:this.options.version??"0.0.0"},onToolsChanged:()=>void this.refresh()}),this.menuPhaseClient=!this.connected}}async close(){let e=this.door;this.door=null,e&&await e.close().catch(()=>{})}async refresh(){return this.refreshing?this.refreshing:(this.refreshing=this.doRefresh().finally(()=>{this.refreshing=null}),this.refreshing)}async doRefresh(){let e=this.door;if(!e)return;let r;try{r=(await e.listTools()).tools}catch(o){console.error(`[gigabuddy] door proxy: tools/list failed (${o instanceof Error?o.message:o}) \u2014 local only`);return}let i=eZ(),n=this.server._registeredTools;if(!this.connected){for(let o of r){let s=UP[o.name];s&&n[s]||n[o.name]||(this.server.tool(o.name,o.description??"",$c(o.inputSchema),this.menuStubCallback()),this.menuStubs.add(o.name))}this.menuStubs.size>0&&console.error(`[${Be().serverName}] door proxy: ${this.menuStubs.size} door tool(s) on the menu (connect to use them)`);return}for(let o of[...this.menuStubs])r.some(s=>s.name===o)||(n[o]?.remove(),this.menuStubs.delete(o),console.error(`[${Be().serverName}] door proxy: menu tool '${o}' not served to this session \u2014 removed`));for(let o of r){if(this.menuStubs.has(o.name)){n[o.name]?.update({description:o.description,paramsSchema:$c(o.inputSchema),callback:this.doorCallback(o.name,null)}),this.menuStubs.delete(o.name),this.registeredDoorOnly.add(o.name);continue}let s=UP[o.name];if(s&&n[s]){let c=n[s];if(i.has(o.name)&&!LP.has(s)&&!this.flipped.has(o.name)){let l=c.handler;c.update({description:o.description,paramsSchema:$c(o.inputSchema),callback:this.doorCallback(o.name,l)}),this.flipped.add(o.name),console.error(`[gigabuddy] door proxy: '${s}' now remote-preferred via door '${o.name}' (local fallback armed)`)}continue}let a=n[o.name];if(a&&i.has(o.name)&&!LP.has(o.name)&&!this.flipped.has(o.name)){if(T_[o.name]){let u=a.handler;a.update({callback:this.wrappedCallback(o.name,u)}),this.flipped.add(o.name),console.error(`[${Be().serverName}] door proxy: '${o.name}' now remote-preferred (wrapped; local fallback armed)`);continue}let l=a.handler;a.update({description:o.description,paramsSchema:$c(o.inputSchema),callback:this.doorCallback(o.name,l)}),this.flipped.add(o.name),console.error(`[gigabuddy] door proxy: '${o.name}' now remote-preferred (local fallback armed)`);continue}!a&&!this.registeredDoorOnly.has(o.name)&&(this.server.tool(o.name,o.description??"",$c(o.inputSchema),this.doorCallback(o.name,null)),this.registeredDoorOnly.add(o.name),console.error(`[gigabuddy] door proxy: registered door tool '${o.name}'`))}}wrappedCallback(e,r){return async(i,n)=>{let o=this.door;if(!o)return r(i,n);let s=T_[e],a={client:this.client,state:this.state,callDoor:l=>o.callTool(e,l)},c;try{c=await s.pre(i,a)}catch(l){return{content:[{type:"text",text:`Failed to ${e.replace(/_/g," ")}: ${l instanceof Error?l.message:String(l)}`}],isError:!0}}if("refuse"in c)return c.refuse;try{let l=await o.callTool(e,c.args);return l.isError||!s.post?l:await s.post(I_(l),l,c.meta??{},i,a)}catch(l){return console.error(`[${Be().serverName}] door proxy: '${e}' remote call failed (${l instanceof Error?l.message:l}) \u2014 falling back to local`),r(i,n)}}}menuStubCallback(){return async()=>({content:[{type:"text",text:"Not connected to a room yet \u2014 this tool runs once you are. Call `connect` to join a room (`list_places` shows what you can reach); a room this repo has used before joins automatically on your next prompt. Then retry this call."}],isError:!0})}trackSessionFocus(e,r){if(!r.isError&&!(e!=="start_activity"&&e!=="pickup_work"&&e!=="pickup_handover"&&e!=="complete_activity"))try{let i=(r.content??[]).map(a=>a.type==="text"?a.text:"").join(`
117
+ `),n=/\b(activity:[A-Za-z0-9_-]+)/.exec(i)?.[1];if(!n)return;if(e==="complete_activity"){this.state.currentActivity?.activityId===n&&this.state.clearCurrentActivity(),this.state.clearOfficeHoursForActivity(n);return}let o=/\b(conv_[A-Za-z0-9_-]+)/.exec(i)?.[1]??"",s=/"([^"\n]+)"/.exec(i)?.[1]??n;this.state.trackActivity({activityId:n,conversationId:o,label:s})}catch{}}doorCallback(e,r){return async(i,n)=>{let o=this.door;if(o)try{let s=await o.callTool(e,i);return this.trackSessionFocus(e,s),s}catch(s){console.error(`[gigabuddy] door proxy: '${e}' remote call failed (${s instanceof Error?s.message:s})`+(r?" \u2014 falling back to local":""))}return r?r(i,n):{content:[{type:"text",text:`The '${e}' tool is served by the Gigabuddy door and it could not be reached. Try again shortly.`}],isError:!0}}}};import{readdir as hZ,readFile as gZ,stat as WP,unlink as A_,writeFile as yZ}from"node:fs/promises";import{join as xc}from"node:path";import{execFile as tZ}from"node:child_process";import{readdir as rZ,readFile as ZP}from"node:fs/promises";import{homedir as nZ}from"node:os";import{join as kf}from"node:path";var $f=kf(nZ(),".claude","sessions");async function kc(t){try{let e=await rZ($f),r=null;for(let i of e)if(i.endsWith(".json"))try{let n=await ZP(kf($f,i),"utf-8"),o=JSON.parse(n);if(o.sessionId!==t||!o.name)continue;let s=o.updatedAt??0;(!r||s>r.updatedAt)&&(r={name:o.name,updatedAt:s})}catch{}return r?.name??null}catch{return null}}function FP(t){return kf($f,`${t}.json`)}async function P_(t){try{let e=await ZP(kf($f,`${t}.json`),"utf-8"),r=JSON.parse(e);return r.sessionId?{claudePid:t,sessionId:r.sessionId,name:r.name}:null}catch{return null}}function iZ(t){return new Promise(e=>{tZ("ps",["-o","ppid=","-p",String(t)],{timeout:2e3},(r,i)=>{if(r)return e(null);let n=Number.parseInt(i.trim(),10);e(Number.isFinite(n)&&n>1?n:null)})})}async function Xo(t=8){let e=process.ppid;for(let r=0;r<t&&e&&e>1;r++){let i=await P_(e);if(i)return i;e=await iZ(e)}return null}import{existsSync as oZ,readFileSync as sZ,statSync as aZ}from"node:fs";import{access as cZ,mkdir as VP,readdir as z_,rm as lZ,stat as C_,unlink as qP,writeFile as uZ}from"node:fs/promises";import{dirname as dZ,join as Rn,sep as R_}from"node:path";function pZ(t){let e=t;for(;;){let r=Rn(e,".git");if(oZ(r))try{if(aZ(r).isDirectory())return e;let n=/^gitdir:\s*(.+)$/m.exec(sZ(r,"utf-8")),o=`${R_}.git${R_}worktrees${R_}`,s=n?.[1]?n[1].indexOf(o):-1;return s===-1?e:n[1].slice(0,s)}catch{return e}let i=dZ(e);if(i===e)return null;e=i}}function fZ(t=process.cwd()){let{scratchEnvVar:e,scratchDirName:r}=Be(),i=process.env[e];return i||Rn(pZ(t)??t,r)}var xf=null;function At(){let t=nf();return(!xf||xf.brand!==t)&&(xf={brand:t,dir:fZ()}),xf.dir}function ur(){return Rn(At(),"sessions")}function Xe(t){return Rn(ur(),t)}async function Br(t){await E_();let e=Xe(t);return await VP(e,{recursive:!0}),e}async function E_(t=At()){await VP(t,{recursive:!0});let e=Rn(t,".gitignore");try{await cZ(e)}catch{await uZ(e,`*
117
118
  `)}}var mZ=3*24*60*60*1e3;async function HP(t){let e=Date.now(),r=0,i=o=>e-o>mZ;await(async o=>{let s;try{s=await z_(o)}catch{return}for(let a of s){if(a===".gitignore"||a==="sessions"||t&&a.includes(t))continue;let c=Rn(o,a);try{let l=await C_(c);if(l.isDirectory())continue;i(l.mtimeMs)&&(await qP(c),r++)}catch{}}})(At());try{let o=await z_(ur());for(let s of o){if(t&&s.includes(t))continue;let a=Rn(ur(),s);try{let c=await C_(a);if(!c.isDirectory()){i(c.mtimeMs)&&(await qP(a),r++);continue}let l=await z_(a),u=c.mtimeMs;for(let d of l)try{let p=await C_(Rn(a,d));p.mtimeMs>u&&(u=p.mtimeMs)}catch{}i(u)&&(await lZ(a,{recursive:!0,force:!0}),r++)}catch{}}}catch{}return r}var Sf=class{clientLabel="claude-code";defaultDisplayName="Claude Code Agent";async resolveSession(){let e=await Xo().catch(()=>null);return e?(this.lastPid=e.claudePid,{sessionId:`cc_${e.sessionId}`,hostSessionId:e.sessionId}):null}lastPid=null;async readSessionName(e){return e.hostSessionId?kc(e.hostSessionId):null}statuslineRef(e){return this.lastPid!==null&&e.hostSessionId?{kind:"claude",pid:this.lastPid,sessionId:e.hostSessionId}:{kind:"external",sessionId:e.sessionId}}async consumeConnectTrigger(e){if(!e.hostSessionId)return!1;let r=xc(Xe(e.sessionId),"pending-connect.json");try{await WP(r)}catch{return!1}return await A_(r).catch(()=>{}),!0}async markConnected(e){e.hostSessionId&&(await Br(e.sessionId).catch(()=>{}),await yZ(xc(Xe(e.sessionId),"active"),e.sessionId).catch(()=>{}),await A_(xc(Xe(e.sessionId),"pending-connect.json")).catch(()=>{}))}async fallbackSession(){try{let e=await hZ(ur()),r=[];for(let i of e)try{let n=await gZ(xc(ur(),i,"pending-connect.json"),"utf-8"),o=JSON.parse(n);o.claudeSessionId&&r.push(o.claudeSessionId)}catch{}if(r.length===1&&r[0])return{sessionId:`cc_${r[0]}`,hostSessionId:r[0]}}catch{}return null}},If=class{constructor(e,r="external"){this.sessionId=e;this.clientLabel=r}clientLabel;defaultDisplayName="Agent";async resolveSession(){return{sessionId:this.sessionId}}async readSessionName(){return null}statuslineRef(e){return{kind:"external",sessionId:e.sessionId}}async consumeConnectTrigger(e){let r=xc(Xe(e.sessionId),"pending-connect.json");try{await WP(r)}catch{return!1}return await A_(r).catch(()=>{}),!0}async markConnected(){}async fallbackSession(){return null}};import{watch as Ic}from"node:fs";import{mkdir as m6,readFile as q_,rename as h6,stat as vz,unlink as hz,writeFile as g6}from"node:fs/promises";import{basename as gz,dirname as y6,isAbsolute as v6,join as yi,resolve as yz}from"node:path";import{open as vZ,readFile as GP,readdir as bZ,rename as _Z,stat as wZ,writeFile as $Z}from"node:fs/promises";import{homedir as kZ}from"node:os";import{join as Tf}from"node:path";var BP=Tf(kZ(),".claude","projects"),xZ=2e5,SZ=1e6,IZ=.85,JP=512*1024;function hi(){return process.env.CLAUDE_CODE_SESSION_ID||void 0}async function Pf(t){let e;try{e=await bZ(BP)}catch{return null}let r=null;for(let i of e){let n=Tf(BP,i,`${t}.jsonl`);try{let o=await wZ(n);if(!o.isFile())continue;(!r||o.mtimeMs>r.mtime)&&(r={path:n,mtime:o.mtimeMs})}catch{}}return r?.path??null}async function KP(t,e){let r=await vZ(t,"r");try{let{size:i}=await r.stat(),n=Math.max(0,i-e),o=i-n,s=Buffer.alloc(o);return await r.read(s,0,o,n),s.toString("utf-8")}finally{await r.close()}}function TZ(t){let e=t.split(`
118
119
  `);for(let r=e.length-1;r>=0;r--){let i=e[r]?.trim();if(!(!i||!i.includes('"usage"')))try{let n=JSON.parse(i),o=n.message?.usage;if(!o)continue;if((o.input_tokens??0)+(o.cache_read_input_tokens??0)+(o.cache_creation_input_tokens??0)>0)return n}catch{}}return null}function O_(t){return Tf(Xe(t),"window.json")}async function XP(t,e,r){await Br(t);let i={model:r??null,contextWindow:e,savedAt:new Date().toISOString()},n=O_(t)+".tmp";await $Z(n,JSON.stringify(i,null,2)),await _Z(n,O_(t))}async function PZ(t){try{let e=await GP(O_(t),"utf-8"),r=JSON.parse(e);return typeof r.contextWindow=="number"&&r.contextWindow>0?r:null}catch{return null}}function zZ(t){return Tf(Xe(`cc_${t}`),"ctxwin.txt")}async function CZ(t){try{let e=await GP(zZ(t),"utf-8"),[r,i]=e.trim().split(/\s+/);if(r!=="v1")return null;let n=Number.parseInt(i??"",10);return Number.isFinite(n)&&n>0?n:null}catch{return null}}function RZ(){let t=process.env.GIGABUDDY_CONTEXT_WINDOW;if(!t)return null;let e=Number.parseInt(t,10);return Number.isFinite(e)&&e>0?e:null}function EZ(){let t=process.env.GIGABUDDY_COMPACTION_FRACTION;if(t){let e=Number.parseFloat(t);if(Number.isFinite(e)&&e>0&&e<=1)return e}return IZ}function AZ(t,e,r){let i=RZ();return i?{window:i,source:"env"}:r&&r>0&&t<=r?{window:r,source:"harness"}:e&&e>0&&t<=e?{window:e,source:"recorded"}:t>xZ?{window:SZ,source:"observed"}:{window:null,source:"unknown"}}function OZ(t,e,r,i){let n=e.message?.usage??{},o=(n.input_tokens??0)+(n.cache_read_input_tokens??0)+(n.cache_creation_input_tokens??0),{window:s,source:a}=AZ(o,r,i);if(s==null)return{sessionId:t,model:e.message?.model??null,usedTokens:o,windowTokens:null,windowSource:a,pct:null,compactionTokens:null,pctOfCompaction:null,shouldHandoff:!1};let c=Math.round(s*EZ());return{sessionId:t,model:e.message?.model??null,usedTokens:o,windowTokens:s,windowSource:a,pct:Math.min(100,Math.round(o/s*1e3)/10),compactionTokens:c,pctOfCompaction:Math.round(o/c*1e3)/10,shouldHandoff:o>=c}}async function Gr(t){let e=t||hi();if(!e)return null;try{let r=await Pf(e);if(!r)return null;let i=await KP(r,JP),n=TZ(i);if(!n)return null;let[o,s]=await Promise.all([PZ(e),CZ(e)]);return OZ(e,n,o?.contextWindow??null,s)}catch{return null}}function NZ(t){let e=t.split(`
119
120
  `);for(let r=e.length-1;r>=0;r--){let i=e[r]?.trim();if(!(!i||!i.includes('"cwd"')))try{let n=JSON.parse(i).cwd;if(typeof n=="string"&&n)return n}catch{}}return null}async function YP(t){let e=t||hi();if(!e)return null;try{let r=await Pf(e);return r?NZ(await KP(r,JP)):null}catch{return null}}function QP(t){let e=n=>`${Math.round(n/1e3)}k`;if(t.windowTokens==null||t.pct==null)return`Context: ~${e(t.usedTokens)} tokens used (window unknown \u2014 declare it with session_status contextWindow=<200000|1000000> to show %)`;let r=t.windowSource==="observed"?" (1M, inferred from observed usage)":t.windowSource==="env"?" (from GIGABUDDY_CONTEXT_WINDOW)":t.windowSource==="recorded"?" (declared)":"",i=t.shouldHandoff?" \u26A0 at compaction threshold \u2014 hand off":"";return`Context: ${t.pct}% of ${e(t.windowTokens)} window${r} \u2014 ${e(t.usedTokens)} used, compaction ~${e(t.compactionTokens??0)} (${t.pctOfCompaction}% there)${i}`}import{randomUUID as JZ}from"node:crypto";import{watch as KZ}from"node:fs";import{mkdir as XZ,readFile as Cf,rename as uz,stat as YZ,unlink as QZ,writeFile as U_}from"node:fs/promises";import{tmpdir as e6}from"node:os";import{join as Qo}from"node:path";import{execFile as MZ}from"node:child_process";import{readFile as gi,readdir as jZ}from"node:fs/promises";import{homedir as DZ,hostname as UZ}from"node:os";import{join as rz}from"node:path";var ez=rz(DZ(),".claude","sessions"),LZ=1024*1024;function tz(t){return Math.round(t/LZ)}function ZZ(t){let e=new Map;for(let r of t.split(`
@@ -264,7 +265,7 @@ ${c}
264
265
  \`\`\``)}return r(`${o}
265
266
  Not an inline-viewable type \u2014 I can render png/jpeg/gif/webp as images and text/JSON/CSV as text.${s}`)}catch(i){return r(`Failed to view ${e}: ${i instanceof Error?i.message:String(i)}`)}}function CC(t,e,r){t.tool("view_file","Look at file(s) shared in the place \u2014 a screenshot in the chat or an activity thread, an image on a page (file:\u2026 refs), a build log or JSON/CSV attachment, or anything surfaced by get_messages / find_messages. Images are returned so you actually SEE them; text/JSON/CSV files are decoded and returned inline (under a size cap); other types return metadata + a download URL. Pass an array of refs to look at several at once (e.g. a screenshot gallery). Access follows place membership, same as the humans reading the same chat.",{file:V.union([V.string(),V.array(V.string()).min(1)]).describe("A file reference \u2014 a `file:<id>` ref (as shown in messages/pages) or a bare id \u2014 OR an array of them to view several in one call.")},async i=>{try{wt(r);let n=Array.isArray(i.file)?i.file:[i.file],o=await Promise.all(n.map(a=>fF(e,a))),s=[];return o.forEach((a,c)=>{n.length>1&&s.push({type:"text",text:`\u2500\u2500 file ${c+1} of ${n.length} \u2500\u2500`}),s.push(...a)}),{content:s}}catch(n){return{content:[{type:"text",text:`Failed to view file: ${n instanceof Error?n.message:String(n)}`}],isError:!0}}}),t.tool("upload_file","Upload local file(s) into the place and get back `file:<id>` ref(s) \u2014 WITHOUT sending a message. Use this to mint an id you can then embed yourself: a screenshot on a page (`![alt](file:<id>)`), an image in a handover, or any file you want referenced before it's posted anywhere. (To attach + post in one step, use send_message/log's `attachments` instead.) Files are private and place-scoped \u2014 readable by place members, and by agents via view_file. Pass an array to upload several at once.",{paths:V.union([V.string(),V.array(V.string()).min(1)]).describe("Local file path(s) to upload \u2014 a single path or an array of them."),attachToActivity:V.boolean().optional().describe("Also attach each uploaded file to your current activity (kind:file) so it shows up in list_activity_members. Default false; no-op if you have no current activity.")},async i=>{try{wt(r);let n=Array.isArray(i.paths)?i.paths:[i.paths],o=await yf(e,r.placeId,n),s="",a=i.attachToActivity?r.currentActivity?.activityId:void 0;if(a){let u=0;for(let d of o)await e.attachToActivity(r.placeId,a,{kind:"file",id:d.info.fileId,note:d.info.name}).then(()=>!0).catch(()=>!1)&&u++;u&&(s=`
266
267
  Attached ${u} file(s) to activity ${a}.`)}let c=o.map(u=>`\u{1F4CE} ${u.info.name} (${u.info.contentType}, ${Cn(u.info.size)}) \u2192 file:${u.info.fileId}`),l=o.length===1?`
267
- Embed it on a page with \`![${o[0].info.name}](file:${o[0].info.fileId})\`, or view it with view_file.`:"\nEmbed any of them on a page with `![alt](file:<id>)`, or view them with view_file.";return{content:[{type:"text",text:`Uploaded ${o.length} file(s):
268
+ Embed it in a page, work-item body, or activity update with \`![${o[0].info.name}](file:${o[0].info.fileId})\` (NOT in plain chat messages \u2014 use send_message/log attachments there), or view it with view_file.`:"\nEmbed any of them in a page, work-item body, or activity update with `![alt](file:<id>)` (NOT in plain chat messages \u2014 use send_message/log attachments there), or view them with view_file.";return{content:[{type:"text",text:`Uploaded ${o.length} file(s):
268
269
  ${c.join(`
269
270
  `)}${s}${l}`}]}}catch(n){return{content:[{type:"text",text:`Failed to upload file: ${n instanceof Error?n.message:String(n)}`}],isError:!0}}})}function RC(t,e,r){let i=n=>n??r.currentActivity?.activityId??null;t.tool("set_my_status","Set YOUR status in an activity \u2014 the per-participant claim primitive, symmetric in both directions. 'active' = I've got this (use it to CLAIM a handover/work item instead of a global lock; as a handover's ORIGIN it instead REOPENS your office hours \u2014 the undo for a premature end_office_hours); 'handed-off'/'done' = RELEASE the claim (the item returns to 'unclaimed' in list_handovers/list_work for the next picker \u2014 claim is derived from active participants, so handing off is all it takes; no admin/participant-removal step); 'watching' = spectating (also releases an active claim); 'blocked' = stuck. Joining is implicit (writing your status auto-joins). Your context-window fullness is auto-stamped so the room sees handoff-health. Posts the transition into the activity thread.",{status:V.enum(["watching","active","blocked","done","handed-off"]).describe("Your status. 'active' claims the work."),statusText:V.string().optional().describe('Optional human one-liner (e.g. "wiring the endpoints").'),activityId:V.string().optional().describe("Activity ID (defaults to your current activity)"),contextPct:V.number().optional().describe("Override context-window fullness (auto-stamped if omitted).")},async n=>{try{wt(r);let o=i(n.activityId);if(!o)return{content:[{type:"text",text:"No active activity. Start one or pass an explicit activityId."}],isError:!0};let s=n.contextPct;if(s==null){let p=r.agentMeta.claudeSessionId??hi()??null;s=(await Gr(p).catch(()=>null))?.pct??void 0}let a=await e.setMyStatus(r.placeId,o,{status:n.status,statusText:n.statusText,contextPct:s,actorType:"buddy"});(n.status==="handed-off"||n.status==="done")&&r.clearOfficeHoursForActivity(o);let c=null;if(n.status==="active"){let p=await Hf(e,r.placeId,o).catch(()=>null);if(p&&r.agentId!=null&&p.originActorId===r.agentId){if(p.pageId&&p.handover){c=new Date(Date.now()+55*6e4).toISOString();let f=p.handover.origin??{};await e.updatePlaceContent(r.placeId,p.pageId,{handover:{...p.handover,origin:{...f,until:c}}}).catch(()=>{});let m=p.title??a.label??o;r.setOfficeHours({role:"origin",pageId:p.pageId,activityId:o,title:m,until:c})}}else p?.workItemId&&await fC(e,r.placeId,p.workItemId).catch(()=>{})}else(n.status==="watching"||n.status==="handed-off"||n.status==="done")&&await mC(e,r.placeId,o,r.agentId??null).catch(()=>{});let l=r.currentActivity?.activityId===o?r.currentActivity.conversationId:a.conversationId??"";!l&&a.conversationId&&(l=a.conversationId);let u=n.status==="watching"||n.status==="handed-off"||n.status==="done",d=!1;if(u?r.currentActivity?.activityId===o&&(r.clearCurrentActivity(),r.awarenessManager?.updateTopicState(r.placeId,{activityId:null}),d=!0):l&&(r.trackActivity({activityId:o,conversationId:l,label:a.label||o}),r.awarenessManager?.updateTopicState(r.placeId,{activityId:o})),l){let p=s!=null?` (context ${s}%)`:"";await e.sendMessage(r.placeId,{content:`${r.agentMeta.displayName} is now **${n.status}**${n.statusText?` \u2014 ${n.statusText}`:""}${p}`,senderName:r.agentMeta.displayName,parentMessageId:l}).catch(()=>{})}return{content:[{type:"text",text:`Status set to '${n.status}' on ${o}${s!=null?` (context ${s}%)`:""}.`+(c?` Office hours REOPENED \u2014 you're listed as available again in list_handovers/list_work (advisory window until ${c}). Watch the activity thread with wait_for_reply and answer with 'log'.`:"")+(d?" Released this session's current-activity focus \u2014 you can start_activity on new work now (this one stays a valid queue item for the next picker).":"")}]}}catch(o){return{content:[{type:"text",text:`Failed to set status: ${o instanceof Error?o.message:String(o)}`}],isError:!0}}})}import{execFile as hF}from"node:child_process";function ns(t,e,r=5e3){return new Promise(i=>{hF("git",["-C",t,...e],{timeout:r,maxBuffer:8*1024*1024},(n,o)=>{i(n?null:o)})})}function gF(t){let e=[],r=null,i=()=>{r?.path&&e.push({path:r.path,branch:r.branch??null,head:r.head??null,detached:!!r.detached,bare:!!r.bare}),r=null};for(let n of t.split(`
270
271
  `)){if(n===""){i();continue}let o=n.indexOf(" "),s=o===-1?n:n.slice(0,o),a=o===-1?"":n.slice(o+1);switch(s){case"worktree":i(),r={path:a};break;case"HEAD":r&&(r.head=a);break;case"branch":r&&(r.branch=a.replace(/^refs\/heads\//,""));break;case"detached":r&&(r.detached=!0);break;case"bare":r&&(r.bare=!0);break}}return i(),e}function yF(t){let e=0,r=0,i=0,n=0;for(let o of t.split(`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gigabuddy/agent",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "The Gigabuddy agent client — connects a coding-agent session to Gigabuddy places: presence, messaging, and the hosted tool door.",
5
5
  "type": "module",
6
6
  "license": "MIT",