@whoz-oss/coday-server 0.104.2 → 0.104.3

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/package.json +1 -1
  2. package/server.js +2 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@whoz-oss/coday-server",
3
- "version": "0.104.2",
3
+ "version": "0.104.3",
4
4
  "repository": "https://github.com/whoz-oss/coday",
5
5
  "type": "module",
6
6
  "main": "server/server.js",
package/server.js CHANGED
@@ -1077,8 +1077,8 @@ ${s}`:`${c}
1077
1077
 
1078
1078
  `)};import{existsSync as DM,mkdirSync as Rst,renameSync as Nst}from"node:fs";import*as ope from"path";var spe=({fromAbsolute:r,toAbsolute:e})=>{if(!DM(r))return{success:!1,message:`Source file not found: ${r}`};if(DM(e))return{success:!1,message:`Destination already exists: ${e}. Remove it explicitly before moving.`};let t=ope.dirname(e);DM(t)||Rst(t,{recursive:!0});try{return Nst(r,e),{success:!0,message:"File moved successfully"}}catch(n){return{success:!1,message:`Error moving file: ${n}`}}};var ng=class extends Vt{static TYPE="FILES";constructor(e,t,n){super(e,t,n)}async buildTools(e,t){let n=[];if(!e.fileReadOnly){let c=({path:l})=>{let f=Yp(l,e);if(f.scope==="project"&&e.fileReadOnly)throw new Error("Cannot delete project files in read-only mode");try{if(Mst(f.absolutePath),f.scope==="exchange"){let p=new vc({filename:f.relativePath,operation:"deleted"});this.interactor.sendEvent(p)}return"File deleted successfully"}catch(p){return this.interactor.error(`Error deleting file ${f.absolutePath}`),`Error deleting file: ${p}`}},u={type:"function",function:{name:`${this.name}__remove`,description:'Remove a file. File path must start with "project://" (for project files) or "exchange://" (for files shared with the user).',parameters:{type:"object",properties:{path:{type:"string",description:'File path with prefix (e.g., "project://temp/old.txt" or "exchange://draft.md")'}}},parse:JSON.parse,function:c}};n.push(u)}let i=({relPath:c})=>{if(!c||c==="."||c==="/")throw new Error('Path must start with "project://" or "exchange://" prefix. Use "project://" to list project files or "exchange://" to list files shared with the users.');let u=Yp(c,e);return IE({relPath:".",root:u.absolutePath})},a={type:"function",function:{name:`${this.name}__ls`,description:'List directories and files in a folder (similar to ls command). Directories end with a slash. Path must start with "project://" or "exchange://" prefix.',parameters:{type:"object",properties:{relPath:{type:"string",description:'Path with prefix (e.g., "project://src" or "exchange://")'}}},parse:JSON.parse,function:i}};n.push(a);let o=async({filePath:c})=>{let u=Yp(c,e);return dce({absolutePath:u.absolutePath,interactor:this.interactor})},s={type:"function",function:{name:`${this.name}__readFile`,description:'Read content from any file type. Supports text files, PDFs, and image files (PNG, JPEG, GIF, WebP). File path must start with "project://" (for project files) or "exchange://" (for files shared with the user). Use searchFiles to find files across both spaces.',parameters:{type:"object",properties:{filePath:{type:"string",description:'File path with prefix (e.g., "project://src/main.ts" or "exchange://document.pdf")'}}},parse:JSON.parse,function:o}};return n.push(s),n.push(this.buildSearchFilesTool(e)),e.fileReadOnly||(n.push(this.buildEditFilesTool(e)),n.push(this.buildMoveFileTool(e))),n}buildSearchFilesTool(e){let t=async({fileName:n,fileContent:i,path:a,fileTypes:o})=>{if(!n&&!i)return"At least one of fileName or fileContent must be provided.";let s=[];if(e.threadFilesRoot&&Fst(e.threadFilesRoot)&&(!a||a.startsWith(Ao.EXCHANGE))){let u=a?.replace(Ao.EXCHANGE,""),{files:l}=await TM({fileName:n,fileContent:i,searchPath:u,root:e.threadFilesRoot,fileTypes:o,interactor:this.interactor}),f=PM({files:l,root:e.threadFilesRoot,prefix:Ao.EXCHANGE});s.push(f)}if(!a?.startsWith(Ao.EXCHANGE)){let u=a?.replace(Ao.PROJECT,""),{files:l}=await TM({fileName:n,fileContent:i,searchPath:u,root:e.project.root,fileTypes:o,interactor:this.interactor}),f=PM({files:l,root:e.project.root,prefix:Ao.PROJECT});s.push(f)}return s.filter(u=>u!=="No matching files found.").join(`
