@whoz-oss/coday-server 0.100.0 → 0.101.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/server.js +1 -1
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -874,7 +874,7 @@ Are you sure you want to delete this MCP server configuration?
|
|
|
874
874
|
|
|
875
875
|
Here are the information collected during previous chats:
|
|
876
876
|
`),n.join(`
|
|
877
|
-
`)):""}loadMemoriesFrom(e){this.memories=[],this.userMemoriesPath=this.readMemories(this.userMemoriesPath)?this.userMemoriesPath:void 0;let t=e?.configPath?eE.join(e.configPath,vM):void 0;this.projectMemoriesPath=this.readMemories(t)?t:void 0,e&&this.checkInit()}readMemories(e){if(!e)return!1;try{if(!qit(e)){let n=xfe.stringify({memories:[]});zit(e,n)}let t=ta(e);return t?(this.memories.push(...t.memories),!0):!1}catch{return!1}}checkInit(){if(!this.userMemoriesPath||!this.projectMemoriesPath)throw new Error("user or project path not set for memory service")}saveMemories(){this.checkInit();let e=this.memories.filter(n=>n.level==="USER"),t=this.memories.filter(n=>n.level==="PROJECT");Mn(this.userMemoriesPath,{memories:e}),Mn(this.projectMemoriesPath,{memories:t})}};import*as _fe from"crypto";import*as Va from"node:fs/promises";import*as Fc from"path";var bm=class r{constructor(e,t,n){this.repository=e;this.defaultProject=t;this.isForcedMode=n}maskingService=new B2;listProjects(){let e=this.repository.listProjects();return this.isForcedMode&&this.defaultProject?e.filter(t=>t===this.defaultProject).map(t=>{let n=this.repository.getConfig(t);return{name:t,volatile:n?.volatile}}):e.map(t=>{let n=this.repository.getConfig(t);return{name:t,volatile:n?.volatile}})}getDefaultProject(){return this.defaultProject||null}getForcedMode(){return this.isForcedMode}getProject(e){this.checkAgainstForced(e);let t=this.repository.getConfig(e);if(t)return{name:e,config:t};if(!this.isForcedMode&&this.defaultProject){let n=process.cwd(),i=r.generateProjectId(n),a=Fc.basename(n);if(e===a||e===i||e===this.defaultProject){console.log(`[PROJECT_SERVICE] Creating volatile project for '${e}' at ${n}`);let o=this.getOrCreateVolatileProject(n);if(t=this.repository.getConfig(o),t)return{name:o,config:t}}}return null}exists(e){return this.checkAgainstForced(e),this.repository.exists(e)}createProject(e,t){if(this.checkAgainstForced(e),!e||!t)throw new Error("Project name and path are required");if(!this.repository.createProject(e,t))throw new Error(`Project '${e}' already exists`)}updateProjectConfig(e,t){if(this.checkAgainstForced(e),!this.repository.exists(e))throw new Error(`Project '${e}' does not exist`);this.repository.saveConfig(e,t)}deleteProject(e){if(this.checkAgainstForced(e),!this.repository.deleteProject(e))throw new Error(`Project '${e}' does not exist`)}getProjectConfigForClient(e){this.checkAgainstForced(e);let t=this.repository.getConfig(e);return t?this.maskingService.maskConfig(t):null}updateProjectConfigFromClient(e,t){this.checkAgainstForced(e);let n=this.repository.getConfig(e);if(!n)throw new Error(`Project '${e}' does not exist`);let i=this.maskingService.unmaskConfig(t,n);this.repository.saveConfig(e,i)}checkAgainstForced(e){if(this.isForcedMode&&this.defaultProject&&this.defaultProject!==e)throw Error(`Project selection outside of ${this.defaultProject} not allowed`)}getOrCreateVolatileProject(e){let t=r.generateProjectId(e);if(this.repository.exists(t))return t;this.repository.createProject(t,e);let n=this.repository.getConfig(t);return n&&(n.volatile=!0,n.createdAt=Date.now(),this.repository.saveConfig(t,n)),t}async registerWorktreeProject(e,t,n){let i=this.repository.getConfig(n),a=i?{...i,path:t,volatile:void 0,createdAt:void 0}:{version:1,path:t,integration:{},storage:{type:"file"},agents:[]};this.repository.createProject(e,t),this.repository.saveConfig(e,a);let o=this.repository.getProjectInfo(n);if(o){let s=this.repository.getProjectInfo(e);if(s)for(let c of["agents","prompts","schedulers","memories"]){let u=Fc.join(o.configPath,c),l=Fc.join(s.configPath,c);try{await Va.access(u),await Va.symlink(u,l)}catch{}}}}async unregisterWorktreeProject(e){let t=this.repository.getProjectInfo(e);if(!t)return;let n=e.lastIndexOf("__");if(n!==-1){let i=e.substring(0,n),a=this.repository.getProjectInfo(i);a&&await this.migrateThreadsToParent(t.configPath,a.configPath)}await Va.rm(t.configPath,{recursive:!0,force:!0})}async migrateThreadsToParent(e,t){let n=Fc.join(e,"threads"),i=Fc.join(t,"threads");try{await Va.access(n)}catch{return}await Va.mkdir(i,{recursive:!0});let a=await Va.readdir(n);for(let o of a){let s=Fc.join(n,o),c=Fc.join(i,o);try{await Va.access(c)}catch{await Va.rename(s,c)}}}static generateProjectId(e){let t=Fc.basename(e),n=_fe.createHash("sha256").update(e).digest("hex").substring(0,8);return`${t}_${n}`}};var kfe=vr(Go(),1);import*as xm from"node:path";import*as Efe from"node:os";import{mkdirSync as Uit}from"fs";var Bit="projects",Sfe="project.yaml",wm=class{constructor(e,t,n){this.interactor=e;this.projectService=t;let i=xm.join(Efe.userInfo().homedir,".coday");this.projectsConfigPath=xm.join(n??i,Bit),Uit(this.projectsConfigPath,{recursive:!0}),this.projects=this.projectService.listProjects().map(a=>a.name)}projectsConfigPath;maskingService=new B2;projects;selectedProjectBehaviorSubject=new kfe.BehaviorSubject(null);selectedProject$=this.selectedProjectBehaviorSubject.asObservable();selectProject(e){let t=xm.join(this.projectsConfigPath,e),n=xm.join(t,Sfe),i=this.projectService.getProject(e)?.config;if(!i?.path){console.log(`[PROJECT_STATE_SERVICE] ERROR: No project path in config for '${e}'`),this.interactor.error("Invalid selection, project path needed \u{1F622}.");return}let o={name:e,config:i,configPath:t};console.log(`[PROJECT_STATE_SERVICE] Selected '${e}' \u2192 ${i.path}`),this.updateSelectedProject(o),this.interactor.displayText(`Project local configuration used: ${n}`)}updateSelectedProject(e){this.selectedProjectBehaviorSubject.next(e)}get selectedProject(){return this.selectedProjectBehaviorSubject.value}save(e){let t=this.selectedProjectBehaviorSubject.value;if(!t){this.interactor.error("No current project selected, save not possible");return}let n={...t.config,...e};Mn(xm.join(t.configPath,Sfe),n),t.config=n,this.updateSelectedProject(t)}getConfigForClient(){let e=this.selectedProjectBehaviorSubject.value;return e?this.maskingService.maskConfig(e.config):null}updateConfigFromClient(e){let t=this.selectedProjectBehaviorSubject.value;if(!t){this.interactor.error("No current project selected, update not possible");return}let n=this.maskingService.unmaskConfig(e,t.config);this.save(n)}};import*as Gu from"node:path";import*as Tfe from"node:os";import{randomUUID as Wit}from"node:crypto";import{existsSync as W2,mkdirSync as Vit,readdirSync as Cfe,unlinkSync as Git}from"fs";import*as yM from"node:path";import*as Afe from"node:os";function Hit(r){return r.replace(/[^a-zA-Z0-9]/g,"_")}function _m(r,e){try{let t=yM.join(Afe.userInfo().homedir,".coday"),n=Hit(r),i=yM.join(e??t,"users",n,"user.yaml"),a=ta(i);return a?a.groups?.includes("CODAY_ADMIN")??!1:!1}catch{return!1}}var tE=class{codayConfigDir;projectService;constructor(e,t){let n=Gu.join(Tfe.userInfo().homedir,".coday");this.codayConfigDir=e??n,this.projectService=t}getProjectPath(e){if(this.projectService)return this.projectService.getProject(e)?.config.path}async getOrCreatePromptsDir(e,t){let n;if(t==="local")n=Gu.join(this.codayConfigDir,"projects",e,"prompts");else{let i=this.getProjectPath(e);if(!i)throw new Error("Project path not configured, cannot access project prompts");let a=await f2({text:"coday.yaml",root:i});if(a.length===0)throw new Error(`coday.yaml not found in project path: ${i}`);let o=Gu.dirname(a[0]);n=Gu.join(i,o,"prompts")}return W2(n)||(Vit(n,{recursive:!0}),console.log(`[PROMPT] Created prompts directory: ${n}`)),n}async getOrCreatePromptFilePath(e,t,n){let i=await this.getOrCreatePromptsDir(e,n);return Gu.join(i,`${t}.yml`)}async findPromptSource(e,t){let n=await this.getOrCreatePromptFilePath(e,t,"local");if(W2(n))return"local";if(this.projectService)try{let i=await this.getOrCreatePromptFilePath(e,t,"project");if(W2(i))return"project"}catch{}return null}async findProjectForPrompt(e){let t=Gu.join(this.codayConfigDir,"projects");if(!W2(t))return null;let n=Cfe(t,{withFileTypes:!0}).filter(i=>i.isDirectory()).map(i=>i.name);for(let i of n){let a=Gu.join(this.codayConfigDir,"projects",i,"prompts",`${e}.yml`);if(W2(a))return i;if(this.projectService)try{let o=await this.getOrCreatePromptFilePath(i,e,"project");if(W2(o))return i}catch{}}return null}validatePlaceholders(e){let t=/\{\{(\w+)\}\}/g,n=[],i=/^[A-Za-z][A-Za-z0-9-]*$/;if(e.forEach(a=>{let o;for(;(o=t.exec(a))!==null;){let s=o[1];s&&s!=="PARAMETERS"&&!i.test(s)&&n.push(s)}}),n.length>0)throw new Error(`Invalid parameter keys: ${n.join(", ")}. Keys must start with a letter and contain only letters, digits, and hyphens.`)}async create(e,t,n="local"){try{this.validatePlaceholders(t.commands);let i=Wit(),a={...t,id:i,source:n,createdAt:new Date().toISOString(),parameterFormat:this.getParameterFormat(t.commands)},o=await this.getOrCreatePromptFilePath(e,i,n);if(W2(o))throw new Error(`Prompt with ID ${i} already exists`);return Mn(o,a),console.log(`[PROMPT] Created prompt ${i} in ${n} for project ${e}`),a}catch(i){throw new Error(`Failed to create prompt: ${i instanceof Error?i.message:"Unknown error"}`)}}async get(e,t){try{let n=await this.findPromptSource(e,t);if(!n)return null;let i=await this.getOrCreatePromptFilePath(e,t,n),a=ta(i);return a?(a.source||(a.source=n),a):null}catch(n){return console.error(`Failed to get prompt ${t}:`,n),null}}async getById(e){try{let t=await this.findProjectForPrompt(e);if(!t)return null;let n=await this.get(t,e);return n?{prompt:n,projectName:t}:null}catch(t){return console.error(`Failed to get prompt ${e}:`,t),null}}async update(e,t,n,i){try{let a=await this.get(e,t);if(!a)return null;if(n.webhookEnabled!==void 0&&n.webhookEnabled!==a.webhookEnabled&&!_m(i,this.codayConfigDir))throw new Error("Only CODAY_ADMIN can enable/disable webhook for prompts");let{id:o,createdAt:s,source:c,...u}=n;n.commands&&this.validatePlaceholders(n.commands);let l={...a,...u,updatedAt:new Date().toISOString()};n.commands&&(l.parameterFormat=this.getParameterFormat(n.commands));let f=await this.getOrCreatePromptFilePath(e,t,a.source);return Mn(f,l),console.log(`[PROMPT] Updated prompt ${t} (${a.source}) by user ${i}`),l}catch(a){throw console.error(`Failed to update prompt ${t}:`,a),a}}async delete(e,t){try{let n=await this.findPromptSource(e,t);if(!n)return!1;let i=await this.getOrCreatePromptFilePath(e,t,n);return Git(i),console.log(`[PROMPT] Deleted prompt ${t} from ${n}`),!0}catch(n){return console.error(`Failed to delete prompt ${t}:`,n),!1}}getParameterFormat(e){if(e.some(a=>/\{\{PARAMETERS\}\}/.test(a)))return"";let n=/\{\{(\w+)\}\}/g,i=new Set;return e.forEach(a=>{let o;for(;(o=n.exec(a))!==null;){let s=o[1];s&&s!=="PARAMETERS"&&i.add(s)}}),i.size===0?"":Array.from(i).map(a=>`${a}=""`).join(" ")}async list(e){try{let t=[],n=["local"];this.projectService&&n.push("project");for(let i of n)try{let a=await this.getOrCreatePromptsDir(e,i);if(!W2(a))continue;let s=Cfe(a).filter(c=>c.endsWith(".yml"));for(let c of s){let u=c.replace(".yml",""),l=await this.get(e,u);l&&t.push({id:l.id,name:l.name,description:l.description,webhookEnabled:l.webhookEnabled,createdBy:l.createdBy,createdAt:l.createdAt,updatedAt:l.updatedAt,source:l.source||i,parameterFormat:l.parameterFormat})}}catch(a){console.log(`[PROMPT] Could not access ${i} prompts for ${e}:`,a)}return t.push(...j3),t.sort((i,a)=>new Date(a.createdAt).getTime()-new Date(i.createdAt).getTime())}catch(t){return console.error(`Failed to list prompts for project ${e}:`,t),[]}}async enableWebhook(e,t,n){return this.update(e,t,{webhookEnabled:!0},n)}async disableWebhook(e,t,n){return this.update(e,t,{webhookEnabled:!1},n)}};var Pfe=vr(Go(),1),rE=class{constructor(e){this.promptService=e}threadCodayManager;threadService;codayOptions;logger;initialize(e,t,n,i){this.threadCodayManager=e,this.threadService=t,this.codayOptions=n,this.logger=i}processCommands(e,t){let n;if(typeof t=="string"){let a=e.some(s=>/\{\{PARAMETERS\}\}/.test(s));if(e.some(s=>/\{\{(?!PARAMETERS\}\})\w+\}\}/.test(s)))throw new Error("Prompt contains structured placeholders ({{key}}). Use an object parameter instead of a string.");a?n=e.map(s=>s.replace(/\{\{PARAMETERS\}\}/g,t)):n=e.map((s,c)=>c===0?`${s} ${t}`.trim():s)}else typeof t=="object"&&t!==null?n=e.map(a=>{let o=a;return Object.entries(t).forEach(([s,c])=>{let u=`{{${s}}}`;o=o.replaceAll(u,String(c))}),o}):n=[...e];let i=new Set;if(n.forEach(a=>{let o=a.match(/\{\{(\w+)\}\}/g);o&&o.forEach(s=>i.add(s))}),i.size>0){let a=Array.from(i).map(o=>o.replace(/[{}]/g,"")).join(", ");throw new Error(`Missing required parameters: ${a}`)}return n}async executePrompt(e,t,n,i,a){if(!this.threadCodayManager||!this.threadService||!this.codayOptions||!this.logger)throw new Error("PromptExecutionService not initialized. Call initialize() first.");let{title:o,awaitFinalAnswer:s=!1,projectName:c}=a||{},u,l;if(c){if(l=c,u=await this.promptService.get(l,e),!u)throw new Error(`Prompt ${e} not found in project ${l}`)}else{let y=await this.promptService.getById(e);if(!y)throw new Error(`Prompt not found: ${e}`);u=y.prompt,l=y.projectName}if(i==="webhook"&&!u.webhookEnabled)throw new Error(`Prompt ${e} is not enabled for webhook execution`);if(!u.commands||u.commands.length===0)throw new Error("Prompt has no commands configured");let f=this.processCommands(u.commands,t);if(!n)throw new Error("Username is required");let d=(await this.threadService.createThread(l,n,o)).id;console.log(`[PROMPT_EXEC] Created new thread: ${d} for ${i} execution`);let m={...this.codayOptions,oneshot:!0,project:l,thread:d,prompts:f};console.log(`[PROMPT_EXEC] Creating instance for ${i} execution with ${f.length} prompts:`,f);let h=this.threadCodayManager.createWithoutConnection(d,l,n,m);h.prepareCoday();let g=h.coday.interactor,v={project:l,title:o??"Untitled",username:n,clientId:d,promptCount:f.length,awaitFinalAnswer:!!s,promptName:u.name,promptId:u.id,executionMode:i};if(i==="webhook"?this.logger.logWebhook(v):this.logger.logWebhook({...v,webhookName:u.name,webhookUuid:u.id}),s){let y=[],x=g.events.pipe((0,Pfe.filter)(k=>(console.log(`[PROMPT_EXEC] Received event type: ${k.type}, role: ${k instanceof Rt?k.role:"N/A"}`),k instanceof Rt&&k.role==="assistant"&&!!k.name))).subscribe(k=>{y.push(k)});try{await h.coday.run(),x.unsubscribe();let k=y[y.length-1];return await this.threadCodayManager.cleanup(d),{threadId:d,lastEvent:k}}catch(k){x.unsubscribe();let S=k instanceof Error?k.message:"Unknown error";throw this.logger.logWebhookError({error:`Prompt execution failed: ${S}`,username:n,project:l,clientId:d}),console.error("[PROMPT_EXEC] Error waiting for prompt completion:",k),await this.threadCodayManager.cleanup(d),k}}else return h.coday.run().catch(y=>{console.error("[PROMPT_EXEC] Error during prompt Coday run:",y)}),setTimeout(()=>{this.threadCodayManager.cleanup(d).catch(y=>{console.error("[PROMPT_EXEC] Error cleaning up prompt thread after timeout:",y)})},300*1e3),{threadId:d}}};var Y6=vr(Pc(),1);import*as ai from"fs";import*as Sm from"path";import{randomUUID as Zit}from"node:crypto";var nE=class{constructor(e,t,n){this.logger=e;this.promptService=t;this.codayConfigDir=n}schedulers=new Map;checkInterval;CHECK_INTERVAL_MS=3e4;promptExecutionService;initializeExecution(e){this.promptExecutionService=e}async initialize(){console.log("[SCHEDULER] Initializing SchedulerService..."),await this.loadAllSchedulers(),this.startScheduler(),console.log(`[SCHEDULER] SchedulerService initialized with ${this.schedulers.size} schedulers`)}stop(){this.checkInterval&&(clearInterval(this.checkInterval),this.checkInterval=void 0,console.log("[SCHEDULER] SchedulerService stopped"))}async loadAllSchedulers(){this.schedulers.clear();let e=Sm.join(this.codayConfigDir,"projects");if(!ai.existsSync(e))return;let t=ai.readdirSync(e,{withFileTypes:!0}).filter(n=>n.isDirectory()).map(n=>n.name);for(let n of t)try{let i=await this.loadProjectSchedulers(n);for(let a of i)this.schedulers.set(a.id,a)}catch(i){console.error(`[SCHEDULER] Failed to load schedulers for project ${n}:`,i)}}async loadProjectSchedulers(e){let t=this.getSchedulersDir(e);if(!ai.existsSync(t))return[];let n=ai.readdirSync(t),i=[];for(let a of n)if(a.endsWith(".yml"))try{let o=Sm.join(t,a),s=ai.readFileSync(o,"utf-8"),c=Y6.parse(s),u=this.calculateNextRunSkippingMissed(c),l=u.occurrenceCount!==c.occurrenceCount;c.nextRun=u.nextRun,c.occurrenceCount=u.occurrenceCount,l&&await this.saveScheduler(c,e),i.push(c)}catch(o){console.error(`[SCHEDULER] Failed to load scheduler from ${a}:`,o)}return i}startScheduler(){this.checkInterval=setInterval(()=>{this.checkAndExecuteSchedulers()},this.CHECK_INTERVAL_MS),this.checkAndExecuteSchedulers()}async checkAndExecuteSchedulers(){for(let e of this.schedulers.values())e.enabled&&dce(e.schedule,e.nextRun??null,e.occurrenceCount??0)&&this.executeScheduler(e).catch(t=>{console.error(`[SCHEDULER] Failed to execute scheduler ${e.id}:`,t)})}async executeSchedulerInternal(e,t,n){if(!this.promptExecutionService)throw new Error("PromptExecutionService not initialized");console.log(`[SCHEDULER] Executing scheduler "${e.name}" (${e.id}) [${n}]`);let i=e.parameters;e.parameters&&typeof e.parameters=="object"&&Object.keys(e.parameters).length===1&&"PARAMETERS"in e.parameters&&(i=String(e.parameters.PARAMETERS));let a=await this.promptExecutionService.executePrompt(e.promptId,i,e.createdBy,"scheduled",{title:n==="scheduled"?`Scheduled: ${e.name}`:`Manual: ${e.name}`,awaitFinalAnswer:!1,projectName:t});return console.log(`[SCHEDULER] Scheduler "${e.name}" executed successfully. Thread: ${a.threadId}`),a.threadId}async executeScheduler(e){let t=new Date().toISOString();console.log(`[SCHEDULER] Executing scheduler "${e.name}" (${e.id})`);let n=this.findProjectForScheduler(e.id);if(!n){console.error(`[SCHEDULER] Cannot find project for scheduler ${e.id}, skipping execution`);return}let i,a,o;e.lastRun=t,e.occurrenceCount=(e.occurrenceCount??0)+1,e.nextRun=uh(e.schedule,new Date,e.occurrenceCount);try{await this.saveScheduler(e,n),console.log(`[SCHEDULER] Next execution for "${e.name}": ${e.nextRun}`),this.schedulers.set(e.id,e),a=await this.executeSchedulerInternal(e,n,"scheduled"),i=!0}catch(s){i=!1,o=s instanceof Error?s.message:String(s),console.error(`[SCHEDULER] Scheduler "${e.name}" failed:`,s)}this.logger.logTriggerExecution({triggerId:e.id,triggerName:e.name,webhookUuid:e.promptId,projectName:n,success:i,threadId:a,error:o})}validateSchedule(e){return pce(e)}calculateNextRunSkippingMissed(e){let t=new Date,n=e.occurrenceCount??0,i=e.nextRun;if(!i)return{nextRun:uh(e.schedule,t,n),occurrenceCount:n};if(new Date(i)>=t)return{nextRun:i,occurrenceCount:n};let a=1e3,o=0,s=0;for(;o<a;){if(n++,s++,i=uh(e.schedule,t,n),!i)return s>0&&console.log(`[SCHEDULER] Scheduler "${e.name}" (${e.id}) expired after skipping ${s} missed occurrence(s)`),{nextRun:null,occurrenceCount:n};if(new Date(i)>=t)return s>0&&console.log(`[SCHEDULER] Scheduler "${e.name}" (${e.id}) skipped ${s} missed occurrence(s), next run: ${i}`),{nextRun:i,occurrenceCount:n};o++}return console.warn(`[SCHEDULER] Could not find future nextRun for scheduler ${e.id} after ${a} iterations`),{nextRun:null,occurrenceCount:n}}getSchedulersDir(e){return Sm.join(this.codayConfigDir,"projects",e,"schedulers")}getSchedulerFilePath(e,t){return Sm.join(this.getSchedulersDir(e),`${t}.yml`)}findProjectForScheduler(e){let t=Sm.join(this.codayConfigDir,"projects");if(!ai.existsSync(t))return null;let n=ai.readdirSync(t,{withFileTypes:!0}).filter(i=>i.isDirectory()).map(i=>i.name);for(let i of n){let a=this.getSchedulerFilePath(i,e);if(ai.existsSync(a))return i}return null}canAccessScheduler(e,t){return e.createdBy===t?!0:_m(t,this.codayConfigDir)}async listSchedulers(e,t){return(await this.loadProjectSchedulers(e)).filter(i=>this.canAccessScheduler(i,t)).map(i=>({id:i.id,name:i.name,enabled:i.enabled,promptId:i.promptId,schedule:i.schedule,parameters:i.parameters,lastRun:i.lastRun,nextRun:i.nextRun,createdBy:i.createdBy}))}async getScheduler(e,t,n){let i=this.getSchedulerFilePath(e,t);if(!ai.existsSync(i))return null;try{let a=ai.readFileSync(i,"utf-8"),o=Y6.parse(a),s=this.calculateNextRunSkippingMissed(o);return o.nextRun=s.nextRun,o.occurrenceCount=s.occurrenceCount,n&&!this.canAccessScheduler(o,n)?(console.log(`[SCHEDULER] Access denied for user ${n} to scheduler ${t}`),null):o}catch(a){return console.error(`[SCHEDULER] Failed to load scheduler ${t}:`,a),null}}async createScheduler(e,t,n){let i=this.validateSchedule(t.schedule);if(!i.valid)throw new Error(`Invalid schedule: ${i.error}`);if(!await this.promptService.getById(t.promptId))throw new Error(`Prompt not found: ${t.promptId}`);let o={id:Zit(),name:t.name,enabled:t.enabled??!0,promptId:t.promptId,schedule:t.schedule,parameters:t.parameters,createdBy:n,createdAt:new Date().toISOString(),nextRun:uh(t.schedule,new Date,0),occurrenceCount:0};return await this.saveScheduler(o,e),this.schedulers.set(o.id,o),console.log(`[SCHEDULER] Created scheduler "${o.name}" (${o.id}) for project ${e}`),o}async updateScheduler(e,t,n,i){let a=await this.getScheduler(e,t,i);if(!a)throw new Error(`Scheduler not found or access denied: ${t}`);if(console.log(`[SCHEDULER] Updating scheduler ${t} by user ${i}`),n.name!==void 0&&(a.name=n.name),n.enabled!==void 0&&(a.enabled=n.enabled),n.parameters!==void 0&&(a.parameters=n.parameters),n.promptId!==void 0){if(!await this.promptService.getById(n.promptId))throw new Error(`Prompt not found: ${n.promptId}`);a.promptId=n.promptId}if(n.schedule!==void 0){let o=this.validateSchedule(n.schedule);if(!o.valid)throw new Error(`Invalid schedule: ${o.error}`);a.schedule=n.schedule,a.occurrenceCount=0,a.nextRun=uh(n.schedule,new Date,0)}return await this.saveScheduler(a,e),this.schedulers.set(a.id,a),console.log(`[SCHEDULER] Updated scheduler "${a.name}" (${a.id})`),a}async deleteScheduler(e,t,n){if(!await this.getScheduler(e,t,n))return!1;let a=this.getSchedulerFilePath(e,t);return ai.unlinkSync(a),this.schedulers.delete(t),console.log(`[SCHEDULER] Deleted scheduler ${t} by user ${n}`),!0}async enableScheduler(e,t,n){return this.updateScheduler(e,t,{enabled:!0},n)}async disableScheduler(e,t,n){return this.updateScheduler(e,t,{enabled:!1},n)}async runSchedulerNow(e,t,n){let i=await this.getScheduler(e,t,n);if(!i)throw new Error(`Scheduler not found or access denied: ${t}`);let a=await this.executeSchedulerInternal(i,e,"manual");return i.lastRun=new Date().toISOString(),await this.saveScheduler(i,e),this.schedulers.set(i.id,i),this.logger.logTriggerExecution({triggerId:i.id,triggerName:i.name,webhookUuid:i.promptId,projectName:e,success:!0,threadId:a}),a}async saveScheduler(e,t){if(!t&&(t=this.findProjectForScheduler(e.id),!t))throw new Error(`Cannot find project for scheduler ${e.id}`);let n=this.getSchedulersDir(t);ai.mkdirSync(n,{recursive:!0});let i=this.getSchedulerFilePath(t,e.id),a=Y6.stringify(e);ai.writeFileSync(i,a,"utf-8")}};import*as Mc from"node:path";import*as Ofe from"node:os";import{existsSync as Dfe,lstatSync as Jit,mkdirSync as Ife,readdirSync as Kit}from"fs";var iE=class r{projectsConfigPath;static PROJECT_FILENAME="project.yaml";constructor(e){let t=Mc.join(Ofe.userInfo().homedir,".coday");this.projectsConfigPath=Mc.join(e??t,"projects"),Ife(this.projectsConfigPath,{recursive:!0})}listProjects(){return Kit(this.projectsConfigPath).filter(t=>Jit(Mc.join(this.projectsConfigPath,t)).isDirectory())}getProjectInfo(e){let t=Mc.join(this.projectsConfigPath,e),n=Mc.join(t,r.PROJECT_FILENAME);return Dfe(n)?{name:e,configPath:t}:null}exists(e){return this.getProjectInfo(e)!==null}getConfig(e){let t=this.getProjectInfo(e);if(!t)return console.log(`[PROJECT_REPO] No project info found for: '${e}'`),null;let n=Mc.join(t.configPath,r.PROJECT_FILENAME),i=ta(n);if(!i)return console.log(`[PROJECT_REPO] Failed to load config for '${e}' from ${n}`),null;let a=ch(i,hce);return a!==i&&(console.log(`[PROJECT_REPO] Config migrated for '${e}', saving...`),Mn(n,a)),a}saveConfig(e,t){let n=this.getProjectInfo(e);if(!n)throw new Error(`Project '${e}' does not exist`);let i=Mc.join(n.configPath,r.PROJECT_FILENAME);Mn(i,t)}createProject(e,t){let n=Mc.join(this.projectsConfigPath,e),i=Mc.join(n,r.PROJECT_FILENAME);return Dfe(i)?!1:(console.log(`[PROJECT_REPO] Creating project '${e}' with path: ${t}`),Ife(n,{recursive:!0}),Mn(i,{version:1,path:t,integration:{},storage:{type:"file"},agents:[]}),console.log(`[PROJECT_REPO] Project '${e}' created successfully`),!0)}deleteProject(e){return this.getProjectInfo(e),!1}};var bM=vr(Pc(),1);import{promises as Zu}from"fs";import Zp from"path";var $fe=async r=>{try{let e=await Zu.readFile(r,"utf-8");return bM.default.parse(e)}catch{return null}},aE=class{constructor(e){this.projectsDir=e}getThreadsDir(e){return Zp.join(this.projectsDir,e,"threads")}async ensureThreadsDir(e){let t=this.getThreadsDir(e);try{await Zu.mkdir(t,{recursive:!0})}catch(n){throw new yu(`Failed to initialize threads directory for project ${e}`,n)}}getThreadFileName(e){return`${e.id}.yml`}async findThreadFile(e,t){try{let n=this.getThreadsDir(e),i=`${t}.yml`;try{return await Zu.access(Zp.join(n,i)),i}catch{}return(await Zu.readdir(n)).find(s=>s.endsWith(`-${t}.yml`))||null}catch(n){throw new yu(`Error finding thread ${t} in project ${e}`,n)}}async getById(e,t){try{let n=await this.findThreadFile(e,t);if(!n)return null;let i=this.getThreadsDir(e),a=Zp.join(i,n),o=await $fe(a);if(!o)return null;let s=ch(o,ZR);return s.projectId||(s.projectId=e),s!==o&&Mn(a,s),new bu(s)}catch(n){throw new yu(`Failed to read thread ${t} from project ${e}`,n)}}async save(e,t){await this.ensureThreadsDir(e);try{if(t.id||(t.id=crypto.randomUUID()),t.projectId||(t.projectId=e),t.projectId!==e)throw new Error(`Thread projectId mismatch: expected ${e}, got ${t.projectId}`);let n=this.getThreadsDir(e),i=this.getThreadFileName(t),a=Zp.join(n,i),o=await this.findThreadFile(e,t.id);if(o&&o!==i){let u=Zp.join(n,o);try{await Zu.unlink(u),console.log(`[THREAD-REPO] Migrated/renamed thread file: ${o} \u2192 ${i}`)}catch(l){console.warn(`[THREAD-REPO] Could not delete old thread file: ${o}`,l)}}let s={...t.serialize(),version:ZR.length+1},c=bM.default.stringify(s);return await Zu.writeFile(a,c,"utf-8"),t}catch(n){throw new yu(`Failed to save thread ${t.id} to project ${e}`,n)}}async listByProject(e){try{let t=this.getThreadsDir(e);try{await Zu.access(t)}catch{return[]}let n=await Zu.readdir(t);return(await Promise.all(n.filter(a=>a.endsWith(".yml")).map(async a=>{let o=await $fe(Zp.join(t,a));if(!o)return null;let s=o.projectId||e;return{id:o.id,username:o.username,projectId:s,name:o.name??"...",summary:o.summary??"",createdDate:o.createdDate??"",modifiedDate:o.modifiedDate??"",price:o.price??0,starring:o.starring??[],users:Array.isArray(o.users)?o.users.map(c=>typeof c=="string"?{userId:c}:c):o.username?[{userId:o.username}]:[],parentThreadId:o.parentThreadId,parentEventId:o.parentEventId,delegatedAgentName:o.delegatedAgentName,delegatedTask:o.delegatedTask}}))).filter(a=>!!a).sort((a,o)=>a.modifiedDate>o.modifiedDate?-1:1)}catch(t){throw new yu(`Failed to list threads for project ${e}`,t)}}async delete(e,t){try{let n=await this.findThreadFile(e,t);if(!n)return!1;let i=this.getThreadsDir(e);return await Zu.unlink(Zp.join(i,n)),!0}catch(n){throw new yu(`Failed to delete thread ${t} from project ${e}`,n)}}};var oE=class{constructor(e,t,n){this.projectRepository=e;this.projectsDir=t;this.threadFileService=n}repositoryCache=new Map;threadListCache=new Map;CACHE_TTL_MS=14400*1e3;loadingPromises=new Map;getThreadRepository(e){let t=this.repositoryCache.get(e);if(t)return t;if(!this.projectRepository.getProjectInfo(e))throw new Error(`Project '${e}' not found`);let i=new aE(this.projectsDir);return this.repositoryCache.set(e,i),i}clearCache(e){e?this.repositoryCache.delete(e):this.repositoryCache.clear()}async listThreads(e,t){let n=this.threadListCache.get(e);if(n&&Date.now()-n.timestamp<this.CACHE_TTL_MS)return n.data.filter(o=>rn(o,t)).sort((o,s)=>o.modifiedDate>s.modifiedDate?-1:1);let i=this.loadingPromises.get(e);if(i)return await i,this.listThreads(e,t);let a=this.loadThreadListFromDisk(e);this.loadingPromises.set(e,a);try{return(await a).filter(s=>rn(s,t)).sort((s,c)=>s.modifiedDate>c.modifiedDate?-1:1)}catch(o){throw this.threadListCache.delete(e),o}finally{this.loadingPromises.delete(e)}}async loadThreadListFromDisk(e){let n=await this.getThreadRepository(e).listByProject(e);return this.threadListCache.set(e,{data:n,timestamp:Date.now()}),n}updateThreadInCache(e,t){let n=this.threadListCache.get(e);if(!n)return;let i=n.data.findIndex(a=>a.id===t.id);i!==-1?n.data[i]=t:n.data.push(t)}removeThreadFromCache(e,t){let n=this.threadListCache.get(e);n&&(n.data=n.data.filter(i=>i.id!==t))}async getThread(e,t){return await this.getThreadRepository(e).getById(e,t)}async createThread(e,t,n){let i=this.getThreadRepository(e),a=new bu({id:crypto.randomUUID(),username:t,projectId:e,name:n||"",price:0}),o=await i.save(e,a);return this.updateThreadInCache(e,{id:o.id,username:o.username,projectId:o.projectId,name:o.name,summary:o.summary,createdDate:o.createdDate,modifiedDate:o.modifiedDate,price:o.price,starring:o.starring,users:o.users}),o}async updateThread(e,t,n){let i=this.getThreadRepository(e),a=await i.getById(e,t);if(!a){let c=this.threadListCache.get(e)?.data.find(u=>u.id===t);if(c)a=new bu({id:c.id,username:c.username,projectId:c.projectId,name:c.name,price:c.price});else throw new Error(`Thread '${t}' not found in project '${e}'`)}n.name!==void 0&&(a.name=n.name),n.summary!==void 0&&(a.summary=n.summary),n.users!==void 0&&(a.users=n.users);let o=await i.save(e,a);return this.updateThreadInCache(e,{id:o.id,username:o.username,projectId:o.projectId,name:o.name,summary:o.summary,createdDate:o.createdDate,modifiedDate:o.modifiedDate,price:o.price,starring:o.starring,users:o.users}),o}async starThread(e,t,n){let i=this.getThreadRepository(e),a=await i.getById(e,t);if(!a)throw new Error(`Thread '${t}' not found in project '${e}'`);a.starring.includes(n)||a.starring.push(n);let o=await i.save(e,a);return this.updateThreadInCache(e,{id:o.id,username:o.username,projectId:o.projectId,name:o.name,summary:o.summary,createdDate:o.createdDate,modifiedDate:o.modifiedDate,price:o.price,starring:o.starring,users:o.users}),o}async unstarThread(e,t,n){let i=this.getThreadRepository(e),a=await i.getById(e,t);if(!a)throw new Error(`Thread '${t}' not found in project '${e}'`);a.starring=a.starring.filter(s=>s!==n);let o=await i.save(e,a);return this.updateThreadInCache(e,{id:o.id,username:o.username,projectId:o.projectId,name:o.name,summary:o.summary,createdDate:o.createdDate,modifiedDate:o.modifiedDate,price:o.price,starring:o.starring,users:o.users}),o}async deleteThread(e,t){let i=await this.getThreadRepository(e).delete(e,t);return i&&(await this.threadFileService.deleteThreadFiles(e,t),this.removeThreadFromCache(e,t)),i}async saveThread(e,t){let i=await this.getThreadRepository(e).save(e,t);return this.updateThreadInCache(e,{createdDate:i.createdDate,delegatedAgentName:i.delegatedAgentName,delegatedTask:i.delegatedTask,id:i.id,modifiedDate:i.modifiedDate,name:i.name,parentEventId:i.parentEventId,parentThreadId:i.parentThreadId,price:i.price,projectId:i.projectId,starring:i.starring,summary:i.summary,username:i.username,users:i.users}),i}async listAllThreads(e){let t=this.threadListCache.get(e);if(t&&Date.now()-t.timestamp<this.CACHE_TTL_MS)return t.data.sort((a,o)=>a.modifiedDate>o.modifiedDate?-1:1);let n=this.loadingPromises.get(e);if(n)return await n,this.listAllThreads(e);let i=this.loadThreadListFromDisk(e);this.loadingPromises.set(e,i);try{return(await i).sort((o,s)=>o.modifiedDate>s.modifiedDate?-1:1)}catch(a){throw this.threadListCache.delete(e),a}finally{this.loadingPromises.delete(e)}}async exists(e,t){return await this.getThread(e,t)!==null}};var Mfe=vr(Pc(),1);import*as ba from"fs/promises";import*as X6 from"path";var jfe=5,Rfe=24,Nfe=100,Ffe={SHORT_THREADS:7,LONG_THREADS:30},sE=class{constructor(e,t){this.projectsConfigPath=e;this.logger=t}cleanupTimer=null;initialTimer=null;isRunning=!1;async start(){if(this.isRunning){this.log("Thread cleanup service already running");return}this.isRunning=!0,this.log("Starting thread cleanup service with user message-based retention"),this.initialTimer=setTimeout(async()=>{await this.performCleanup(),this.cleanupTimer=setInterval(async()=>{await this.performCleanup()},Rfe*60*60*1e3),this.log(`Thread cleanup scheduled every ${Rfe} hours`)},jfe*60*1e3),this.log(`Initial cleanup scheduled in ${jfe} minutes`)}async stop(){this.initialTimer&&(clearTimeout(this.initialTimer),this.initialTimer=null),this.cleanupTimer&&(clearInterval(this.cleanupTimer),this.cleanupTimer=null),this.isRunning=!1,this.log("Thread cleanup service stopped (all timers cleared)")}async performCleanup(){let e=Date.now(),t=0,n=0,i=0;try{this.log("Starting thread cleanup...");let a=await ba.readdir(this.projectsConfigPath);this.log(`Found ${a.length} projects to scan`);for(let s of a)try{let c=X6.join(this.projectsConfigPath,s);if(!(await ba.lstat(c)).isDirectory())continue;let l=X6.join(c,"threads");try{await ba.access(l)}catch{continue}let{scanned:f,deleted:p,errors:d}=await this.cleanupProjectThreads(s,l);t+=f,n+=p,i+=d}catch(c){this.logError(`Error processing project ${s}: ${c}`),i++}let o=Date.now()-e;this.log(`Cleanup completed: ${n}/${t} threads deleted across all projects in ${o}ms (${i} errors)`)}catch(a){let o=Date.now()-e;this.logError(`Cleanup failed after ${o}ms: ${a}`)}}async cleanupProjectThreads(e,t){let n=0,i=0,a=0;try{let s=(await ba.readdir(t)).filter(c=>c.endsWith(".yml"));if(n=s.length,s.length===0)return{scanned:n,deleted:i,errors:a};this.log(`Scanning ${s.length} threads in project ${e}`);for(let c=0;c<s.length;c+=Nfe){let u=s.slice(c,c+Nfe),l=await this.processBatch(u,t,e);i+=l.deleted,a+=l.errors}i>0&&this.log(`Project ${e}: deleted ${i}/${n} expired threads`)}catch(o){this.logError(`Error scanning project ${e}: ${o}`),a++}return{scanned:n,deleted:i,errors:a}}async processBatch(e,t,n){let i=0,a=0;return await Promise.all(e.map(async o=>{let s=X6.join(t,o),c;try{let u=await ba.readFile(s,"utf-8");c=Mfe.parse(u)}catch(u){await ba.unlink(s),this.logError(`Error processing file ${o} in project ${n}: ${u}`)}if(!(!c||!c.modifiedDate)&&!(c.starring&&Array.isArray(c.starring)&&c.starring.length>0)&&this.shouldDeleteThread(c)){let u=this.countUserMessages(c.messages||[]),l=this.getDaysSinceModified(c.modifiedDate),f=c.id||o.replace(".yml","");await ba.unlink(s),await this.deleteThreadFiles(t,f),i++,this.logger.logThreadCleanup(n,o),console.log(`ThreadCleanup: Deleted thread ${f} and its files directory (${u} user messages, ${l} days old)`)}})),{deleted:i,errors:a}}shouldDeleteThread(e){if(!e||!e.modifiedDate)return!1;let n=this.countUserMessages(e.messages||[])<=3?Ffe.SHORT_THREADS:Ffe.LONG_THREADS;return this.getDaysSinceModified(e.modifiedDate)>n}countUserMessages(e){return e.filter(t=>t&&t.type==="message"&&t.role==="user").length}getDaysSinceModified(e){let t=new Date(e),i=new Date().getTime()-t.getTime();return Math.floor(i/(1e3*60*60*24))}async deleteThreadFiles(e,t){try{let n=X6.join(e,`${t}-files`);try{await ba.access(n),await ba.rm(n,{recursive:!0,force:!0}),console.log(`ThreadCleanup: Deleted files directory ${n}`)}catch(i){if(i.code!=="ENOENT")throw i}}catch(n){console.error(`ThreadCleanup: Error deleting files for ${t}:`,n)}}log(e){let t=new Date().toISOString();console.log(`[${t}] ThreadCleanup: ${e}`)}logError(e){let t=new Date().toISOString();console.error(`[${t}] ThreadCleanup ERROR: ${e}`)}async forceCleanup(){this.log("Manual cleanup triggered"),await this.performCleanup()}};import*as qi from"path";import*as Jp from"fs";import*as xa from"fs/promises";var cE=class{constructor(e){this.projectsDir=e}getThreadFilesDir(e,t){return qi.join(this.projectsDir,e,"threads",`${t}-files`)}async ensureThreadFilesDir(e,t){let n=this.getThreadFilesDir(e,t);Jp.existsSync(n)||await xa.mkdir(n,{recursive:!0})}async listFiles(e,t){await this.ensureThreadFilesDir(e,t);let n=this.getThreadFilesDir(e,t),i=await xa.readdir(n);return await Promise.all(i.map(async o=>{let s=qi.join(n,o),c=await xa.stat(s);return{filename:o,size:c.size,lastModified:c.mtime.toISOString()}}))}async saveFile(e,t,n,i){let a=this.getThreadFilesDir(e,t);await xa.mkdir(a,{recursive:!0});let o=qi.join(a,n);await xa.writeFile(o,i)}async getFile(e,t,n){await this.ensureThreadFilesDir(e,t);let i=this.getThreadFilesDir(e,t),a=qi.join(i,n),o=qi.resolve(a),s=qi.resolve(i);if(!o.startsWith(s))throw new Error("Access denied: invalid file path");if(!Jp.existsSync(a))throw new Error(`File '${n}' not found`);return await xa.readFile(a)}async getFilePath(e,t,n){await this.ensureThreadFilesDir(e,t);let i=this.getThreadFilesDir(e,t),a=qi.join(i,n),o=qi.resolve(a),s=qi.resolve(i);if(!o.startsWith(s))throw new Error("Access denied: invalid file path");if(!Jp.existsSync(a))throw new Error(`File '${n}' not found`);return a}fileExists(e,t,n){let i=this.getThreadFilesDir(e,t),a=qi.join(i,n),o=qi.resolve(a),s=qi.resolve(i);return o.startsWith(s)?Jp.existsSync(a):!1}async deleteFile(e,t,n){await this.ensureThreadFilesDir(e,t);let i=this.getThreadFilesDir(e,t),a=qi.join(i,n),o=qi.resolve(a),s=qi.resolve(i);if(!o.startsWith(s))throw new Error("Access denied: invalid file path");if(!Jp.existsSync(a))throw new Error(`File '${n}' not found`);await xa.unlink(a)}async deleteThreadFiles(e,t){try{let n=this.getThreadFilesDir(e,t);Jp.existsSync(n)&&(await xa.rm(n,{recursive:!0,force:!0}),console.log(`Deleted thread files directory: ${n}`))}catch(n){console.error(`Error deleting thread files for ${t}:`,n)}}};var Lfe=vr(Go(),1);var uE=class{constructor(e,t,n,i){this.threadRepository=t;this.projectId=n;this.interactor=i;this.username=e.username}activeThread$=new Lfe.BehaviorSubject(null);isKilled=!1;activeThread=this.activeThread$.asObservable();username;async kill(){this.isKilled=!0,this.activeThread$.complete()}async create(e){let t=new bu({id:"",username:this.username,name:e??"",price:0});return this.activeThread$.next(t),t}async select(e){let t=await this.threadRepository.getById(this.projectId,e);if(!t)throw new Error(`Thread ${e} not found`);return this.activeThread$.next(t),t}async save(e){let t=this.activeThread$.value;if(!t){console.error(`No thread existing when save attempt with name '${e}'`);return}e&&(t.name=e);let n=await this.threadRepository.save(this.projectId,t);this.activeThread$.next(n)}async autoSave(e){if(this.isKilled){console.log("Autosave skipped: service has been killed");return}let t=this.activeThread$.value;if(!t||t.messagesLength==0){console.log(`Autosave of an empty or falsy thread aborted, threadId: ${t?.id}, user: ${this.username}`);return}try{e&&(t.name=e),await this.threadRepository.save(this.projectId,t),this.interactor&&this.interactor.sendEvent(new uc({threadId:t.id,name:t.name||void 0}))}catch(n){console.log("Autosave failed (service may have been killed):",n instanceof Error?n.message:n)}}async truncateAtUserMessage(e){let t=this.activeThread$.value;if(!t)return console.error("No active thread available for truncation"),!1;let n=t.truncateAtUserMessage(e);return n||this.interactor?.warn("Failed to truncate thread."),n}getCurrentThread(){return this.activeThread$.value}};async function qfe(r,e){let t=e.project.selectedProject,n=e.user.getUserData(t.name),i=await QS(t.config.path,r,n),a=new k0({...i,root:t.config.path,name:t.name},e.user.username);return console.log(`[BUILD_CONTEXT] Context for '${a.project.name}' \u2192 root: ${a.project.root}, desc: ${a.project.description?.length||0} chars`),a}var lE=class extends Fe{constructor(t,n){super({commandWord:"select-project",description:"Select an existing project"});this.interactor=t;this.services=n}async handle(t,n){try{let i=this.getSubCommand(t);return await this.selectProject(i)??n}catch(i){return this.interactor.error(`Invalid project selection because: ${i.toString()}`),n}}async selectProject(t){return this.services.project.selectProject(t),this.services.project.selectedProject?qfe(this.interactor,this.services):null}};var fE=class extends Fe{constructor(t,n,i){super({commandWord:"add",description:"Add a new integration configuration. User level is default, use --project/-p for project level."});this.interactor=t;this.service=n;this.editHandler=i}async handle(t,n){let a=!!Tt(this.getSubCommand(t),[{key:"project",alias:"p"}]).project,o=a?"project":"user",s=this.service.getMergedIntegrations(),c=Object.keys(s),u=Xf.filter(h=>!c.includes(h));if(u.length===0)return this.interactor.displayText(`All available integrations (${Xf.join(", ")}) are already configured.`),n;let l=await this.interactor.chooseOption(u.sort(),"Select integration to add:",`Available integrations to configure:
|
|
877
|
+
`)):""}loadMemoriesFrom(e){this.memories=[],this.userMemoriesPath=this.readMemories(this.userMemoriesPath)?this.userMemoriesPath:void 0;let t=e?.configPath?eE.join(e.configPath,vM):void 0;this.projectMemoriesPath=this.readMemories(t)?t:void 0,e&&this.checkInit()}readMemories(e){if(!e)return!1;try{if(!qit(e)){let n=xfe.stringify({memories:[]});zit(e,n)}let t=ta(e);return t?(this.memories.push(...t.memories),!0):!1}catch{return!1}}checkInit(){if(!this.userMemoriesPath||!this.projectMemoriesPath)throw new Error("user or project path not set for memory service")}saveMemories(){this.checkInit();let e=this.memories.filter(n=>n.level==="USER"),t=this.memories.filter(n=>n.level==="PROJECT");Mn(this.userMemoriesPath,{memories:e}),Mn(this.projectMemoriesPath,{memories:t})}};import*as _fe from"crypto";import*as Va from"node:fs/promises";import*as Fc from"path";var bm=class r{constructor(e,t,n){this.repository=e;this.defaultProject=t;this.isForcedMode=n}maskingService=new B2;listProjects(){let e=this.repository.listProjects();return this.isForcedMode&&this.defaultProject?e.filter(t=>t===this.defaultProject).map(t=>{let n=this.repository.getConfig(t);return{name:t,volatile:n?.volatile}}):e.map(t=>{let n=this.repository.getConfig(t);return{name:t,volatile:n?.volatile}})}getDefaultProject(){return this.defaultProject||null}getForcedMode(){return this.isForcedMode}getProject(e){this.checkAgainstForced(e);let t=this.repository.getConfig(e);if(t)return{name:e,config:t};if(!this.isForcedMode&&this.defaultProject){let n=process.cwd(),i=r.generateProjectId(n),a=Fc.basename(n);if(e===a||e===i||e===this.defaultProject){console.log(`[PROJECT_SERVICE] Creating volatile project for '${e}' at ${n}`);let o=this.getOrCreateVolatileProject(n);if(t=this.repository.getConfig(o),t)return{name:o,config:t}}}return null}exists(e){return this.checkAgainstForced(e),this.repository.exists(e)}createProject(e,t){if(this.checkAgainstForced(e),!e||!t)throw new Error("Project name and path are required");if(!this.repository.createProject(e,t))throw new Error(`Project '${e}' already exists`)}updateProjectConfig(e,t){if(this.checkAgainstForced(e),!this.repository.exists(e))throw new Error(`Project '${e}' does not exist`);this.repository.saveConfig(e,t)}deleteProject(e){if(this.checkAgainstForced(e),!this.repository.deleteProject(e))throw new Error(`Project '${e}' does not exist`)}getProjectConfigForClient(e){this.checkAgainstForced(e);let t=this.repository.getConfig(e);return t?this.maskingService.maskConfig(t):null}updateProjectConfigFromClient(e,t){this.checkAgainstForced(e);let n=this.repository.getConfig(e);if(!n)throw new Error(`Project '${e}' does not exist`);let i=this.maskingService.unmaskConfig(t,n);this.repository.saveConfig(e,i)}checkAgainstForced(e){if(this.isForcedMode&&this.defaultProject&&this.defaultProject!==e)throw Error(`Project selection outside of ${this.defaultProject} not allowed`)}getOrCreateVolatileProject(e){let t=r.generateProjectId(e);if(this.repository.exists(t))return t;this.repository.createProject(t,e);let n=this.repository.getConfig(t);return n&&(n.volatile=!0,n.createdAt=Date.now(),this.repository.saveConfig(t,n)),t}async registerWorktreeProject(e,t,n){let i=this.repository.getConfig(n),a=i?{...i,path:t,volatile:void 0,createdAt:void 0}:{version:1,path:t,integration:{},storage:{type:"file"},agents:[]};this.repository.createProject(e,t),this.repository.saveConfig(e,a);let o=this.repository.getProjectInfo(n);if(o){let s=this.repository.getProjectInfo(e);if(s)for(let c of["agents","prompts","schedulers","memories"]){let u=Fc.join(o.configPath,c),l=Fc.join(s.configPath,c);try{await Va.access(u),await Va.symlink(u,l)}catch{}}}}async unregisterWorktreeProject(e){let t=this.repository.getProjectInfo(e);if(!t)return;let n=e.lastIndexOf("__");if(n!==-1){let i=e.substring(0,n),a=this.repository.getProjectInfo(i);a&&await this.migrateThreadsToParent(t.configPath,a.configPath)}await Va.rm(t.configPath,{recursive:!0,force:!0})}async migrateThreadsToParent(e,t){let n=Fc.join(e,"threads"),i=Fc.join(t,"threads");try{await Va.access(n)}catch{return}await Va.mkdir(i,{recursive:!0});let a=await Va.readdir(n);for(let o of a){let s=Fc.join(n,o),c=Fc.join(i,o);try{await Va.access(c)}catch{await Va.rename(s,c)}}}static generateProjectId(e){let t=Fc.basename(e),n=_fe.createHash("sha256").update(e).digest("hex").substring(0,8);return`${t}_${n}`}};var kfe=vr(Go(),1);import*as xm from"node:path";import*as Efe from"node:os";import{mkdirSync as Uit}from"fs";var Bit="projects",Sfe="project.yaml",wm=class{constructor(e,t,n){this.interactor=e;this.projectService=t;let i=xm.join(Efe.userInfo().homedir,".coday");this.projectsConfigPath=xm.join(n??i,Bit),Uit(this.projectsConfigPath,{recursive:!0}),this.projects=this.projectService.listProjects().map(a=>a.name)}projectsConfigPath;maskingService=new B2;projects;selectedProjectBehaviorSubject=new kfe.BehaviorSubject(null);selectedProject$=this.selectedProjectBehaviorSubject.asObservable();selectProject(e){let t=xm.join(this.projectsConfigPath,e),n=xm.join(t,Sfe),i=this.projectService.getProject(e)?.config;if(!i?.path){console.log(`[PROJECT_STATE_SERVICE] ERROR: No project path in config for '${e}'`),this.interactor.error("Invalid selection, project path needed \u{1F622}.");return}let o={name:e,config:i,configPath:t};console.log(`[PROJECT_STATE_SERVICE] Selected '${e}' \u2192 ${i.path}`),this.updateSelectedProject(o),this.interactor.displayText(`Project local configuration used: ${n}`)}updateSelectedProject(e){this.selectedProjectBehaviorSubject.next(e)}get selectedProject(){return this.selectedProjectBehaviorSubject.value}save(e){let t=this.selectedProjectBehaviorSubject.value;if(!t){this.interactor.error("No current project selected, save not possible");return}let n={...t.config,...e};Mn(xm.join(t.configPath,Sfe),n),t.config=n,this.updateSelectedProject(t)}getConfigForClient(){let e=this.selectedProjectBehaviorSubject.value;return e?this.maskingService.maskConfig(e.config):null}updateConfigFromClient(e){let t=this.selectedProjectBehaviorSubject.value;if(!t){this.interactor.error("No current project selected, update not possible");return}let n=this.maskingService.unmaskConfig(e,t.config);this.save(n)}};import*as Gu from"node:path";import*as Tfe from"node:os";import{randomUUID as Wit}from"node:crypto";import{existsSync as W2,mkdirSync as Vit,readdirSync as Cfe,unlinkSync as Git}from"fs";import*as yM from"node:path";import*as Afe from"node:os";function Hit(r){return r.replace(/[^a-zA-Z0-9]/g,"_")}function _m(r,e){try{let t=yM.join(Afe.userInfo().homedir,".coday"),n=Hit(r),i=yM.join(e??t,"users",n,"user.yaml"),a=ta(i);return a?a.groups?.includes("CODAY_ADMIN")??!1:!1}catch{return!1}}var tE=class{codayConfigDir;projectService;constructor(e,t){let n=Gu.join(Tfe.userInfo().homedir,".coday");this.codayConfigDir=e??n,this.projectService=t}getProjectPath(e){if(this.projectService)return this.projectService.getProject(e)?.config.path}async getOrCreatePromptsDir(e,t){let n;if(t==="local")n=Gu.join(this.codayConfigDir,"projects",e,"prompts");else{let i=this.getProjectPath(e);if(!i)throw new Error("Project path not configured, cannot access project prompts");let a=await f2({text:"coday.yaml",root:i});if(a.length===0)throw new Error(`coday.yaml not found in project path: ${i}`);let o=Gu.dirname(a[0]);n=Gu.join(i,o,"prompts")}return W2(n)||(Vit(n,{recursive:!0}),console.log(`[PROMPT] Created prompts directory: ${n}`)),n}async getOrCreatePromptFilePath(e,t,n){let i=await this.getOrCreatePromptsDir(e,n);return Gu.join(i,`${t}.yml`)}async findPromptSource(e,t){let n=await this.getOrCreatePromptFilePath(e,t,"local");if(W2(n))return"local";if(this.projectService)try{let i=await this.getOrCreatePromptFilePath(e,t,"project");if(W2(i))return"project"}catch{}return null}async findProjectForPrompt(e){let t=Gu.join(this.codayConfigDir,"projects");if(!W2(t))return null;let n=Cfe(t,{withFileTypes:!0}).filter(i=>i.isDirectory()).map(i=>i.name);for(let i of n){let a=Gu.join(this.codayConfigDir,"projects",i,"prompts",`${e}.yml`);if(W2(a))return i;if(this.projectService)try{let o=await this.getOrCreatePromptFilePath(i,e,"project");if(W2(o))return i}catch{}}return null}validatePlaceholders(e){let t=/\{\{(\w+)\}\}/g,n=[],i=/^[A-Za-z][A-Za-z0-9-]*$/;if(e.forEach(a=>{let o;for(;(o=t.exec(a))!==null;){let s=o[1];s&&s!=="PARAMETERS"&&!i.test(s)&&n.push(s)}}),n.length>0)throw new Error(`Invalid parameter keys: ${n.join(", ")}. Keys must start with a letter and contain only letters, digits, and hyphens.`)}async create(e,t,n="local"){try{this.validatePlaceholders(t.commands);let i=Wit(),a={...t,id:i,source:n,createdAt:new Date().toISOString(),parameterFormat:this.getParameterFormat(t.commands)},o=await this.getOrCreatePromptFilePath(e,i,n);if(W2(o))throw new Error(`Prompt with ID ${i} already exists`);return Mn(o,a),console.log(`[PROMPT] Created prompt ${i} in ${n} for project ${e}`),a}catch(i){throw new Error(`Failed to create prompt: ${i instanceof Error?i.message:"Unknown error"}`)}}async get(e,t){try{let n=await this.findPromptSource(e,t);if(!n)return null;let i=await this.getOrCreatePromptFilePath(e,t,n),a=ta(i);return a?(a.source||(a.source=n),a):null}catch(n){return console.error(`Failed to get prompt ${t}:`,n),null}}async getById(e){try{let t=await this.findProjectForPrompt(e);if(!t)return null;let n=await this.get(t,e);return n?{prompt:n,projectName:t}:null}catch(t){return console.error(`Failed to get prompt ${e}:`,t),null}}async update(e,t,n,i){try{let a=await this.get(e,t);if(!a)return null;if(n.webhookEnabled!==void 0&&n.webhookEnabled!==a.webhookEnabled&&!_m(i,this.codayConfigDir))throw new Error("Only CODAY_ADMIN can enable/disable webhook for prompts");let{id:o,createdAt:s,source:c,...u}=n;n.commands&&this.validatePlaceholders(n.commands);let l={...a,...u,updatedAt:new Date().toISOString()};n.commands&&(l.parameterFormat=this.getParameterFormat(n.commands));let f=await this.getOrCreatePromptFilePath(e,t,a.source);return Mn(f,l),console.log(`[PROMPT] Updated prompt ${t} (${a.source}) by user ${i}`),l}catch(a){throw console.error(`Failed to update prompt ${t}:`,a),a}}async delete(e,t){try{let n=await this.findPromptSource(e,t);if(!n)return!1;let i=await this.getOrCreatePromptFilePath(e,t,n);return Git(i),console.log(`[PROMPT] Deleted prompt ${t} from ${n}`),!0}catch(n){return console.error(`Failed to delete prompt ${t}:`,n),!1}}getParameterFormat(e){if(e.some(a=>/\{\{PARAMETERS\}\}/.test(a)))return"";let n=/\{\{(\w+)\}\}/g,i=new Set;return e.forEach(a=>{let o;for(;(o=n.exec(a))!==null;){let s=o[1];s&&s!=="PARAMETERS"&&i.add(s)}}),i.size===0?"":Array.from(i).map(a=>`${a}=""`).join(" ")}async list(e){try{let t=[],n=["local"];this.projectService&&n.push("project");for(let i of n)try{let a=await this.getOrCreatePromptsDir(e,i);if(!W2(a))continue;let s=Cfe(a).filter(c=>c.endsWith(".yml"));for(let c of s){let u=c.replace(".yml",""),l=await this.get(e,u);l&&t.push({id:l.id,name:l.name,description:l.description,webhookEnabled:l.webhookEnabled,createdBy:l.createdBy,createdAt:l.createdAt,updatedAt:l.updatedAt,source:l.source||i,parameterFormat:l.parameterFormat})}}catch(a){console.log(`[PROMPT] Could not access ${i} prompts for ${e}:`,a)}return t.push(...j3),t.sort((i,a)=>new Date(a.createdAt).getTime()-new Date(i.createdAt).getTime())}catch(t){return console.error(`Failed to list prompts for project ${e}:`,t),[]}}async enableWebhook(e,t,n){return this.update(e,t,{webhookEnabled:!0},n)}async disableWebhook(e,t,n){return this.update(e,t,{webhookEnabled:!1},n)}};var Pfe=vr(Go(),1),rE=class{constructor(e){this.promptService=e}threadCodayManager;threadService;codayOptions;logger;initialize(e,t,n,i){this.threadCodayManager=e,this.threadService=t,this.codayOptions=n,this.logger=i}processCommands(e,t){let n;if(typeof t=="string"){let a=e.some(s=>/\{\{PARAMETERS\}\}/.test(s));if(e.some(s=>/\{\{(?!PARAMETERS\}\})\w+\}\}/.test(s)))throw new Error("Prompt contains structured placeholders ({{key}}). Use an object parameter instead of a string.");a?n=e.map(s=>s.replace(/\{\{PARAMETERS\}\}/g,t)):n=e.map((s,c)=>c===0?`${s} ${t}`.trim():s)}else typeof t=="object"&&t!==null?n=e.map(a=>{let o=a;return Object.entries(t).forEach(([s,c])=>{let u=`{{${s}}}`;o=o.replaceAll(u,String(c))}),o}):n=[...e];let i=new Set;if(n.forEach(a=>{let o=a.match(/\{\{(\w+)\}\}/g);o&&o.forEach(s=>i.add(s))}),i.size>0){let a=Array.from(i).map(o=>o.replace(/[{}]/g,"")).join(", ");throw new Error(`Missing required parameters: ${a}`)}return n}async executePrompt(e,t,n,i,a){if(!this.threadCodayManager||!this.threadService||!this.codayOptions||!this.logger)throw new Error("PromptExecutionService not initialized. Call initialize() first.");let{title:o,awaitFinalAnswer:s=!1,projectName:c}=a||{},u,l;if(c){if(l=c,u=await this.promptService.get(l,e),!u)throw new Error(`Prompt ${e} not found in project ${l}`)}else{let y=await this.promptService.getById(e);if(!y)throw new Error(`Prompt not found: ${e}`);u=y.prompt,l=y.projectName}if(i==="webhook"&&!u.webhookEnabled)throw new Error(`Prompt ${e} is not enabled for webhook execution`);if(!u.commands||u.commands.length===0)throw new Error("Prompt has no commands configured");let f=this.processCommands(u.commands,t);if(!n)throw new Error("Username is required");let d=(await this.threadService.createThread(l,n,o)).id;console.log(`[PROMPT_EXEC] Created new thread: ${d} for ${i} execution`);let m={...this.codayOptions,oneshot:!0,project:l,thread:d,prompts:f};console.log(`[PROMPT_EXEC] Creating instance for ${i} execution with ${f.length} prompts:`,f);let h=this.threadCodayManager.createWithoutConnection(d,l,n,m);h.prepareCoday();let g=h.coday.interactor,v={project:l,title:o??"Untitled",username:n,clientId:d,promptCount:f.length,awaitFinalAnswer:!!s,promptName:u.name,promptId:u.id,executionMode:i};if(i==="webhook"?this.logger.logWebhook(v):this.logger.logWebhook({...v,webhookName:u.name,webhookUuid:u.id}),s){let y=[],x=g.events.pipe((0,Pfe.filter)(k=>(console.log(`[PROMPT_EXEC] Received event type: ${k.type}, role: ${k instanceof Rt?k.role:"N/A"}`),k instanceof Rt&&k.role==="assistant"&&!!k.name))).subscribe(k=>{y.push(k)});try{await h.coday.run(),x.unsubscribe();let k=y[y.length-1];return await this.threadCodayManager.cleanup(d),{threadId:d,lastEvent:k}}catch(k){x.unsubscribe();let S=k instanceof Error?k.message:"Unknown error";throw this.logger.logWebhookError({error:`Prompt execution failed: ${S}`,username:n,project:l,clientId:d}),console.error("[PROMPT_EXEC] Error waiting for prompt completion:",k),await this.threadCodayManager.cleanup(d),k}}else return h.coday.run().catch(y=>{console.error("[PROMPT_EXEC] Error during prompt Coday run:",y)}),setTimeout(()=>{this.threadCodayManager.cleanup(d).catch(y=>{console.error("[PROMPT_EXEC] Error cleaning up prompt thread after timeout:",y)})},300*1e3),{threadId:d}}};var Y6=vr(Pc(),1);import*as ai from"fs";import*as Sm from"path";import{randomUUID as Zit}from"node:crypto";var nE=class{constructor(e,t,n){this.logger=e;this.promptService=t;this.codayConfigDir=n}schedulers=new Map;checkInterval;CHECK_INTERVAL_MS=3e4;promptExecutionService;initializeExecution(e){this.promptExecutionService=e}async initialize(){console.log("[SCHEDULER] Initializing SchedulerService..."),await this.loadAllSchedulers(),this.startScheduler(),console.log(`[SCHEDULER] SchedulerService initialized with ${this.schedulers.size} schedulers`)}stop(){this.checkInterval&&(clearInterval(this.checkInterval),this.checkInterval=void 0,console.log("[SCHEDULER] SchedulerService stopped"))}async loadAllSchedulers(){this.schedulers.clear();let e=Sm.join(this.codayConfigDir,"projects");if(!ai.existsSync(e))return;let t=ai.readdirSync(e,{withFileTypes:!0}).filter(n=>n.isDirectory()).map(n=>n.name);for(let n of t)try{let i=await this.loadProjectSchedulers(n);for(let a of i)this.schedulers.set(a.id,a)}catch(i){console.error(`[SCHEDULER] Failed to load schedulers for project ${n}:`,i)}}async loadProjectSchedulers(e){let t=this.getSchedulersDir(e);if(!ai.existsSync(t))return[];let n=ai.readdirSync(t),i=[];for(let a of n)if(a.endsWith(".yml"))try{let o=Sm.join(t,a),s=ai.readFileSync(o,"utf-8"),c=Y6.parse(s),u=this.calculateNextRunSkippingMissed(c),l=u.occurrenceCount!==c.occurrenceCount;c.nextRun=u.nextRun,c.occurrenceCount=u.occurrenceCount,l&&await this.saveScheduler(c,e),i.push(c)}catch(o){console.error(`[SCHEDULER] Failed to load scheduler from ${a}:`,o)}return i}startScheduler(){this.checkInterval=setInterval(()=>{this.checkAndExecuteSchedulers()},this.CHECK_INTERVAL_MS),this.checkAndExecuteSchedulers()}async checkAndExecuteSchedulers(){for(let e of this.schedulers.values())e.enabled&&dce(e.schedule,e.nextRun??null,e.occurrenceCount??0)&&this.executeScheduler(e).catch(t=>{console.error(`[SCHEDULER] Failed to execute scheduler ${e.id}:`,t)})}async executeSchedulerInternal(e,t,n){if(!this.promptExecutionService)throw new Error("PromptExecutionService not initialized");console.log(`[SCHEDULER] Executing scheduler "${e.name}" (${e.id}) [${n}]`);let i=e.parameters;e.parameters&&typeof e.parameters=="object"&&Object.keys(e.parameters).length===1&&"PARAMETERS"in e.parameters&&(i=String(e.parameters.PARAMETERS));let a=await this.promptExecutionService.executePrompt(e.promptId,i,e.createdBy,"scheduled",{title:n==="scheduled"?`Scheduled: ${e.name}`:`Manual: ${e.name}`,awaitFinalAnswer:!1,projectName:t});return console.log(`[SCHEDULER] Scheduler "${e.name}" executed successfully. Thread: ${a.threadId}`),a.threadId}async executeScheduler(e){let t=new Date().toISOString();console.log(`[SCHEDULER] Executing scheduler "${e.name}" (${e.id})`);let n=this.findProjectForScheduler(e.id);if(!n){console.error(`[SCHEDULER] Cannot find project for scheduler ${e.id}, skipping execution`);return}let i,a,o;e.lastRun=t,e.occurrenceCount=(e.occurrenceCount??0)+1,e.nextRun=uh(e.schedule,new Date,e.occurrenceCount);try{await this.saveScheduler(e,n),console.log(`[SCHEDULER] Next execution for "${e.name}": ${e.nextRun}`),this.schedulers.set(e.id,e),a=await this.executeSchedulerInternal(e,n,"scheduled"),i=!0}catch(s){i=!1,o=s instanceof Error?s.message:String(s),console.error(`[SCHEDULER] Scheduler "${e.name}" failed:`,s)}this.logger.logTriggerExecution({triggerId:e.id,triggerName:e.name,webhookUuid:e.promptId,projectName:n,success:i,threadId:a,error:o})}validateSchedule(e){return pce(e)}calculateNextRunSkippingMissed(e){let t=new Date,n=e.occurrenceCount??0,i=e.nextRun;if(!i)return{nextRun:uh(e.schedule,t,n),occurrenceCount:n};if(new Date(i)>=t)return{nextRun:i,occurrenceCount:n};let a=1e3,o=0,s=0;for(;o<a;){if(n++,s++,i=uh(e.schedule,t,n),!i)return s>0&&console.log(`[SCHEDULER] Scheduler "${e.name}" (${e.id}) expired after skipping ${s} missed occurrence(s)`),{nextRun:null,occurrenceCount:n};if(new Date(i)>=t)return s>0&&console.log(`[SCHEDULER] Scheduler "${e.name}" (${e.id}) skipped ${s} missed occurrence(s), next run: ${i}`),{nextRun:i,occurrenceCount:n};o++}return console.warn(`[SCHEDULER] Could not find future nextRun for scheduler ${e.id} after ${a} iterations`),{nextRun:null,occurrenceCount:n}}getSchedulersDir(e){return Sm.join(this.codayConfigDir,"projects",e,"schedulers")}getSchedulerFilePath(e,t){return Sm.join(this.getSchedulersDir(e),`${t}.yml`)}findProjectForScheduler(e){let t=Sm.join(this.codayConfigDir,"projects");if(!ai.existsSync(t))return null;let n=ai.readdirSync(t,{withFileTypes:!0}).filter(i=>i.isDirectory()).map(i=>i.name);for(let i of n){let a=this.getSchedulerFilePath(i,e);if(ai.existsSync(a))return i}return null}canAccessScheduler(e,t){return e.createdBy===t?!0:_m(t,this.codayConfigDir)}async listSchedulers(e,t){return(await this.loadProjectSchedulers(e)).filter(i=>this.canAccessScheduler(i,t)).map(i=>({id:i.id,name:i.name,enabled:i.enabled,promptId:i.promptId,schedule:i.schedule,parameters:i.parameters,lastRun:i.lastRun,nextRun:i.nextRun,createdBy:i.createdBy}))}async getScheduler(e,t,n){let i=this.getSchedulerFilePath(e,t);if(!ai.existsSync(i))return null;try{let a=ai.readFileSync(i,"utf-8"),o=Y6.parse(a),s=this.calculateNextRunSkippingMissed(o);return o.nextRun=s.nextRun,o.occurrenceCount=s.occurrenceCount,n&&!this.canAccessScheduler(o,n)?(console.log(`[SCHEDULER] Access denied for user ${n} to scheduler ${t}`),null):o}catch(a){return console.error(`[SCHEDULER] Failed to load scheduler ${t}:`,a),null}}async createScheduler(e,t,n){let i=this.validateSchedule(t.schedule);if(!i.valid)throw new Error(`Invalid schedule: ${i.error}`);if(!await this.promptService.getById(t.promptId))throw new Error(`Prompt not found: ${t.promptId}`);let o={id:Zit(),name:t.name,enabled:t.enabled??!0,promptId:t.promptId,schedule:t.schedule,parameters:t.parameters,createdBy:n,createdAt:new Date().toISOString(),nextRun:uh(t.schedule,new Date,0),occurrenceCount:0};return await this.saveScheduler(o,e),this.schedulers.set(o.id,o),console.log(`[SCHEDULER] Created scheduler "${o.name}" (${o.id}) for project ${e}`),o}async updateScheduler(e,t,n,i){let a=await this.getScheduler(e,t,i);if(!a)throw new Error(`Scheduler not found or access denied: ${t}`);if(console.log(`[SCHEDULER] Updating scheduler ${t} by user ${i}`),n.name!==void 0&&(a.name=n.name),n.enabled!==void 0&&(a.enabled=n.enabled),n.parameters!==void 0&&(a.parameters=n.parameters),n.promptId!==void 0){if(!await this.promptService.getById(n.promptId))throw new Error(`Prompt not found: ${n.promptId}`);a.promptId=n.promptId}if(n.schedule!==void 0){let o=this.validateSchedule(n.schedule);if(!o.valid)throw new Error(`Invalid schedule: ${o.error}`);a.schedule=n.schedule,a.occurrenceCount=0,a.nextRun=uh(n.schedule,new Date,0)}return await this.saveScheduler(a,e),this.schedulers.set(a.id,a),console.log(`[SCHEDULER] Updated scheduler "${a.name}" (${a.id})`),a}async deleteScheduler(e,t,n){if(!await this.getScheduler(e,t,n))return!1;let a=this.getSchedulerFilePath(e,t);return ai.unlinkSync(a),this.schedulers.delete(t),console.log(`[SCHEDULER] Deleted scheduler ${t} by user ${n}`),!0}async enableScheduler(e,t,n){return this.updateScheduler(e,t,{enabled:!0},n)}async disableScheduler(e,t,n){return this.updateScheduler(e,t,{enabled:!1},n)}async runSchedulerNow(e,t,n){let i=await this.getScheduler(e,t,n);if(!i)throw new Error(`Scheduler not found or access denied: ${t}`);let a=await this.executeSchedulerInternal(i,e,"manual");return i.lastRun=new Date().toISOString(),await this.saveScheduler(i,e),this.schedulers.set(i.id,i),this.logger.logTriggerExecution({triggerId:i.id,triggerName:i.name,webhookUuid:i.promptId,projectName:e,success:!0,threadId:a}),a}async saveScheduler(e,t){if(!t&&(t=this.findProjectForScheduler(e.id),!t))throw new Error(`Cannot find project for scheduler ${e.id}`);let n=this.getSchedulersDir(t);ai.mkdirSync(n,{recursive:!0});let i=this.getSchedulerFilePath(t,e.id),a=Y6.stringify(e);ai.writeFileSync(i,a,"utf-8")}};import*as Mc from"node:path";import*as Ofe from"node:os";import{existsSync as Dfe,lstatSync as Jit,mkdirSync as Ife,readdirSync as Kit}from"fs";var iE=class r{projectsConfigPath;static PROJECT_FILENAME="project.yaml";constructor(e){let t=Mc.join(Ofe.userInfo().homedir,".coday");this.projectsConfigPath=Mc.join(e??t,"projects"),Ife(this.projectsConfigPath,{recursive:!0})}listProjects(){return Kit(this.projectsConfigPath).filter(t=>Jit(Mc.join(this.projectsConfigPath,t)).isDirectory())}getProjectInfo(e){let t=Mc.join(this.projectsConfigPath,e),n=Mc.join(t,r.PROJECT_FILENAME);return Dfe(n)?{name:e,configPath:t}:null}exists(e){return this.getProjectInfo(e)!==null}getConfig(e){let t=this.getProjectInfo(e);if(!t)return console.log(`[PROJECT_REPO] No project info found for: '${e}'`),null;let n=Mc.join(t.configPath,r.PROJECT_FILENAME),i=ta(n);if(!i)return console.log(`[PROJECT_REPO] Failed to load config for '${e}' from ${n}`),null;let a=ch(i,hce);return a!==i&&(console.log(`[PROJECT_REPO] Config migrated for '${e}', saving...`),Mn(n,a)),a}saveConfig(e,t){let n=this.getProjectInfo(e);if(!n)throw new Error(`Project '${e}' does not exist`);let i=Mc.join(n.configPath,r.PROJECT_FILENAME);Mn(i,t)}createProject(e,t){let n=Mc.join(this.projectsConfigPath,e),i=Mc.join(n,r.PROJECT_FILENAME);return Dfe(i)?!1:(console.log(`[PROJECT_REPO] Creating project '${e}' with path: ${t}`),Ife(n,{recursive:!0}),Mn(i,{version:1,path:t,integration:{},storage:{type:"file"},agents:[]}),console.log(`[PROJECT_REPO] Project '${e}' created successfully`),!0)}deleteProject(e){return this.getProjectInfo(e),!1}};var bM=vr(Pc(),1);import{promises as Zu}from"fs";import Zp from"path";var $fe=async r=>{try{let e=await Zu.readFile(r,"utf-8");return bM.default.parse(e)}catch{return null}},aE=class{constructor(e){this.projectsDir=e}getThreadsDir(e){return Zp.join(this.projectsDir,e,"threads")}async ensureThreadsDir(e){let t=this.getThreadsDir(e);try{await Zu.mkdir(t,{recursive:!0})}catch(n){throw new yu(`Failed to initialize threads directory for project ${e}`,n)}}getThreadFileName(e){return`${e.id}.yml`}async findThreadFile(e,t){try{let n=this.getThreadsDir(e),i=`${t}.yml`;try{return await Zu.access(Zp.join(n,i)),i}catch{}return(await Zu.readdir(n)).find(s=>s.endsWith(`-${t}.yml`))||null}catch(n){throw new yu(`Error finding thread ${t} in project ${e}`,n)}}async getById(e,t){try{let n=await this.findThreadFile(e,t);if(!n)return null;let i=this.getThreadsDir(e),a=Zp.join(i,n),o=await $fe(a);if(!o)return null;let s=ch(o,ZR);return s.projectId||(s.projectId=e),s!==o&&Mn(a,s),new bu(s)}catch(n){throw new yu(`Failed to read thread ${t} from project ${e}`,n)}}async save(e,t){await this.ensureThreadsDir(e);try{if(t.id||(t.id=crypto.randomUUID()),t.projectId||(t.projectId=e),t.projectId!==e)throw new Error(`Thread projectId mismatch: expected ${e}, got ${t.projectId}`);let n=this.getThreadsDir(e),i=this.getThreadFileName(t),a=Zp.join(n,i),o=await this.findThreadFile(e,t.id);if(o&&o!==i){let u=Zp.join(n,o);try{await Zu.unlink(u),console.log(`[THREAD-REPO] Migrated/renamed thread file: ${o} \u2192 ${i}`)}catch(l){console.warn(`[THREAD-REPO] Could not delete old thread file: ${o}`,l)}}let s={...t.serialize(),version:ZR.length+1},c=bM.default.stringify(s);return await Zu.writeFile(a,c,"utf-8"),t}catch(n){throw new yu(`Failed to save thread ${t.id} to project ${e}`,n)}}async listByProject(e){try{let t=this.getThreadsDir(e);try{await Zu.access(t)}catch{return[]}let n=await Zu.readdir(t);return(await Promise.all(n.filter(a=>a.endsWith(".yml")).map(async a=>{let o=await $fe(Zp.join(t,a));if(!o)return null;let s=o.projectId||e;return{id:o.id,username:o.username,projectId:s,name:o.name??"...",summary:o.summary??"",createdDate:o.createdDate??"",modifiedDate:o.modifiedDate??"",price:o.price??0,starring:o.starring??[],users:Array.isArray(o.users)?o.users.map(c=>typeof c=="string"?{userId:c}:c):o.username?[{userId:o.username}]:[],parentThreadId:o.parentThreadId,parentEventId:o.parentEventId,delegatedAgentName:o.delegatedAgentName,delegatedTask:o.delegatedTask}}))).filter(a=>!!a).sort((a,o)=>a.modifiedDate>o.modifiedDate?-1:1)}catch(t){throw new yu(`Failed to list threads for project ${e}`,t)}}async delete(e,t){try{let n=await this.findThreadFile(e,t);if(!n)return!1;let i=this.getThreadsDir(e);return await Zu.unlink(Zp.join(i,n)),!0}catch(n){throw new yu(`Failed to delete thread ${t} from project ${e}`,n)}}};var oE=class{constructor(e,t,n){this.projectRepository=e;this.projectsDir=t;this.threadFileService=n}repositoryCache=new Map;threadListCache=new Map;CACHE_TTL_MS=14400*1e3;loadingPromises=new Map;getThreadRepository(e){let t=this.repositoryCache.get(e);if(t)return t;if(!this.projectRepository.getProjectInfo(e))throw new Error(`Project '${e}' not found`);let i=new aE(this.projectsDir);return this.repositoryCache.set(e,i),i}clearCache(e){e?this.repositoryCache.delete(e):this.repositoryCache.clear()}async listThreads(e,t){let n=this.threadListCache.get(e);if(n&&Date.now()-n.timestamp<this.CACHE_TTL_MS)return n.data.filter(o=>rn(o,t)).sort((o,s)=>o.modifiedDate>s.modifiedDate?-1:1);let i=this.loadingPromises.get(e);if(i)return await i,this.listThreads(e,t);let a=this.loadThreadListFromDisk(e);this.loadingPromises.set(e,a);try{return(await a).filter(s=>rn(s,t)).sort((s,c)=>s.modifiedDate>c.modifiedDate?-1:1)}catch(o){throw this.threadListCache.delete(e),o}finally{this.loadingPromises.delete(e)}}async loadThreadListFromDisk(e){let n=await this.getThreadRepository(e).listByProject(e);return this.threadListCache.set(e,{data:n,timestamp:Date.now()}),n}toThreadSummary(e){return{id:e.id,username:e.username,projectId:e.projectId,name:e.name,summary:e.summary,createdDate:e.createdDate,modifiedDate:e.modifiedDate,price:e.price,starring:e.starring,users:e.users,parentThreadId:e.parentThreadId,parentEventId:e.parentEventId,delegatedAgentName:e.delegatedAgentName,delegatedTask:e.delegatedTask}}updateThreadInCache(e,t){let n=this.threadListCache.get(e);if(!n)return;let i=n.data.findIndex(a=>a.id===t.id);i!==-1?n.data[i]=t:n.data.push(t)}removeThreadFromCache(e,t){let n=this.threadListCache.get(e);n&&(n.data=n.data.filter(i=>i.id!==t))}async getThread(e,t){return await this.getThreadRepository(e).getById(e,t)}async createThread(e,t,n){let i=this.getThreadRepository(e),a=new bu({id:crypto.randomUUID(),username:t,projectId:e,name:n||"",price:0}),o=await i.save(e,a);return this.updateThreadInCache(e,this.toThreadSummary(o)),o}async updateThread(e,t,n){let i=this.getThreadRepository(e),a=await i.getById(e,t);if(!a){let c=this.threadListCache.get(e)?.data.find(u=>u.id===t);if(c)a=new bu({id:c.id,username:c.username,projectId:c.projectId,name:c.name,price:c.price});else throw new Error(`Thread '${t}' not found in project '${e}'`)}n.name!==void 0&&(a.name=n.name),n.summary!==void 0&&(a.summary=n.summary),n.users!==void 0&&(a.users=n.users);let o=await i.save(e,a);return this.updateThreadInCache(e,this.toThreadSummary(o)),o}async starThread(e,t,n){let i=this.getThreadRepository(e),a=await i.getById(e,t);if(!a)throw new Error(`Thread '${t}' not found in project '${e}'`);a.starring.includes(n)||a.starring.push(n);let o=await i.save(e,a);return this.updateThreadInCache(e,this.toThreadSummary(o)),o}async unstarThread(e,t,n){let i=this.getThreadRepository(e),a=await i.getById(e,t);if(!a)throw new Error(`Thread '${t}' not found in project '${e}'`);a.starring=a.starring.filter(s=>s!==n);let o=await i.save(e,a);return this.updateThreadInCache(e,this.toThreadSummary(o)),o}async deleteThread(e,t){let i=await this.getThreadRepository(e).delete(e,t);return i&&(await this.threadFileService.deleteThreadFiles(e,t),this.removeThreadFromCache(e,t)),i}async saveThread(e,t){let i=await this.getThreadRepository(e).save(e,t);return this.updateThreadInCache(e,this.toThreadSummary(i)),i}async listAllThreads(e){let t=this.threadListCache.get(e);if(t&&Date.now()-t.timestamp<this.CACHE_TTL_MS)return t.data.sort((a,o)=>a.modifiedDate>o.modifiedDate?-1:1);let n=this.loadingPromises.get(e);if(n)return await n,this.listAllThreads(e);let i=this.loadThreadListFromDisk(e);this.loadingPromises.set(e,i);try{return(await i).sort((o,s)=>o.modifiedDate>s.modifiedDate?-1:1)}catch(a){throw this.threadListCache.delete(e),a}finally{this.loadingPromises.delete(e)}}async exists(e,t){return await this.getThread(e,t)!==null}};var Mfe=vr(Pc(),1);import*as ba from"fs/promises";import*as X6 from"path";var jfe=5,Rfe=24,Nfe=100,Ffe={SHORT_THREADS:7,LONG_THREADS:30},sE=class{constructor(e,t){this.projectsConfigPath=e;this.logger=t}cleanupTimer=null;initialTimer=null;isRunning=!1;async start(){if(this.isRunning){this.log("Thread cleanup service already running");return}this.isRunning=!0,this.log("Starting thread cleanup service with user message-based retention"),this.initialTimer=setTimeout(async()=>{await this.performCleanup(),this.cleanupTimer=setInterval(async()=>{await this.performCleanup()},Rfe*60*60*1e3),this.log(`Thread cleanup scheduled every ${Rfe} hours`)},jfe*60*1e3),this.log(`Initial cleanup scheduled in ${jfe} minutes`)}async stop(){this.initialTimer&&(clearTimeout(this.initialTimer),this.initialTimer=null),this.cleanupTimer&&(clearInterval(this.cleanupTimer),this.cleanupTimer=null),this.isRunning=!1,this.log("Thread cleanup service stopped (all timers cleared)")}async performCleanup(){let e=Date.now(),t=0,n=0,i=0;try{this.log("Starting thread cleanup...");let a=await ba.readdir(this.projectsConfigPath);this.log(`Found ${a.length} projects to scan`);for(let s of a)try{let c=X6.join(this.projectsConfigPath,s);if(!(await ba.lstat(c)).isDirectory())continue;let l=X6.join(c,"threads");try{await ba.access(l)}catch{continue}let{scanned:f,deleted:p,errors:d}=await this.cleanupProjectThreads(s,l);t+=f,n+=p,i+=d}catch(c){this.logError(`Error processing project ${s}: ${c}`),i++}let o=Date.now()-e;this.log(`Cleanup completed: ${n}/${t} threads deleted across all projects in ${o}ms (${i} errors)`)}catch(a){let o=Date.now()-e;this.logError(`Cleanup failed after ${o}ms: ${a}`)}}async cleanupProjectThreads(e,t){let n=0,i=0,a=0;try{let s=(await ba.readdir(t)).filter(c=>c.endsWith(".yml"));if(n=s.length,s.length===0)return{scanned:n,deleted:i,errors:a};this.log(`Scanning ${s.length} threads in project ${e}`);for(let c=0;c<s.length;c+=Nfe){let u=s.slice(c,c+Nfe),l=await this.processBatch(u,t,e);i+=l.deleted,a+=l.errors}i>0&&this.log(`Project ${e}: deleted ${i}/${n} expired threads`)}catch(o){this.logError(`Error scanning project ${e}: ${o}`),a++}return{scanned:n,deleted:i,errors:a}}async processBatch(e,t,n){let i=0,a=0;return await Promise.all(e.map(async o=>{let s=X6.join(t,o),c;try{let u=await ba.readFile(s,"utf-8");c=Mfe.parse(u)}catch(u){await ba.unlink(s),this.logError(`Error processing file ${o} in project ${n}: ${u}`)}if(!(!c||!c.modifiedDate)&&!(c.starring&&Array.isArray(c.starring)&&c.starring.length>0)&&this.shouldDeleteThread(c)){let u=this.countUserMessages(c.messages||[]),l=this.getDaysSinceModified(c.modifiedDate),f=c.id||o.replace(".yml","");await ba.unlink(s),await this.deleteThreadFiles(t,f),i++,this.logger.logThreadCleanup(n,o),console.log(`ThreadCleanup: Deleted thread ${f} and its files directory (${u} user messages, ${l} days old)`)}})),{deleted:i,errors:a}}shouldDeleteThread(e){if(!e||!e.modifiedDate)return!1;let n=this.countUserMessages(e.messages||[])<=3?Ffe.SHORT_THREADS:Ffe.LONG_THREADS;return this.getDaysSinceModified(e.modifiedDate)>n}countUserMessages(e){return e.filter(t=>t&&t.type==="message"&&t.role==="user").length}getDaysSinceModified(e){let t=new Date(e),i=new Date().getTime()-t.getTime();return Math.floor(i/(1e3*60*60*24))}async deleteThreadFiles(e,t){try{let n=X6.join(e,`${t}-files`);try{await ba.access(n),await ba.rm(n,{recursive:!0,force:!0}),console.log(`ThreadCleanup: Deleted files directory ${n}`)}catch(i){if(i.code!=="ENOENT")throw i}}catch(n){console.error(`ThreadCleanup: Error deleting files for ${t}:`,n)}}log(e){let t=new Date().toISOString();console.log(`[${t}] ThreadCleanup: ${e}`)}logError(e){let t=new Date().toISOString();console.error(`[${t}] ThreadCleanup ERROR: ${e}`)}async forceCleanup(){this.log("Manual cleanup triggered"),await this.performCleanup()}};import*as qi from"path";import*as Jp from"fs";import*as xa from"fs/promises";var cE=class{constructor(e){this.projectsDir=e}getThreadFilesDir(e,t){return qi.join(this.projectsDir,e,"threads",`${t}-files`)}async ensureThreadFilesDir(e,t){let n=this.getThreadFilesDir(e,t);Jp.existsSync(n)||await xa.mkdir(n,{recursive:!0})}async listFiles(e,t){await this.ensureThreadFilesDir(e,t);let n=this.getThreadFilesDir(e,t),i=await xa.readdir(n);return await Promise.all(i.map(async o=>{let s=qi.join(n,o),c=await xa.stat(s);return{filename:o,size:c.size,lastModified:c.mtime.toISOString()}}))}async saveFile(e,t,n,i){let a=this.getThreadFilesDir(e,t);await xa.mkdir(a,{recursive:!0});let o=qi.join(a,n);await xa.writeFile(o,i)}async getFile(e,t,n){await this.ensureThreadFilesDir(e,t);let i=this.getThreadFilesDir(e,t),a=qi.join(i,n),o=qi.resolve(a),s=qi.resolve(i);if(!o.startsWith(s))throw new Error("Access denied: invalid file path");if(!Jp.existsSync(a))throw new Error(`File '${n}' not found`);return await xa.readFile(a)}async getFilePath(e,t,n){await this.ensureThreadFilesDir(e,t);let i=this.getThreadFilesDir(e,t),a=qi.join(i,n),o=qi.resolve(a),s=qi.resolve(i);if(!o.startsWith(s))throw new Error("Access denied: invalid file path");if(!Jp.existsSync(a))throw new Error(`File '${n}' not found`);return a}fileExists(e,t,n){let i=this.getThreadFilesDir(e,t),a=qi.join(i,n),o=qi.resolve(a),s=qi.resolve(i);return o.startsWith(s)?Jp.existsSync(a):!1}async deleteFile(e,t,n){await this.ensureThreadFilesDir(e,t);let i=this.getThreadFilesDir(e,t),a=qi.join(i,n),o=qi.resolve(a),s=qi.resolve(i);if(!o.startsWith(s))throw new Error("Access denied: invalid file path");if(!Jp.existsSync(a))throw new Error(`File '${n}' not found`);await xa.unlink(a)}async deleteThreadFiles(e,t){try{let n=this.getThreadFilesDir(e,t);Jp.existsSync(n)&&(await xa.rm(n,{recursive:!0,force:!0}),console.log(`Deleted thread files directory: ${n}`))}catch(n){console.error(`Error deleting thread files for ${t}:`,n)}}};var Lfe=vr(Go(),1);var uE=class{constructor(e,t,n,i){this.threadRepository=t;this.projectId=n;this.interactor=i;this.username=e.username}activeThread$=new Lfe.BehaviorSubject(null);isKilled=!1;activeThread=this.activeThread$.asObservable();username;async kill(){this.isKilled=!0,this.activeThread$.complete()}async create(e){let t=new bu({id:"",username:this.username,name:e??"",price:0});return this.activeThread$.next(t),t}async select(e){let t=await this.threadRepository.getById(this.projectId,e);if(!t)throw new Error(`Thread ${e} not found`);return this.activeThread$.next(t),t}async save(e){let t=this.activeThread$.value;if(!t){console.error(`No thread existing when save attempt with name '${e}'`);return}e&&(t.name=e);let n=await this.threadRepository.save(this.projectId,t);this.activeThread$.next(n)}async autoSave(e){if(this.isKilled){console.log("Autosave skipped: service has been killed");return}let t=this.activeThread$.value;if(!t||t.messagesLength==0){console.log(`Autosave of an empty or falsy thread aborted, threadId: ${t?.id}, user: ${this.username}`);return}try{e&&(t.name=e),await this.threadRepository.save(this.projectId,t),this.interactor&&this.interactor.sendEvent(new uc({threadId:t.id,name:t.name||void 0}))}catch(n){console.log("Autosave failed (service may have been killed):",n instanceof Error?n.message:n)}}async truncateAtUserMessage(e){let t=this.activeThread$.value;if(!t)return console.error("No active thread available for truncation"),!1;let n=t.truncateAtUserMessage(e);return n||this.interactor?.warn("Failed to truncate thread."),n}getCurrentThread(){return this.activeThread$.value}};async function qfe(r,e){let t=e.project.selectedProject,n=e.user.getUserData(t.name),i=await QS(t.config.path,r,n),a=new k0({...i,root:t.config.path,name:t.name},e.user.username);return console.log(`[BUILD_CONTEXT] Context for '${a.project.name}' \u2192 root: ${a.project.root}, desc: ${a.project.description?.length||0} chars`),a}var lE=class extends Fe{constructor(t,n){super({commandWord:"select-project",description:"Select an existing project"});this.interactor=t;this.services=n}async handle(t,n){try{let i=this.getSubCommand(t);return await this.selectProject(i)??n}catch(i){return this.interactor.error(`Invalid project selection because: ${i.toString()}`),n}}async selectProject(t){return this.services.project.selectProject(t),this.services.project.selectedProject?qfe(this.interactor,this.services):null}};var fE=class extends Fe{constructor(t,n,i){super({commandWord:"add",description:"Add a new integration configuration. User level is default, use --project/-p for project level."});this.interactor=t;this.service=n;this.editHandler=i}async handle(t,n){let a=!!Tt(this.getSubCommand(t),[{key:"project",alias:"p"}]).project,o=a?"project":"user",s=this.service.getMergedIntegrations(),c=Object.keys(s),u=Xf.filter(h=>!c.includes(h));if(u.length===0)return this.interactor.displayText(`All available integrations (${Xf.join(", ")}) are already configured.`),n;let l=await this.interactor.chooseOption(u.sort(),"Select integration to add:",`Available integrations to configure:
|
|
878
878
|
|
|
879
879
|
${u.map(h=>`- **${h}**`).join(`
|
|
880
880
|
`)}
|