1079
1079
 
1080
- `)||"No matching files found."};return{type:"function",function:{name:`${this.name}__searchFiles`,description:'Search for files by name pattern and/or content text. At least one of fileName or fileContent must be provided. When both are provided, only files whose name matches fileName AND whose content matches fileContent are returned. If the total size of matching files is reasonable, their content is returned directly. Otherwise, only the list of matching paths is returned. Paths are prefixed with "project://" or "exchange://".',parameters:{type:"object",properties:{fileName:{type:"string",description:'Partial file name or pattern to match against file names (e.g. "config", "user.service").'},fileContent:{type:"string",description:"Text to search for inside file contents."},path:{type:"string",description:'Optional path to restrict the search scope. Use "project://" or "exchange://" prefix.'},fileTypes:{type:"array",items:{type:"string"},description:'Optional array of file extensions to restrict content search (e.g. ["ts", "json"]).'}}},parse:JSON.parse,function:t}}}buildEditFilesTool(e){return{type:"function",function:{name:"editFiles",description:'Edit one or more files in a single tool call. Each edit targets a specific file and specifies an operation: "write" replaces the entire file content (or creates it), "patch" replaces specific chunks within an existing file. Edits are executed independently: a failure on one file does not prevent others from being processed. File paths must start with "project://" or "exchange://".',parameters:{type:"object",properties:{edits:{type:"array",description:"List of file edits to perform.",items:{type:"object",properties:{operation:{type:"string",enum:["write","patch"],description:'"write" to overwrite/create the file, "patch" to replace specific chunks.'},path:{type:"string",description:'File path with prefix (e.g. "project://src/main.ts" or "exchange://report.md").'},content:{type:"string",description:'Full file content. Required for "write" operation.'},replacements:{type:"array",description:'Required for "patch" operation.',items:{type:"object",properties:{oldPart:{type:"string",description:"Existing content to replace (must be unique and at least 15 chars)."},newPart:{type:"string",description:"Replacement content."}}}}}}}}},parse:JSON.parse,function:({edits:n})=>{if(!n||!Array.isArray(n)||n.length===0)return"No edits provided.";let i=[];for(let a of n)try{let o=Yp(a.path,e);if(a.operation==="write"){let s=epe({relPath:Am.basename(o.absolutePath),root:Am.dirname(o.absolutePath),interactor:this.interactor,content:a.content});if(o.scope==="exchange"){let c=new vc({filename:o.relativePath,operation:"created",size:Buffer.from(a.content).length});this.interactor.sendEvent(c)}i.push(`${a.path}: ${s}`)}else if(a.operation==="patch"){let s=rpe({relPath:Am.basename(o.absolutePath),root:Am.dirname(o.absolutePath),interactor:this.interactor,replacements:a.replacements});if(o.scope==="exchange"){let c=new vc({filename:o.relativePath,operation:"updated"});this.interactor.sendEvent(c)}i.push(`${a.path}: ${s}`)}else i.push(`${a.path}: Unknown operation`)}catch(o){i.push(`${a.path}: Error \u2014 ${o}`)}return i.join(`
1081
- `)}}}}buildMoveFileTool(e){let t=({from:n,to:i})=>{let a=Yp(n,e),o=Yp(i,e);if(a.scope!==o.scope)return`Cannot move files between scopes: "${n}" (${a.scope}) \u2192 "${i}" (${o.scope}). Use separate copy and delete operations.`;let{success:s,message:c}=spe({fromAbsolute:a.absolutePath,toAbsolute:o.absolutePath});return s&&a.scope==="exchange"&&(this.interactor.sendEvent(new vc({filename:a.relativePath,operation:"deleted"})),this.interactor.sendEvent(new vc({filename:o.relativePath,operation:"created"}))),c};return{type:"function",function:{name:`${this.name}__moveFile`,description:'Move or rename a file within the same scope (project or exchange). Fails if the source does not exist or the destination already exists. Cross-scope moves (project \u2194 exchange) are not allowed. File paths must start with "project://" or "exchange://".',parameters:{type:"object",properties:{from:{type:"string",description:'Source file path with prefix (e.g. "project://old/path.ts").'},to:{type:"string",description:'Destination file path with prefix (e.g. "project://new/path.ts").'}}},parse:JSON.parse,function:t}}}};import*as cpe from"fs";import{promisify as Lst}from"util";var PKt=Lst(cpe.unlink);var OE=class extends Re{constructor(t){super({commandWord:"folder",description:"Loads files from a folder, does not do depth nor recursive."});this.interactor=t}async handle(t,n){let i=this.getSubCommand(t);if(!i)return this.interactor.error("Please provide a valid folder path."),n;i.startsWith(".")||(i=`.${i}`);let a;try{a=await IE({relPath:i,root:n.project.root});let o=a.filter(s=>!s.endsWith("/")).map(s=>`load file ${i}/${s}`);n.addCommands(...o),this.interactor.displayText(`Folder loaded: ${i}`)}catch(o){this.interactor.error(`Failed to load folder: ${o}`)}return n}};var $E=class extends Zn{constructor(e){super({commandWord:"load",description:"handles load related commands"},e),this.handlers=[new PE(e),new OE(e)]}};var jE=class extends Re{constructor(t,n){super({commandWord:"agents",description:"Show agent usage statistics sorted by number of calls. Use --from=YYYY-MM-DD and --to=YYYY-MM-DD for custom date range (default: last 7 days)"});this.interactor=t;this.services=n}async handle(t,n){try{let i=this.getSubCommand(t),a=Tt(i,[{key:"from",alias:"f"},{key:"to",alias:"t"}]),o,s;if(a.to&&typeof a.to=="string"){let p=this.parseDate(a.to);if(!p)return this.interactor.error("Invalid --to date format. Please use YYYY-MM-DD"),n;o=p,o.setHours(23,59,59,999)}else o=new Date,o.setHours(23,59,59,999);if(a.from&&typeof a.from=="string"){let p=this.parseDate(a.from);if(!p)return this.interactor.error("Invalid --from date format. Please use YYYY-MM-DD"),n;s=p,s.setHours(0,0,0,0)}else s=new Date(o),s.setDate(s.getDate()-7),s.setHours(0,0,0,0);if(s>o)return this.interactor.error("Invalid date range: --from date must be before --to date"),n;let c=(await this.services.logger.readLogs(s,o)).filter(p=>!p.type||p.type==="AGENT_USAGE");if(c.length===0){let p=this.formatDateRange(s,o);return this.interactor.displayText(`\u{1F4CA} No usage data found for ${p}`),n}let u=new Map;for(let p of c){let d=u.get(p.agent)||{calls:0,totalCost:0};u.set(p.agent,{calls:d.calls+1,totalCost:d.totalCost+p.cost})}let l=Array.from(u.entries()).map(([p,d])=>({agent:p,calls:d.calls,totalCost:d.totalCost,avgCost:d.totalCost/d.calls}));l.sort((p,d)=>d.calls-p.calls);let f=this.formatAgentStats(l,s,o);this.interactor.displayText(f)}catch(i){this.interactor.error(`Failed to retrieve agent statistics: ${i}`)}return n}parseDate(t){if(!t.match(/^(\d{4})-(\d{2})-(\d{2})$/))return null;let i=new Date(t);return isNaN(i.getTime())?null:i}formatDateRange(t,n){let i=t.toISOString().split("T")[0],a=n.toISOString().split("T")[0];return`${i} to ${a}`}formatAgentStats(t,n,i){let o=`\u{1F4CA} Agent Usage Statistics (${this.formatDateRange(n,i)})
1080
+ `)||"No matching files found."};return{type:"function",function:{name:`${this.name}__searchFiles`,description:'Search for files by name pattern and/or content text. At least one of fileName or fileContent must be provided. When both are provided, only files whose name matches fileName AND whose content matches fileContent are returned. If the total size of matching files is reasonable, their content is returned directly. Otherwise, only the list of matching paths is returned. Paths are prefixed with "project://" or "exchange://".',parameters:{type:"object",properties:{fileName:{type:"string",description:'Partial file name or pattern to match against file names (e.g. "config", "user.service").'},fileContent:{type:"string",description:"Text to search for inside file contents."},path:{type:"string",description:'Optional path to restrict the search scope. Use "project://" or "exchange://" prefix.'},fileTypes:{type:"array",items:{type:"string"},description:'Optional array of file extensions to restrict content search (e.g. ["ts", "json"]).'}}},parse:JSON.parse,function:t}}}buildEditFilesTool(e){let t=({edits:n})=>{if(!n||!Array.isArray(n)||n.length===0)return"No edits provided.";let i=[];for(let a of n)try{let o=Yp(a.path,e);if(a.operation==="write"){let s=epe({relPath:Am.basename(o.absolutePath),root:Am.dirname(o.absolutePath),interactor:this.interactor,content:a.content});if(o.scope==="exchange"){let c=new vc({filename:o.relativePath,operation:"created",size:Buffer.from(a.content).length});this.interactor.sendEvent(c)}i.push(`${a.path}: ${s}`)}else if(a.operation==="patch"){let s=rpe({relPath:Am.basename(o.absolutePath),root:Am.dirname(o.absolutePath),interactor:this.interactor,replacements:a.replacements});if(o.scope==="exchange"){let c=new vc({filename:o.relativePath,operation:"updated"});this.interactor.sendEvent(c)}i.push(`${a.path}: ${s}`)}else i.push(`${a.path}: Unknown operation`)}catch(o){i.push(`${a.path}: Error \u2014 ${o}`)}return i.join(`
1081
+ `)};return{type:"function",function:{name:`${this.name}__editFiles`,description:'Edit one or more files in a single tool call. Each edit targets a specific file and specifies an operation: "write" replaces the entire file content (or creates it), "patch" replaces specific chunks within an existing file. Edits are executed independently: a failure on one file does not prevent others from being processed. File paths must start with "project://" or "exchange://".',parameters:{type:"object",properties:{edits:{type:"array",description:"List of file edits to perform.",items:{type:"object",properties:{operation:{type:"string",enum:["write","patch"],description:'"write" to overwrite/create the file, "patch" to replace specific chunks.'},path:{type:"string",description:'File path with prefix (e.g. "project://src/main.ts" or "exchange://report.md").'},content:{type:"string",description:'Full file content. Required for "write" operation.'},replacements:{type:"array",description:'Required for "patch" operation.',items:{type:"object",properties:{oldPart:{type:"string",description:"Existing content to replace (must be unique and at least 15 chars)."},newPart:{type:"string",description:"Replacement content."}}}}}}}}},parse:JSON.parse,function:t}}}buildMoveFileTool(e){let t=({from:n,to:i})=>{let a=Yp(n,e),o=Yp(i,e);if(a.scope!==o.scope)return`Cannot move files between scopes: "${n}" (${a.scope}) \u2192 "${i}" (${o.scope}). Use separate copy and delete operations.`;let{success:s,message:c}=spe({fromAbsolute:a.absolutePath,toAbsolute:o.absolutePath});return s&&a.scope==="exchange"&&(this.interactor.sendEvent(new vc({filename:a.relativePath,operation:"deleted"})),this.interactor.sendEvent(new vc({filename:o.relativePath,operation:"created"}))),c};return{type:"function",function:{name:`${this.name}__moveFile`,description:'Move or rename a file within the same scope (project or exchange). Fails if the source does not exist or the destination already exists. Cross-scope moves (project \u2194 exchange) are not allowed. File paths must start with "project://" or "exchange://".',parameters:{type:"object",properties:{from:{type:"string",description:'Source file path with prefix (e.g. "project://old/path.ts").'},to:{type:"string",description:'Destination file path with prefix (e.g. "project://new/path.ts").'}}},parse:JSON.parse,function:t}}}};import*as cpe from"fs";import{promisify as Lst}from"util";var PKt=Lst(cpe.unlink);var OE=class extends Re{constructor(t){super({commandWord:"folder",description:"Loads files from a folder, does not do depth nor recursive."});this.interactor=t}async handle(t,n){let i=this.getSubCommand(t);if(!i)return this.interactor.error("Please provide a valid folder path."),n;i.startsWith(".")||(i=`.${i}`);let a;try{a=await IE({relPath:i,root:n.project.root});let o=a.filter(s=>!s.endsWith("/")).map(s=>`load file ${i}/${s}`);n.addCommands(...o),this.interactor.displayText(`Folder loaded: ${i}`)}catch(o){this.interactor.error(`Failed to load folder: ${o}`)}return n}};var $E=class extends Zn{constructor(e){super({commandWord:"load",description:"handles load related commands"},e),this.handlers=[new PE(e),new OE(e)]}};var jE=class extends Re{constructor(t,n){super({commandWord:"agents",description:"Show agent usage statistics sorted by number of calls. Use --from=YYYY-MM-DD and --to=YYYY-MM-DD for custom date range (default: last 7 days)"});this.interactor=t;this.services=n}async handle(t,n){try{let i=this.getSubCommand(t),a=Tt(i,[{key:"from",alias:"f"},{key:"to",alias:"t"}]),o,s;if(a.to&&typeof a.to=="string"){let p=this.parseDate(a.to);if(!p)return this.interactor.error("Invalid --to date format. Please use YYYY-MM-DD"),n;o=p,o.setHours(23,59,59,999)}else o=new Date,o.setHours(23,59,59,999);if(a.from&&typeof a.from=="string"){let p=this.parseDate(a.from);if(!p)return this.interactor.error("Invalid --from date format. Please use YYYY-MM-DD"),n;s=p,s.setHours(0,0,0,0)}else s=new Date(o),s.setDate(s.getDate()-7),s.setHours(0,0,0,0);if(s>o)return this.interactor.error("Invalid date range: --from date must be before --to date"),n;let c=(await this.services.logger.readLogs(s,o)).filter(p=>!p.type||p.type==="AGENT_USAGE");if(c.length===0){let p=this.formatDateRange(s,o);return this.interactor.displayText(`\u{1F4CA} No usage data found for ${p}`),n}let u=new Map;for(let p of c){let d=u.get(p.agent)||{calls:0,totalCost:0};u.set(p.agent,{calls:d.calls+1,totalCost:d.totalCost+p.cost})}let l=Array.from(u.entries()).map(([p,d])=>({agent:p,calls:d.calls,totalCost:d.totalCost,avgCost:d.totalCost/d.calls}));l.sort((p,d)=>d.calls-p.calls);let f=this.formatAgentStats(l,s,o);this.interactor.displayText(f)}catch(i){this.interactor.error(`Failed to retrieve agent statistics: ${i}`)}return n}parseDate(t){if(!t.match(/^(\d{4})-(\d{2})-(\d{2})$/))return null;let i=new Date(t);return isNaN(i.getTime())?null:i}formatDateRange(t,n){let i=t.toISOString().split("T")[0],a=n.toISOString().split("T")[0];return`${i} to ${a}`}formatAgentStats(t,n,i){let o=`\u{1F4CA} Agent Usage Statistics (${this.formatDateRange(n,i)})
1082
1082
  =====================================
1083
1083
 
1084
1084
  Agent | Calls | Total Cost | Avg Cost