@jcheesepkg/nanobot 0.6.0 → 0.6.1

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.
@@ -56,8 +56,6 @@ declare class AgentLoop {
56
56
  private runAgentLoop;
57
57
  /** Generate a fallback interim message based on which tools are being called. */
58
58
  private getToolCallFallbackText;
59
- /** Check if the worker reports no credits for this user. Returns true if blocked. */
60
- private checkNoCredits;
61
59
  private updateToolContexts;
62
60
  /** Process a message directly (for CLI or cron usage). */
63
61
  processDirect(content: string, sessionKey?: string, channel?: string, chatId?: string): Promise<string>;
@@ -1 +1 @@
1
- {"version":3,"file":"loop.d.mts","names":[],"sources":["../../src/agent/loop.ts"],"mappings":";;;;;;;;;;;;AAkCA;;;;;;;cAAa,SAAA;EAAA,QACH,GAAA;EAAA,QACA,QAAA;EAAA,QACA,SAAA;EAAA,QACA,KAAA;EAAA,QACA,SAAA;EAAA,QACA,aAAA;EAAA,SAEC,OAAA,EAAS,cAAA;EAAA,SACT,QAAA,EAAU,cAAA;EAAA,SACV,KAAA,EAAO,YAAA;EAAA,SACP,SAAA,EAAW,eAAA;EAAA,QAEZ,QAAA;EARA;EAAA,QAWA,QAAA;cAEI,MAAA;IACV,GAAA,EAAK,UAAA;IACL,QAAA,EAAU,WAAA;IACV,SAAA;IACA,KAAA;IACA,SAAA;IACA,aAAA;IACA,WAAA;IACA,UAAA,GAAa,cAAA;IACb,mBAAA;IACA,YAAA;IACA,aAAA;IACA,WAAA,GAAc,IAAA;EAAA;EAAA,QAmCR,oBAAA;EA5CN;EAyGI,GAAA,CAAA,GAAO,OAAA;EAvGX;EAwIF,IAAA,CAAA;EAtIE;EAAA,QA4IY,cAAA;EAAA,QAuFA,oBAAA;EAAA,QA4CA,YAAA;EA5QZ;EAAA,QAwWM,uBAAA;EAtWN;EAAA,QAwXY,cAAA;EAAA,QAsBN,kBAAA;EA3WA;EA6XF,aAAA,CACJ,OAAA,UACA,UAAA,WACA,OAAA,WACA,MAAA,YACC,OAAA;AAAA"}
1
+ {"version":3,"file":"loop.d.mts","names":[],"sources":["../../src/agent/loop.ts"],"mappings":";;;;;;;;;;;;AAkCA;;;;;;;cAAa,SAAA;EAAA,QACH,GAAA;EAAA,QACA,QAAA;EAAA,QACA,SAAA;EAAA,QACA,KAAA;EAAA,QACA,SAAA;EAAA,QACA,aAAA;EAAA,SAEC,OAAA,EAAS,cAAA;EAAA,SACT,QAAA,EAAU,cAAA;EAAA,SACV,KAAA,EAAO,YAAA;EAAA,SACP,SAAA,EAAW,eAAA;EAAA,QAEZ,QAAA;EARA;EAAA,QAWA,QAAA;cAEI,MAAA;IACV,GAAA,EAAK,UAAA;IACL,QAAA,EAAU,WAAA;IACV,SAAA;IACA,KAAA;IACA,SAAA;IACA,aAAA;IACA,WAAA;IACA,UAAA,GAAa,cAAA;IACb,mBAAA;IACA,YAAA;IACA,aAAA;IACA,WAAA,GAAc,IAAA;EAAA;EAAA,QAmCR,oBAAA;EA5CN;EAyGI,GAAA,CAAA,GAAO,OAAA;EAvGX;EAwIF,IAAA,CAAA;EAtIE;EAAA,QA4IY,cAAA;EAAA,QAuFA,oBAAA;EAAA,QA4CA,YAAA;EA5QZ;EAAA,QAwWM,uBAAA;EAAA,QAiBA,kBAAA;EAvXQ;EAyYV,aAAA,CACJ,OAAA,UACA,UAAA,WACA,OAAA,WACA,MAAA,YACC,OAAA;AAAA"}
@@ -266,22 +266,6 @@ var AgentLoop = class {
266
266
  for (const name of toolNames) if (fallbacks[name]) return fallbacks[name];
267
267
  return "ちょっと待ってね...";
268
268
  }
269
- /** Check if the worker reports no credits for this user. Returns true if blocked. */
270
- async checkNoCredits() {
271
- const workerUrl = process.env.WORKER_URL;
272
- const userId = process.env.NANOBOT_USER_ID;
273
- if (!workerUrl || !userId) return false;
274
- try {
275
- const res = await fetch(`${workerUrl}/api/users/${userId}/has-credits`, { signal: AbortSignal.timeout(5e3) });
276
- if (res.ok) {
277
- if (!(await res.json()).hasCredits) {
278
- console.log("Credit gate: no credits, skipping");
279
- return true;
280
- }
281
- }
282
- } catch {}
283
- return false;
284
- }
285
269
  updateToolContexts(channel, chatId) {
286
270
  const messageTool = this.tools.get("message");
287
271
  if (messageTool instanceof MessageTool) messageTool.setContext(channel, chatId);
@@ -292,7 +276,6 @@ var AgentLoop = class {
292
276
  }
293
277
  /** Process a message directly (for CLI or cron usage). */
294
278
  async processDirect(content, sessionKey = "cli:direct", channel = "cli", chatId = "direct") {
295
- if (await this.checkNoCredits()) return "";
296
279
  const session = this.sessions.getOrCreate(sessionKey);
297
280
  this.updateToolContexts(channel, chatId);
298
281
  const messages = this.context.buildMessages({
@@ -1 +1 @@
1
- {"version":3,"file":"loop.mjs","names":[],"sources":["../../src/agent/loop.ts"],"sourcesContent":["import type { LLMProvider, ChatMessage, ToolCallRequest } from \"../providers/base.js\";\nimport type { MessageBus } from \"../bus/queue.js\";\nimport type {\n InboundMessage,\n OutboundMessage,\n} from \"../bus/events.js\";\nimport { createOutboundMessage } from \"../bus/events.js\";\nimport { ContextBuilder } from \"./context.js\";\nimport { ToolRegistry } from \"./tools/registry.js\";\nimport {\n ReadFileTool,\n WriteFileTool,\n EditFileTool,\n ListDirTool,\n} from \"./tools/filesystem.js\";\nimport { ExecTool } from \"./tools/shell.js\";\nimport { WebSearchTool, WebFetchTool } from \"./tools/web.js\";\nimport { MessageTool } from \"./tools/message.js\";\nimport { SpawnTool } from \"./tools/spawn.js\";\nimport { CronTool } from \"./tools/cron.js\";\nimport { SubagentManager } from \"./subagent.js\";\nimport { SessionManager } from \"../session/manager.js\";\nimport type { ExecToolConfig } from \"../config/schema.js\";\nimport type { Tool } from \"./tools/base.js\";\n\n/**\n * The agent loop: core processing engine.\n *\n * 1. Receives messages from the bus\n * 2. Builds context with history, memory, skills\n * 3. Calls the LLM\n * 4. Executes tool calls\n * 5. Sends responses back\n */\nexport class AgentLoop {\n private bus: MessageBus;\n private provider: LLMProvider;\n private workspace: string;\n private model: string;\n private maxTokens: number;\n private maxIterations: number;\n\n readonly context: ContextBuilder;\n readonly sessions: SessionManager;\n readonly tools: ToolRegistry;\n readonly subagents: SubagentManager;\n\n private _running = false;\n\n /** In-flight AbortControllers keyed by session key. */\n private inflight = new Map<string, AbortController>();\n\n constructor(params: {\n bus: MessageBus;\n provider: LLMProvider;\n workspace: string;\n model?: string;\n maxTokens?: number;\n maxIterations?: number;\n braveApiKey?: string;\n execConfig?: ExecToolConfig;\n restrictToWorkspace?: boolean;\n toolsEnabled?: string[];\n toolsDisabled?: string[];\n customTools?: Tool[];\n }) {\n this.bus = params.bus;\n this.provider = params.provider;\n this.workspace = params.workspace;\n this.model = params.model ?? params.provider.getDefaultModel();\n this.maxTokens = params.maxTokens ?? 8192;\n this.maxIterations = params.maxIterations ?? 20;\n\n const execConfig = params.execConfig ?? { timeout: 60 };\n const restrictToWorkspace = params.restrictToWorkspace ?? false;\n\n this.context = new ContextBuilder(params.workspace);\n this.sessions = new SessionManager(params.workspace);\n this.tools = new ToolRegistry();\n this.subagents = new SubagentManager({\n provider: params.provider,\n workspace: params.workspace,\n bus: params.bus,\n model: this.model,\n braveApiKey: params.braveApiKey,\n execConfig,\n restrictToWorkspace,\n });\n\n this.registerDefaultTools(\n execConfig,\n restrictToWorkspace,\n params.braveApiKey,\n params.toolsEnabled,\n params.toolsDisabled,\n params.customTools,\n );\n }\n\n private registerDefaultTools(\n execConfig: ExecToolConfig,\n restrictToWorkspace: boolean,\n braveApiKey?: string,\n toolsEnabled?: string[],\n toolsDisabled?: string[],\n customTools?: Tool[],\n ): void {\n const enabled = new Set(toolsEnabled ?? []);\n const disabled = new Set(toolsDisabled ?? []);\n const hasAllowlist = enabled.size > 0;\n const shouldRegister = (name: string): boolean =>\n (hasAllowlist ? enabled.has(name) : true) && !disabled.has(name);\n\n const registerIfEnabled = (tool: Tool): void => {\n if (shouldRegister(tool.name)) {\n this.tools.register(tool);\n }\n };\n\n // File tools — pass allowedDir when restrictToWorkspace is enabled\n const allowedDir = restrictToWorkspace ? this.workspace : undefined;\n registerIfEnabled(new ReadFileTool({ allowedDir }));\n registerIfEnabled(new WriteFileTool({ allowedDir }));\n registerIfEnabled(new EditFileTool({ allowedDir }));\n registerIfEnabled(new ListDirTool({ allowedDir }));\n\n // Shell tool\n registerIfEnabled(\n new ExecTool({\n workingDir: this.workspace,\n timeout: execConfig.timeout,\n restrictToWorkspace,\n }),\n );\n\n // Web tools\n registerIfEnabled(new WebSearchTool({ apiKey: braveApiKey }));\n registerIfEnabled(new WebFetchTool());\n\n // Message tool\n const messageTool = new MessageTool({\n sendCallback: (msg) => this.bus.publishOutbound(msg),\n });\n registerIfEnabled(messageTool);\n\n // Spawn tool\n const spawnTool = new SpawnTool(this.subagents);\n registerIfEnabled(spawnTool);\n\n // Cron tool — always registered, uses DO scheduling via worker API\n registerIfEnabled(new CronTool());\n\n if (customTools && customTools.length > 0) {\n for (const tool of customTools) {\n registerIfEnabled(tool);\n }\n }\n }\n\n /** Run the agent loop, processing messages from the bus. */\n async run(): Promise<void> {\n this._running = true;\n console.log(\"Agent loop started\");\n\n while (this._running) {\n try {\n const msg = await this.bus.consumeInboundTimeout(1000);\n\n // Process concurrently so new messages can abort in-flight ones\n this.processMessage(msg)\n .then(async (response) => {\n if (response) {\n await this.bus.publishOutbound(response);\n }\n })\n .catch(async (err) => {\n if (isAbortError(err)) return; // Already handled\n console.error(\"Error processing message:\", err);\n await this.bus.publishOutbound(\n createOutboundMessage({\n channel: msg.channel,\n chatId: msg.chatId,\n content: `Sorry, I encountered an error: ${err instanceof Error ? err.message : err}`,\n }),\n );\n });\n } catch {\n // timeout, continue\n }\n }\n }\n\n /** Stop the agent loop. */\n stop(): void {\n this._running = false;\n console.log(\"Agent loop stopping\");\n }\n\n /** Process a single inbound message. */\n private async processMessage(\n msg: InboundMessage,\n ): Promise<OutboundMessage | null> {\n // Handle system messages (subagent announces)\n if (msg.channel === \"system\") {\n return this.processSystemMessage(msg);\n }\n\n console.log(`Processing message from ${msg.channel}:${msg.senderId}`);\n\n const sessionKey = `${msg.channel}:${msg.chatId}`;\n\n // Abort any in-flight request for this session\n const existing = this.inflight.get(sessionKey);\n if (existing) {\n console.log(`Aborting in-flight request for ${sessionKey}`);\n existing.abort();\n }\n\n // Create a new AbortController for this request\n const controller = new AbortController();\n this.inflight.set(sessionKey, controller);\n\n const session = this.sessions.getOrCreate(sessionKey);\n\n // Update tool contexts\n this.updateToolContexts(msg.channel, msg.chatId);\n\n // Build initial messages\n const messages = this.context.buildMessages({\n history: session.getHistory(),\n currentMessage: msg.content,\n media: msg.media.length > 0 ? msg.media : undefined,\n channel: msg.channel,\n chatId: msg.chatId,\n });\n\n // The messages array is: [system, ...history, currentUser]\n // We want to save from the current user message onward (skip system + old history).\n const savedHistoryLen = session.getHistory().length;\n const newMsgStart = 1 + savedHistoryLen; // 1 for system prompt\n\n try {\n // Agent loop (mutates messages by appending assistant/tool messages)\n const finalContent = await this.runAgentLoop(messages, controller.signal, (text) => {\n if (text && text.trim().length > 0) {\n this.bus.publishOutbound(\n createOutboundMessage({\n channel: msg.channel,\n chatId: msg.chatId,\n content: text,\n }),\n );\n }\n });\n\n // Save the new messages from this turn (user + all agent loop messages)\n session.addTurnMessages(messages.slice(newMsgStart));\n this.sessions.save(session);\n\n return createOutboundMessage({\n channel: msg.channel,\n chatId: msg.chatId,\n content: finalContent,\n });\n } catch (err) {\n if (isAbortError(err)) {\n // Request was aborted because a new message arrived.\n // Save the user message to history so the next request has context,\n // but don't save any assistant response.\n const userMessages = messages.slice(newMsgStart).filter((m) => m.role === \"user\");\n if (userMessages.length > 0) {\n session.addTurnMessages(userMessages);\n this.sessions.save(session);\n }\n console.log(`Request aborted for ${sessionKey}, user message saved to history`);\n return null; // No response -- the new message will handle it\n }\n throw err; // Re-throw non-abort errors\n } finally {\n // Clean up if this is still our controller\n if (this.inflight.get(sessionKey) === controller) {\n this.inflight.delete(sessionKey);\n }\n }\n }\n\n private async processSystemMessage(\n msg: InboundMessage,\n ): Promise<OutboundMessage | null> {\n console.log(`Processing system message from ${msg.senderId}`);\n\n let originChannel: string;\n let originChatId: string;\n\n if (msg.chatId.includes(\":\")) {\n const [ch, id] = msg.chatId.split(\":\", 2);\n originChannel = ch;\n originChatId = id;\n } else {\n originChannel = \"cli\";\n originChatId = msg.chatId;\n }\n\n const sessionKey = `${originChannel}:${originChatId}`;\n const session = this.sessions.getOrCreate(sessionKey);\n\n this.updateToolContexts(originChannel, originChatId);\n\n const messages = this.context.buildMessages({\n history: session.getHistory(),\n currentMessage: msg.content,\n channel: originChannel,\n chatId: originChatId,\n });\n\n const savedHistoryLen = session.getHistory().length;\n const newMsgStart = 1 + savedHistoryLen;\n\n const finalContent = await this.runAgentLoop(messages);\n\n session.addTurnMessages(messages.slice(newMsgStart));\n this.sessions.save(session);\n\n return createOutboundMessage({\n channel: originChannel,\n chatId: originChatId,\n content: finalContent,\n });\n }\n\n private async runAgentLoop(\n messages: ChatMessage[],\n signal?: AbortSignal,\n onToolCallText?: (text: string) => void,\n ): Promise<string> {\n let finalContent: string | null = null;\n let sentToolCallNotice = false;\n\n for (let i = 0; i < this.maxIterations; i++) {\n const response = await this.provider.chat({\n messages,\n tools: this.tools.getDefinitions(),\n model: this.model,\n maxTokens: this.maxTokens,\n signal,\n });\n\n if (response.hasToolCalls) {\n // Send an interim message so the user knows we're working\n if (!sentToolCallNotice && onToolCallText) {\n const interimText = response.content?.trim()\n || this.getToolCallFallbackText(response.toolCalls);\n if (interimText) {\n onToolCallText(interimText);\n }\n sentToolCallNotice = true;\n }\n const toolCallDicts = response.toolCalls.map((tc) => ({\n id: tc.id,\n type: \"function\" as const,\n function: {\n name: tc.name,\n arguments: JSON.stringify(tc.arguments),\n },\n }));\n\n this.context.addAssistantMessage(\n messages,\n response.content,\n toolCallDicts,\n );\n\n for (const tc of response.toolCalls) {\n console.log(`Tool: ${tc.name}(${JSON.stringify(tc.arguments)})`);\n\n // Detect skill reads\n if (tc.name === \"read_file\") {\n const path = String(tc.arguments?.path ?? \"\");\n const skillMatch = path.match(/skills\\/([^/]+)\\/SKILL\\.md$/);\n if (skillMatch) {\n console.log(`Skill activated: ${skillMatch[1]}`);\n }\n }\n\n const result = await this.tools.execute(tc.name, tc.arguments);\n this.context.addToolResult(messages, tc.id, tc.name, result);\n }\n } else {\n finalContent = response.content;\n\n // If the LLM returned empty content after tool use, nudge it to respond\n if ((!finalContent || finalContent.trim().length === 0) && i > 0) {\n messages.push({\n role: \"assistant\",\n content: \"\",\n });\n messages.push({\n role: \"user\",\n content: \"(You used tools but didn't respond to the user. Please provide a brief response summarizing what you did.)\",\n });\n continue;\n }\n\n // Push the final assistant message so it gets persisted with the turn\n messages.push({ role: \"assistant\", content: finalContent ?? \"\" });\n break;\n }\n }\n\n if (!finalContent || finalContent.trim().length === 0) {\n finalContent = \"I've completed processing but have no response to give.\";\n }\n\n // If we exhausted iterations without a non-tool-call response, still persist the final text\n if (messages[messages.length - 1]?.role !== \"assistant\" || messages[messages.length - 1]?.content !== finalContent) {\n messages.push({ role: \"assistant\", content: finalContent });\n }\n\n return finalContent;\n }\n\n /** Generate a fallback interim message based on which tools are being called. */\n private getToolCallFallbackText(toolCalls: ToolCallRequest[]): string {\n const toolNames = toolCalls.map((tc) => tc.name);\n const fallbacks: Record<string, string> = {\n web_search: \"検索中...\",\n web_fetch: \"ページを読み込み中...\",\n read_file: \"ファイルを確認中...\",\n write_file: \"ファイルを書き込み中...\",\n edit_file: \"ファイルを編集中...\",\n exec: \"コマンドを実行中...\",\n spawn: \"サブエージェントを起動中...\",\n };\n for (const name of toolNames) {\n if (fallbacks[name]) return fallbacks[name];\n }\n return \"ちょっと待ってね...\";\n }\n\n /** Check if the worker reports no credits for this user. Returns true if blocked. */\n private async checkNoCredits(): Promise<boolean> {\n const workerUrl = process.env.WORKER_URL;\n const userId = process.env.NANOBOT_USER_ID;\n if (!workerUrl || !userId) return false; // no gate configured\n\n try {\n const res = await fetch(`${workerUrl}/api/users/${userId}/has-credits`, {\n signal: AbortSignal.timeout(5000),\n });\n if (res.ok) {\n const data = await res.json() as { hasCredits: boolean };\n if (!data.hasCredits) {\n console.log(\"Credit gate: no credits, skipping\");\n return true;\n }\n }\n } catch {\n // Fail open — if worker is unreachable, allow the request\n }\n return false;\n }\n\n private updateToolContexts(channel: string, chatId: string): void {\n const messageTool = this.tools.get(\"message\");\n if (messageTool instanceof MessageTool) {\n messageTool.setContext(channel, chatId);\n }\n\n const spawnTool = this.tools.get(\"spawn\");\n if (spawnTool instanceof SpawnTool) {\n spawnTool.setContext(channel, chatId);\n }\n\n const cronTool = this.tools.get(\"cron\");\n if (cronTool instanceof CronTool) {\n cronTool.setContext(channel, chatId);\n }\n }\n\n /** Process a message directly (for CLI or cron usage). */\n async processDirect(\n content: string,\n sessionKey = \"cli:direct\",\n channel = \"cli\",\n chatId = \"direct\",\n ): Promise<string> {\n // Credit gate: skip if worker reports no credits\n if (await this.checkNoCredits()) {\n return \"\";\n }\n\n // Use inline version of processMessage for direct calls\n const session = this.sessions.getOrCreate(sessionKey);\n this.updateToolContexts(channel, chatId);\n\n const messages = this.context.buildMessages({\n history: session.getHistory(),\n currentMessage: content,\n channel,\n chatId,\n });\n\n const savedHistoryLen = session.getHistory().length;\n const newMsgStart = 1 + savedHistoryLen;\n\n const finalContent = await this.runAgentLoop(messages);\n\n session.addTurnMessages(messages.slice(newMsgStart));\n this.sessions.save(session);\n\n return finalContent;\n }\n}\n\n/** Check if an error is an abort/cancellation error. */\nfunction isAbortError(err: unknown): boolean {\n if (err instanceof DOMException && err.name === \"AbortError\") return true;\n if (err instanceof Error) {\n if (err.name === \"AbortError\") return true;\n // OpenAI SDK wraps abort as APIUserAbortError\n if (err.name === \"APIUserAbortError\") return true;\n if (err.message.includes(\"abort\")) return true;\n }\n return false;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"loop.mjs","names":[],"sources":["../../src/agent/loop.ts"],"sourcesContent":["import type { LLMProvider, ChatMessage, ToolCallRequest } from \"../providers/base.js\";\nimport type { MessageBus } from \"../bus/queue.js\";\nimport type {\n InboundMessage,\n OutboundMessage,\n} from \"../bus/events.js\";\nimport { createOutboundMessage } from \"../bus/events.js\";\nimport { ContextBuilder } from \"./context.js\";\nimport { ToolRegistry } from \"./tools/registry.js\";\nimport {\n ReadFileTool,\n WriteFileTool,\n EditFileTool,\n ListDirTool,\n} from \"./tools/filesystem.js\";\nimport { ExecTool } from \"./tools/shell.js\";\nimport { WebSearchTool, WebFetchTool } from \"./tools/web.js\";\nimport { MessageTool } from \"./tools/message.js\";\nimport { SpawnTool } from \"./tools/spawn.js\";\nimport { CronTool } from \"./tools/cron.js\";\nimport { SubagentManager } from \"./subagent.js\";\nimport { SessionManager } from \"../session/manager.js\";\nimport type { ExecToolConfig } from \"../config/schema.js\";\nimport type { Tool } from \"./tools/base.js\";\n\n/**\n * The agent loop: core processing engine.\n *\n * 1. Receives messages from the bus\n * 2. Builds context with history, memory, skills\n * 3. Calls the LLM\n * 4. Executes tool calls\n * 5. Sends responses back\n */\nexport class AgentLoop {\n private bus: MessageBus;\n private provider: LLMProvider;\n private workspace: string;\n private model: string;\n private maxTokens: number;\n private maxIterations: number;\n\n readonly context: ContextBuilder;\n readonly sessions: SessionManager;\n readonly tools: ToolRegistry;\n readonly subagents: SubagentManager;\n\n private _running = false;\n\n /** In-flight AbortControllers keyed by session key. */\n private inflight = new Map<string, AbortController>();\n\n constructor(params: {\n bus: MessageBus;\n provider: LLMProvider;\n workspace: string;\n model?: string;\n maxTokens?: number;\n maxIterations?: number;\n braveApiKey?: string;\n execConfig?: ExecToolConfig;\n restrictToWorkspace?: boolean;\n toolsEnabled?: string[];\n toolsDisabled?: string[];\n customTools?: Tool[];\n }) {\n this.bus = params.bus;\n this.provider = params.provider;\n this.workspace = params.workspace;\n this.model = params.model ?? params.provider.getDefaultModel();\n this.maxTokens = params.maxTokens ?? 8192;\n this.maxIterations = params.maxIterations ?? 20;\n\n const execConfig = params.execConfig ?? { timeout: 60 };\n const restrictToWorkspace = params.restrictToWorkspace ?? false;\n\n this.context = new ContextBuilder(params.workspace);\n this.sessions = new SessionManager(params.workspace);\n this.tools = new ToolRegistry();\n this.subagents = new SubagentManager({\n provider: params.provider,\n workspace: params.workspace,\n bus: params.bus,\n model: this.model,\n braveApiKey: params.braveApiKey,\n execConfig,\n restrictToWorkspace,\n });\n\n this.registerDefaultTools(\n execConfig,\n restrictToWorkspace,\n params.braveApiKey,\n params.toolsEnabled,\n params.toolsDisabled,\n params.customTools,\n );\n }\n\n private registerDefaultTools(\n execConfig: ExecToolConfig,\n restrictToWorkspace: boolean,\n braveApiKey?: string,\n toolsEnabled?: string[],\n toolsDisabled?: string[],\n customTools?: Tool[],\n ): void {\n const enabled = new Set(toolsEnabled ?? []);\n const disabled = new Set(toolsDisabled ?? []);\n const hasAllowlist = enabled.size > 0;\n const shouldRegister = (name: string): boolean =>\n (hasAllowlist ? enabled.has(name) : true) && !disabled.has(name);\n\n const registerIfEnabled = (tool: Tool): void => {\n if (shouldRegister(tool.name)) {\n this.tools.register(tool);\n }\n };\n\n // File tools — pass allowedDir when restrictToWorkspace is enabled\n const allowedDir = restrictToWorkspace ? this.workspace : undefined;\n registerIfEnabled(new ReadFileTool({ allowedDir }));\n registerIfEnabled(new WriteFileTool({ allowedDir }));\n registerIfEnabled(new EditFileTool({ allowedDir }));\n registerIfEnabled(new ListDirTool({ allowedDir }));\n\n // Shell tool\n registerIfEnabled(\n new ExecTool({\n workingDir: this.workspace,\n timeout: execConfig.timeout,\n restrictToWorkspace,\n }),\n );\n\n // Web tools\n registerIfEnabled(new WebSearchTool({ apiKey: braveApiKey }));\n registerIfEnabled(new WebFetchTool());\n\n // Message tool\n const messageTool = new MessageTool({\n sendCallback: (msg) => this.bus.publishOutbound(msg),\n });\n registerIfEnabled(messageTool);\n\n // Spawn tool\n const spawnTool = new SpawnTool(this.subagents);\n registerIfEnabled(spawnTool);\n\n // Cron tool — always registered, uses DO scheduling via worker API\n registerIfEnabled(new CronTool());\n\n if (customTools && customTools.length > 0) {\n for (const tool of customTools) {\n registerIfEnabled(tool);\n }\n }\n }\n\n /** Run the agent loop, processing messages from the bus. */\n async run(): Promise<void> {\n this._running = true;\n console.log(\"Agent loop started\");\n\n while (this._running) {\n try {\n const msg = await this.bus.consumeInboundTimeout(1000);\n\n // Process concurrently so new messages can abort in-flight ones\n this.processMessage(msg)\n .then(async (response) => {\n if (response) {\n await this.bus.publishOutbound(response);\n }\n })\n .catch(async (err) => {\n if (isAbortError(err)) return; // Already handled\n console.error(\"Error processing message:\", err);\n await this.bus.publishOutbound(\n createOutboundMessage({\n channel: msg.channel,\n chatId: msg.chatId,\n content: `Sorry, I encountered an error: ${err instanceof Error ? err.message : err}`,\n }),\n );\n });\n } catch {\n // timeout, continue\n }\n }\n }\n\n /** Stop the agent loop. */\n stop(): void {\n this._running = false;\n console.log(\"Agent loop stopping\");\n }\n\n /** Process a single inbound message. */\n private async processMessage(\n msg: InboundMessage,\n ): Promise<OutboundMessage | null> {\n // Handle system messages (subagent announces)\n if (msg.channel === \"system\") {\n return this.processSystemMessage(msg);\n }\n\n console.log(`Processing message from ${msg.channel}:${msg.senderId}`);\n\n const sessionKey = `${msg.channel}:${msg.chatId}`;\n\n // Abort any in-flight request for this session\n const existing = this.inflight.get(sessionKey);\n if (existing) {\n console.log(`Aborting in-flight request for ${sessionKey}`);\n existing.abort();\n }\n\n // Create a new AbortController for this request\n const controller = new AbortController();\n this.inflight.set(sessionKey, controller);\n\n const session = this.sessions.getOrCreate(sessionKey);\n\n // Update tool contexts\n this.updateToolContexts(msg.channel, msg.chatId);\n\n // Build initial messages\n const messages = this.context.buildMessages({\n history: session.getHistory(),\n currentMessage: msg.content,\n media: msg.media.length > 0 ? msg.media : undefined,\n channel: msg.channel,\n chatId: msg.chatId,\n });\n\n // The messages array is: [system, ...history, currentUser]\n // We want to save from the current user message onward (skip system + old history).\n const savedHistoryLen = session.getHistory().length;\n const newMsgStart = 1 + savedHistoryLen; // 1 for system prompt\n\n try {\n // Agent loop (mutates messages by appending assistant/tool messages)\n const finalContent = await this.runAgentLoop(messages, controller.signal, (text) => {\n if (text && text.trim().length > 0) {\n this.bus.publishOutbound(\n createOutboundMessage({\n channel: msg.channel,\n chatId: msg.chatId,\n content: text,\n }),\n );\n }\n });\n\n // Save the new messages from this turn (user + all agent loop messages)\n session.addTurnMessages(messages.slice(newMsgStart));\n this.sessions.save(session);\n\n return createOutboundMessage({\n channel: msg.channel,\n chatId: msg.chatId,\n content: finalContent,\n });\n } catch (err) {\n if (isAbortError(err)) {\n // Request was aborted because a new message arrived.\n // Save the user message to history so the next request has context,\n // but don't save any assistant response.\n const userMessages = messages.slice(newMsgStart).filter((m) => m.role === \"user\");\n if (userMessages.length > 0) {\n session.addTurnMessages(userMessages);\n this.sessions.save(session);\n }\n console.log(`Request aborted for ${sessionKey}, user message saved to history`);\n return null; // No response -- the new message will handle it\n }\n throw err; // Re-throw non-abort errors\n } finally {\n // Clean up if this is still our controller\n if (this.inflight.get(sessionKey) === controller) {\n this.inflight.delete(sessionKey);\n }\n }\n }\n\n private async processSystemMessage(\n msg: InboundMessage,\n ): Promise<OutboundMessage | null> {\n console.log(`Processing system message from ${msg.senderId}`);\n\n let originChannel: string;\n let originChatId: string;\n\n if (msg.chatId.includes(\":\")) {\n const [ch, id] = msg.chatId.split(\":\", 2);\n originChannel = ch;\n originChatId = id;\n } else {\n originChannel = \"cli\";\n originChatId = msg.chatId;\n }\n\n const sessionKey = `${originChannel}:${originChatId}`;\n const session = this.sessions.getOrCreate(sessionKey);\n\n this.updateToolContexts(originChannel, originChatId);\n\n const messages = this.context.buildMessages({\n history: session.getHistory(),\n currentMessage: msg.content,\n channel: originChannel,\n chatId: originChatId,\n });\n\n const savedHistoryLen = session.getHistory().length;\n const newMsgStart = 1 + savedHistoryLen;\n\n const finalContent = await this.runAgentLoop(messages);\n\n session.addTurnMessages(messages.slice(newMsgStart));\n this.sessions.save(session);\n\n return createOutboundMessage({\n channel: originChannel,\n chatId: originChatId,\n content: finalContent,\n });\n }\n\n private async runAgentLoop(\n messages: ChatMessage[],\n signal?: AbortSignal,\n onToolCallText?: (text: string) => void,\n ): Promise<string> {\n let finalContent: string | null = null;\n let sentToolCallNotice = false;\n\n for (let i = 0; i < this.maxIterations; i++) {\n const response = await this.provider.chat({\n messages,\n tools: this.tools.getDefinitions(),\n model: this.model,\n maxTokens: this.maxTokens,\n signal,\n });\n\n if (response.hasToolCalls) {\n // Send an interim message so the user knows we're working\n if (!sentToolCallNotice && onToolCallText) {\n const interimText = response.content?.trim()\n || this.getToolCallFallbackText(response.toolCalls);\n if (interimText) {\n onToolCallText(interimText);\n }\n sentToolCallNotice = true;\n }\n const toolCallDicts = response.toolCalls.map((tc) => ({\n id: tc.id,\n type: \"function\" as const,\n function: {\n name: tc.name,\n arguments: JSON.stringify(tc.arguments),\n },\n }));\n\n this.context.addAssistantMessage(\n messages,\n response.content,\n toolCallDicts,\n );\n\n for (const tc of response.toolCalls) {\n console.log(`Tool: ${tc.name}(${JSON.stringify(tc.arguments)})`);\n\n // Detect skill reads\n if (tc.name === \"read_file\") {\n const path = String(tc.arguments?.path ?? \"\");\n const skillMatch = path.match(/skills\\/([^/]+)\\/SKILL\\.md$/);\n if (skillMatch) {\n console.log(`Skill activated: ${skillMatch[1]}`);\n }\n }\n\n const result = await this.tools.execute(tc.name, tc.arguments);\n this.context.addToolResult(messages, tc.id, tc.name, result);\n }\n } else {\n finalContent = response.content;\n\n // If the LLM returned empty content after tool use, nudge it to respond\n if ((!finalContent || finalContent.trim().length === 0) && i > 0) {\n messages.push({\n role: \"assistant\",\n content: \"\",\n });\n messages.push({\n role: \"user\",\n content: \"(You used tools but didn't respond to the user. Please provide a brief response summarizing what you did.)\",\n });\n continue;\n }\n\n // Push the final assistant message so it gets persisted with the turn\n messages.push({ role: \"assistant\", content: finalContent ?? \"\" });\n break;\n }\n }\n\n if (!finalContent || finalContent.trim().length === 0) {\n finalContent = \"I've completed processing but have no response to give.\";\n }\n\n // If we exhausted iterations without a non-tool-call response, still persist the final text\n if (messages[messages.length - 1]?.role !== \"assistant\" || messages[messages.length - 1]?.content !== finalContent) {\n messages.push({ role: \"assistant\", content: finalContent });\n }\n\n return finalContent;\n }\n\n /** Generate a fallback interim message based on which tools are being called. */\n private getToolCallFallbackText(toolCalls: ToolCallRequest[]): string {\n const toolNames = toolCalls.map((tc) => tc.name);\n const fallbacks: Record<string, string> = {\n web_search: \"検索中...\",\n web_fetch: \"ページを読み込み中...\",\n read_file: \"ファイルを確認中...\",\n write_file: \"ファイルを書き込み中...\",\n edit_file: \"ファイルを編集中...\",\n exec: \"コマンドを実行中...\",\n spawn: \"サブエージェントを起動中...\",\n };\n for (const name of toolNames) {\n if (fallbacks[name]) return fallbacks[name];\n }\n return \"ちょっと待ってね...\";\n }\n\n private updateToolContexts(channel: string, chatId: string): void {\n const messageTool = this.tools.get(\"message\");\n if (messageTool instanceof MessageTool) {\n messageTool.setContext(channel, chatId);\n }\n\n const spawnTool = this.tools.get(\"spawn\");\n if (spawnTool instanceof SpawnTool) {\n spawnTool.setContext(channel, chatId);\n }\n\n const cronTool = this.tools.get(\"cron\");\n if (cronTool instanceof CronTool) {\n cronTool.setContext(channel, chatId);\n }\n }\n\n /** Process a message directly (for CLI or cron usage). */\n async processDirect(\n content: string,\n sessionKey = \"cli:direct\",\n channel = \"cli\",\n chatId = \"direct\",\n ): Promise<string> {\n // Use inline version of processMessage for direct calls\n const session = this.sessions.getOrCreate(sessionKey);\n this.updateToolContexts(channel, chatId);\n\n const messages = this.context.buildMessages({\n history: session.getHistory(),\n currentMessage: content,\n channel,\n chatId,\n });\n\n const savedHistoryLen = session.getHistory().length;\n const newMsgStart = 1 + savedHistoryLen;\n\n const finalContent = await this.runAgentLoop(messages);\n\n session.addTurnMessages(messages.slice(newMsgStart));\n this.sessions.save(session);\n\n return finalContent;\n }\n}\n\n/** Check if an error is an abort/cancellation error. */\nfunction isAbortError(err: unknown): boolean {\n if (err instanceof DOMException && err.name === \"AbortError\") return true;\n if (err instanceof Error) {\n if (err.name === \"AbortError\") return true;\n // OpenAI SDK wraps abort as APIUserAbortError\n if (err.name === \"APIUserAbortError\") return true;\n if (err.message.includes(\"abort\")) return true;\n }\n return false;\n}\n"],"mappings":"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"}
@@ -70,31 +70,31 @@ declare const ChannelsConfigSchema: z.ZodObject<{
70
70
  channelAccessToken?: string | undefined;
71
71
  }>>;
72
72
  }, "strip", z.ZodTypeAny, {
73
- telegram: {
74
- enabled: boolean;
75
- token: string;
76
- allowFrom: string[];
77
- proxy?: string | null | undefined;
78
- };
79
73
  line: {
80
74
  enabled: boolean;
81
75
  allowFrom: string[];
82
76
  channelSecret: string;
83
77
  channelAccessToken: string;
84
78
  };
85
- }, {
86
- telegram?: {
87
- enabled?: boolean | undefined;
88
- token?: string | undefined;
89
- allowFrom?: string[] | undefined;
79
+ telegram: {
80
+ enabled: boolean;
81
+ token: string;
82
+ allowFrom: string[];
90
83
  proxy?: string | null | undefined;
91
- } | undefined;
84
+ };
85
+ }, {
92
86
  line?: {
93
87
  enabled?: boolean | undefined;
94
88
  allowFrom?: string[] | undefined;
95
89
  channelSecret?: string | undefined;
96
90
  channelAccessToken?: string | undefined;
97
91
  } | undefined;
92
+ telegram?: {
93
+ enabled?: boolean | undefined;
94
+ token?: string | undefined;
95
+ allowFrom?: string[] | undefined;
96
+ proxy?: string | null | undefined;
97
+ } | undefined;
98
98
  }>;
99
99
  type ChannelsConfig = z.infer<typeof ChannelsConfigSchema>;
100
100
  declare const AgentDefaultsSchema: z.ZodObject<{
@@ -539,15 +539,15 @@ declare const ToolsConfigSchema: z.ZodObject<{
539
539
  export?: string | undefined;
540
540
  }>, "many">>;
541
541
  }, "strip", z.ZodTypeAny, {
542
+ exec: {
543
+ timeout: number;
544
+ };
542
545
  web: {
543
546
  search: {
544
547
  apiKey: string;
545
548
  maxResults: number;
546
549
  };
547
550
  };
548
- exec: {
549
- timeout: number;
550
- };
551
551
  restrictToWorkspace: boolean;
552
552
  enabled?: string[] | undefined;
553
553
  disabled?: string[] | undefined;
@@ -557,6 +557,9 @@ declare const ToolsConfigSchema: z.ZodObject<{
557
557
  export?: string | undefined;
558
558
  }[] | undefined;
559
559
  }, {
560
+ exec?: {
561
+ timeout?: number | undefined;
562
+ } | undefined;
560
563
  enabled?: string[] | undefined;
561
564
  web?: {
562
565
  search?: {
@@ -564,9 +567,6 @@ declare const ToolsConfigSchema: z.ZodObject<{
564
567
  maxResults?: number | undefined;
565
568
  } | undefined;
566
569
  } | undefined;
567
- exec?: {
568
- timeout?: number | undefined;
569
- } | undefined;
570
570
  restrictToWorkspace?: boolean | undefined;
571
571
  disabled?: string[] | undefined;
572
572
  custom?: {
@@ -648,31 +648,31 @@ declare const ConfigSchema: z.ZodObject<{
648
648
  channelAccessToken?: string | undefined;
649
649
  }>>;
650
650
  }, "strip", z.ZodTypeAny, {
651
- telegram: {
652
- enabled: boolean;
653
- token: string;
654
- allowFrom: string[];
655
- proxy?: string | null | undefined;
656
- };
657
651
  line: {
658
652
  enabled: boolean;
659
653
  allowFrom: string[];
660
654
  channelSecret: string;
661
655
  channelAccessToken: string;
662
656
  };
663
- }, {
664
- telegram?: {
665
- enabled?: boolean | undefined;
666
- token?: string | undefined;
667
- allowFrom?: string[] | undefined;
657
+ telegram: {
658
+ enabled: boolean;
659
+ token: string;
660
+ allowFrom: string[];
668
661
  proxy?: string | null | undefined;
669
- } | undefined;
662
+ };
663
+ }, {
670
664
  line?: {
671
665
  enabled?: boolean | undefined;
672
666
  allowFrom?: string[] | undefined;
673
667
  channelSecret?: string | undefined;
674
668
  channelAccessToken?: string | undefined;
675
669
  } | undefined;
670
+ telegram?: {
671
+ enabled?: boolean | undefined;
672
+ token?: string | undefined;
673
+ allowFrom?: string[] | undefined;
674
+ proxy?: string | null | undefined;
675
+ } | undefined;
676
676
  }>>;
677
677
  providers: z.ZodDefault<z.ZodObject<{
678
678
  anthropic: z.ZodDefault<z.ZodObject<{
@@ -988,15 +988,15 @@ declare const ConfigSchema: z.ZodObject<{
988
988
  export?: string | undefined;
989
989
  }>, "many">>;
990
990
  }, "strip", z.ZodTypeAny, {
991
+ exec: {
992
+ timeout: number;
993
+ };
991
994
  web: {
992
995
  search: {
993
996
  apiKey: string;
994
997
  maxResults: number;
995
998
  };
996
999
  };
997
- exec: {
998
- timeout: number;
999
- };
1000
1000
  restrictToWorkspace: boolean;
1001
1001
  enabled?: string[] | undefined;
1002
1002
  disabled?: string[] | undefined;
@@ -1006,6 +1006,9 @@ declare const ConfigSchema: z.ZodObject<{
1006
1006
  export?: string | undefined;
1007
1007
  }[] | undefined;
1008
1008
  }, {
1009
+ exec?: {
1010
+ timeout?: number | undefined;
1011
+ } | undefined;
1009
1012
  enabled?: string[] | undefined;
1010
1013
  web?: {
1011
1014
  search?: {
@@ -1013,9 +1016,6 @@ declare const ConfigSchema: z.ZodObject<{
1013
1016
  maxResults?: number | undefined;
1014
1017
  } | undefined;
1015
1018
  } | undefined;
1016
- exec?: {
1017
- timeout?: number | undefined;
1018
- } | undefined;
1019
1019
  restrictToWorkspace?: boolean | undefined;
1020
1020
  disabled?: string[] | undefined;
1021
1021
  custom?: {
@@ -1035,18 +1035,18 @@ declare const ConfigSchema: z.ZodObject<{
1035
1035
  };
1036
1036
  };
1037
1037
  channels: {
1038
- telegram: {
1039
- enabled: boolean;
1040
- token: string;
1041
- allowFrom: string[];
1042
- proxy?: string | null | undefined;
1043
- };
1044
1038
  line: {
1045
1039
  enabled: boolean;
1046
1040
  allowFrom: string[];
1047
1041
  channelSecret: string;
1048
1042
  channelAccessToken: string;
1049
1043
  };
1044
+ telegram: {
1045
+ enabled: boolean;
1046
+ token: string;
1047
+ allowFrom: string[];
1048
+ proxy?: string | null | undefined;
1049
+ };
1050
1050
  };
1051
1051
  providers: {
1052
1052
  anthropic: {
@@ -1110,15 +1110,15 @@ declare const ConfigSchema: z.ZodObject<{
1110
1110
  port: number;
1111
1111
  };
1112
1112
  tools: {
1113
+ exec: {
1114
+ timeout: number;
1115
+ };
1113
1116
  web: {
1114
1117
  search: {
1115
1118
  apiKey: string;
1116
1119
  maxResults: number;
1117
1120
  };
1118
1121
  };
1119
- exec: {
1120
- timeout: number;
1121
- };
1122
1122
  restrictToWorkspace: boolean;
1123
1123
  enabled?: string[] | undefined;
1124
1124
  disabled?: string[] | undefined;
@@ -1139,18 +1139,18 @@ declare const ConfigSchema: z.ZodObject<{
1139
1139
  } | undefined;
1140
1140
  } | undefined;
1141
1141
  channels?: {
1142
- telegram?: {
1143
- enabled?: boolean | undefined;
1144
- token?: string | undefined;
1145
- allowFrom?: string[] | undefined;
1146
- proxy?: string | null | undefined;
1147
- } | undefined;
1148
1142
  line?: {
1149
1143
  enabled?: boolean | undefined;
1150
1144
  allowFrom?: string[] | undefined;
1151
1145
  channelSecret?: string | undefined;
1152
1146
  channelAccessToken?: string | undefined;
1153
1147
  } | undefined;
1148
+ telegram?: {
1149
+ enabled?: boolean | undefined;
1150
+ token?: string | undefined;
1151
+ allowFrom?: string[] | undefined;
1152
+ proxy?: string | null | undefined;
1153
+ } | undefined;
1154
1154
  } | undefined;
1155
1155
  providers?: {
1156
1156
  anthropic?: {
@@ -1214,6 +1214,9 @@ declare const ConfigSchema: z.ZodObject<{
1214
1214
  port?: number | undefined;
1215
1215
  } | undefined;
1216
1216
  tools?: {
1217
+ exec?: {
1218
+ timeout?: number | undefined;
1219
+ } | undefined;
1217
1220
  enabled?: string[] | undefined;
1218
1221
  web?: {
1219
1222
  search?: {
@@ -1221,9 +1224,6 @@ declare const ConfigSchema: z.ZodObject<{
1221
1224
  maxResults?: number | undefined;
1222
1225
  } | undefined;
1223
1226
  } | undefined;
1224
- exec?: {
1225
- timeout?: number | undefined;
1226
- } | undefined;
1227
1227
  restrictToWorkspace?: boolean | undefined;
1228
1228
  disabled?: string[] | undefined;
1229
1229
  custom?: {
package/dist/index.d.mts CHANGED
@@ -13,7 +13,6 @@ import { LineChannel } from "./channels/line.mjs";
13
13
  import { ChannelManager } from "./channels/manager.mjs";
14
14
  import { TelegramChannel } from "./channels/telegram.mjs";
15
15
  import { loadConfig, saveConfig } from "./config/loader.mjs";
16
- import { CronService } from "./cron/service.mjs";
17
16
  import { HeartbeatService } from "./heartbeat/service.mjs";
18
17
  import { OpenAIProvider } from "./providers/openai-provider.mjs";
19
18
 
@@ -21,5 +20,5 @@ import { OpenAIProvider } from "./providers/openai-provider.mjs";
21
20
  declare const VERSION: string;
22
21
  declare const LOGO = "\uD83D\uDC08";
23
22
  //#endregion
24
- export { AgentLoop, ChannelManager, type Config, ContextBuilder, CronService, HeartbeatService, type InboundMessage, type LLMProvider, type LLMResponse, LOGO, LineChannel, MemoryStore, MessageBus, OpenAIProvider, type OutboundMessage, PROVIDERS, type ProviderSpec, SessionManager, SkillsLoader, SubagentManager, TelegramChannel, type ToolCallRequest, VERSION, findByModel, findByName, findGateway, getModelOverrides, loadConfig, resolveModel, saveConfig };
23
+ export { AgentLoop, ChannelManager, type Config, ContextBuilder, HeartbeatService, type InboundMessage, type LLMProvider, type LLMResponse, LOGO, LineChannel, MemoryStore, MessageBus, OpenAIProvider, type OutboundMessage, PROVIDERS, type ProviderSpec, SessionManager, SkillsLoader, SubagentManager, TelegramChannel, type ToolCallRequest, VERSION, findByModel, findByName, findGateway, getModelOverrides, loadConfig, resolveModel, saveConfig };
25
24
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;cAmBa,OAAA;AAAA,cACA,IAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;cAmBa,OAAA;AAAA,cACA,IAAA"}
package/dist/index.mjs CHANGED
@@ -11,7 +11,6 @@ import { OpenAIProvider } from "./providers/openai-provider.mjs";
11
11
  import { TelegramChannel } from "./channels/telegram.mjs";
12
12
  import { LineChannel } from "./channels/line.mjs";
13
13
  import { ChannelManager } from "./channels/manager.mjs";
14
- import { CronService } from "./cron/service.mjs";
15
14
  import { HeartbeatService } from "./heartbeat/service.mjs";
16
15
  import { readFileSync } from "node:fs";
17
16
  import { dirname, join } from "node:path";
@@ -32,5 +31,5 @@ const VERSION = readVersion();
32
31
  const LOGO = "🐈";
33
32
 
34
33
  //#endregion
35
- export { AgentLoop, ChannelManager, ContextBuilder, CronService, HeartbeatService, LOGO, LineChannel, MemoryStore, MessageBus, OpenAIProvider, PROVIDERS, SessionManager, SkillsLoader, SubagentManager, TelegramChannel, VERSION, findByModel, findByName, findGateway, getModelOverrides, loadConfig, resolveModel, saveConfig };
34
+ export { AgentLoop, ChannelManager, ContextBuilder, HeartbeatService, LOGO, LineChannel, MemoryStore, MessageBus, OpenAIProvider, PROVIDERS, SessionManager, SkillsLoader, SubagentManager, TelegramChannel, VERSION, findByModel, findByName, findGateway, getModelOverrides, loadConfig, resolveModel, saveConfig };
36
35
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nfunction readVersion(): string {\n try {\n // Works from both src/ (dev) and dist/ (built)\n for (const rel of [\"../package.json\", \"../../package.json\"]) {\n try {\n const pkg = JSON.parse(readFileSync(join(__dirname, rel), \"utf-8\"));\n if (pkg.version) return pkg.version;\n } catch { /* try next */ }\n }\n } catch { /* fallback */ }\n return \"0.0.0\";\n}\n\nexport const VERSION = readVersion();\nexport const LOGO = \"\\u{1F408}\";\n\n// Core exports\nexport { AgentLoop } from \"./agent/loop.js\";\nexport { ContextBuilder } from \"./agent/context.js\";\nexport { MemoryStore } from \"./agent/memory.js\";\nexport { SkillsLoader } from \"./agent/skills.js\";\nexport { SubagentManager } from \"./agent/subagent.js\";\n\n// Bus\nexport { MessageBus } from \"./bus/queue.js\";\nexport type { InboundMessage, OutboundMessage } from \"./bus/events.js\";\n\n// Config\nexport { loadConfig, saveConfig } from \"./config/loader.js\";\nexport type { Config } from \"./config/schema.js\";\n\n// Providers\nexport { OpenAIProvider } from \"./providers/openai-provider.js\";\nexport type { LLMProvider, LLMResponse, ToolCallRequest } from \"./providers/base.js\";\nexport { PROVIDERS, findByModel, findByName, findGateway, resolveModel, getModelOverrides } from \"./providers/registry.js\";\nexport type { ProviderSpec } from \"./providers/registry.js\";\n\n// Channels\nexport { TelegramChannel } from \"./channels/telegram.js\";\nexport { LineChannel } from \"./channels/line.js\";\nexport { ChannelManager } from \"./channels/manager.js\";\n\n// Services\nexport { CronService } from \"./cron/service.js\";\nexport { HeartbeatService } from \"./heartbeat/service.js\";\nexport { SessionManager } from \"./session/manager.js\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAIA,MAAM,YAAY,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;AAEzD,SAAS,cAAsB;AAC7B,KAAI;AAEF,OAAK,MAAM,OAAO,CAAC,mBAAmB,qBAAqB,CACzD,KAAI;GACF,MAAM,MAAM,KAAK,MAAM,aAAa,KAAK,WAAW,IAAI,EAAE,QAAQ,CAAC;AACnE,OAAI,IAAI,QAAS,QAAO,IAAI;UACtB;SAEJ;AACR,QAAO;;AAGT,MAAa,UAAU,aAAa;AACpC,MAAa,OAAO"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nfunction readVersion(): string {\n try {\n // Works from both src/ (dev) and dist/ (built)\n for (const rel of [\"../package.json\", \"../../package.json\"]) {\n try {\n const pkg = JSON.parse(readFileSync(join(__dirname, rel), \"utf-8\"));\n if (pkg.version) return pkg.version;\n } catch { /* try next */ }\n }\n } catch { /* fallback */ }\n return \"0.0.0\";\n}\n\nexport const VERSION = readVersion();\nexport const LOGO = \"\\u{1F408}\";\n\n// Core exports\nexport { AgentLoop } from \"./agent/loop.js\";\nexport { ContextBuilder } from \"./agent/context.js\";\nexport { MemoryStore } from \"./agent/memory.js\";\nexport { SkillsLoader } from \"./agent/skills.js\";\nexport { SubagentManager } from \"./agent/subagent.js\";\n\n// Bus\nexport { MessageBus } from \"./bus/queue.js\";\nexport type { InboundMessage, OutboundMessage } from \"./bus/events.js\";\n\n// Config\nexport { loadConfig, saveConfig } from \"./config/loader.js\";\nexport type { Config } from \"./config/schema.js\";\n\n// Providers\nexport { OpenAIProvider } from \"./providers/openai-provider.js\";\nexport type { LLMProvider, LLMResponse, ToolCallRequest } from \"./providers/base.js\";\nexport { PROVIDERS, findByModel, findByName, findGateway, resolveModel, getModelOverrides } from \"./providers/registry.js\";\nexport type { ProviderSpec } from \"./providers/registry.js\";\n\n// Channels\nexport { TelegramChannel } from \"./channels/telegram.js\";\nexport { LineChannel } from \"./channels/line.js\";\nexport { ChannelManager } from \"./channels/manager.js\";\n\n// Services\nexport { HeartbeatService } from \"./heartbeat/service.js\";\nexport { SessionManager } from \"./session/manager.js\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAIA,MAAM,YAAY,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;AAEzD,SAAS,cAAsB;AAC7B,KAAI;AAEF,OAAK,MAAM,OAAO,CAAC,mBAAmB,qBAAqB,CACzD,KAAI;GACF,MAAM,MAAM,KAAK,MAAM,aAAa,KAAK,WAAW,IAAI,EAAE,QAAQ,CAAC;AACnE,OAAI,IAAI,QAAS,QAAO,IAAI;UACtB;SAEJ;AACR,QAAO;;AAGT,MAAa,UAAU,aAAa;AACpC,MAAa,OAAO"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jcheesepkg/nanobot",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "Lightweight AI assistant - TypeScript port",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",
@@ -1,42 +0,0 @@
1
- import { CronJob, CronSchedule } from "./types.mjs";
2
-
3
- //#region src/cron/service.d.ts
4
- type JobCallback = (job: CronJob) => Promise<string | null>;
5
- /** Service for managing and executing scheduled jobs. */
6
- declare class CronService {
7
- private storePath;
8
- onJob: JobCallback | null;
9
- private store;
10
- private timerHandle;
11
- private _running;
12
- constructor(storePath: string, onJob?: JobCallback);
13
- private loadStore;
14
- private saveStore;
15
- start(): Promise<void>;
16
- stop(): void;
17
- private recomputeNextRuns;
18
- private getNextWakeMs;
19
- private armTimer;
20
- private onTimer;
21
- private executeJob;
22
- listJobs(includeDisabled?: boolean): CronJob[];
23
- addJob(params: {
24
- name: string;
25
- schedule: CronSchedule;
26
- message: string;
27
- deliver?: boolean;
28
- channel?: string;
29
- to?: string;
30
- deleteAfterRun?: boolean;
31
- }): CronJob;
32
- removeJob(jobId: string): boolean;
33
- enableJob(jobId: string, enabled?: boolean): CronJob | null;
34
- status(): {
35
- enabled: boolean;
36
- jobs: number;
37
- nextWakeAtMs: number | null;
38
- };
39
- }
40
- //#endregion
41
- export { CronService };
42
- //# sourceMappingURL=service.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"service.d.mts","names":[],"sources":["../../src/cron/service.ts"],"mappings":";;;KA6CK,WAAA,IAAe,GAAA,EAAK,OAAA,KAAY,OAAA;;cAGxB,WAAA;EAAA,QACH,SAAA;EACR,KAAA,EAAO,WAAA;EAAA,QACC,KAAA;EAAA,QACA,WAAA;EAAA,QACA,QAAA;cAEI,SAAA,UAAmB,KAAA,GAAQ,WAAA;EAAA,QAK/B,SAAA;EAAA,QAmEA,SAAA;EAyCF,KAAA,CAAA,GAAS,OAAA;EAWf,IAAA,CAAA;EAAA,QAQQ,iBAAA;EAAA,QAUA,aAAA;EAAA,QAQA,QAAA;EAAA,QAiBM,OAAA;EAAA,QAgBA,UAAA;EAkCd,QAAA,CAAS,eAAA,aAA0B,OAAA;EAWnC,MAAA,CAAO,MAAA;IACL,IAAA;IACA,QAAA,EAAU,YAAA;IACV,OAAA;IACA,OAAA;IACA,OAAA;IACA,EAAA;IACA,cAAA;EAAA,IACE,OAAA;EAgCJ,SAAA,CAAU,KAAA;EAcV,SAAA,CAAU,KAAA,UAAe,OAAA,aAAiB,OAAA;EAmB1C,MAAA,CAAA;IAAY,OAAA;IAAkB,IAAA;IAAc,YAAA;EAAA;AAAA"}
@@ -1,250 +0,0 @@
1
- import { createCronJobState, createCronPayload, createCronStore } from "./types.mjs";
2
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
- import { dirname } from "node:path";
4
- import { randomUUID } from "node:crypto";
5
- import cronParser from "cron-parser";
6
-
7
- //#region src/cron/service.ts
8
- function nowMs() {
9
- return Date.now();
10
- }
11
- function computeNextRun(schedule, currentMs) {
12
- if (schedule.kind === "at") return schedule.atMs && schedule.atMs > currentMs ? schedule.atMs : null;
13
- if (schedule.kind === "every") {
14
- if (!schedule.everyMs || schedule.everyMs <= 0) return null;
15
- return currentMs + schedule.everyMs;
16
- }
17
- if (schedule.kind === "cron" && schedule.expr) try {
18
- return cronParser.parseExpression(schedule.expr).next().getTime();
19
- } catch {
20
- return null;
21
- }
22
- return null;
23
- }
24
- /** Service for managing and executing scheduled jobs. */
25
- var CronService = class {
26
- storePath;
27
- onJob;
28
- store = null;
29
- timerHandle = null;
30
- _running = false;
31
- constructor(storePath, onJob) {
32
- this.storePath = storePath;
33
- this.onJob = onJob ?? null;
34
- }
35
- loadStore() {
36
- if (this.store) return this.store;
37
- if (existsSync(this.storePath)) try {
38
- const raw = readFileSync(this.storePath, "utf-8");
39
- const data = JSON.parse(raw);
40
- const jobs = (data.jobs ?? []).map((j) => ({
41
- id: j.id,
42
- name: j.name,
43
- enabled: j.enabled ?? true,
44
- schedule: {
45
- kind: j.schedule.kind,
46
- atMs: j.schedule.atMs,
47
- everyMs: j.schedule.everyMs,
48
- expr: j.schedule.expr,
49
- tz: j.schedule.tz
50
- },
51
- payload: {
52
- kind: j.payload?.kind ?? "agent_turn",
53
- message: j.payload?.message ?? "",
54
- deliver: j.payload?.deliver ?? false,
55
- channel: j.payload?.channel,
56
- to: j.payload?.to
57
- },
58
- state: {
59
- nextRunAtMs: (j.state ?? {}).nextRunAtMs,
60
- lastRunAtMs: (j.state ?? {}).lastRunAtMs,
61
- lastStatus: (j.state ?? {}).lastStatus,
62
- lastError: (j.state ?? {}).lastError
63
- },
64
- createdAtMs: j.createdAtMs ?? 0,
65
- updatedAtMs: j.updatedAtMs ?? 0,
66
- deleteAfterRun: j.deleteAfterRun ?? false
67
- }));
68
- this.store = {
69
- version: data.version ?? 1,
70
- jobs
71
- };
72
- } catch (err) {
73
- console.warn("Failed to load cron store:", err);
74
- this.store = createCronStore();
75
- }
76
- else this.store = createCronStore();
77
- return this.store;
78
- }
79
- saveStore() {
80
- if (!this.store) return;
81
- const dir = dirname(this.storePath);
82
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
83
- const data = {
84
- version: this.store.version,
85
- jobs: this.store.jobs.map((j) => ({
86
- id: j.id,
87
- name: j.name,
88
- enabled: j.enabled,
89
- schedule: {
90
- kind: j.schedule.kind,
91
- atMs: j.schedule.atMs,
92
- everyMs: j.schedule.everyMs,
93
- expr: j.schedule.expr,
94
- tz: j.schedule.tz
95
- },
96
- payload: {
97
- kind: j.payload.kind,
98
- message: j.payload.message,
99
- deliver: j.payload.deliver,
100
- channel: j.payload.channel,
101
- to: j.payload.to
102
- },
103
- state: {
104
- nextRunAtMs: j.state.nextRunAtMs,
105
- lastRunAtMs: j.state.lastRunAtMs,
106
- lastStatus: j.state.lastStatus,
107
- lastError: j.state.lastError
108
- },
109
- createdAtMs: j.createdAtMs,
110
- updatedAtMs: j.updatedAtMs,
111
- deleteAfterRun: j.deleteAfterRun
112
- }))
113
- };
114
- writeFileSync(this.storePath, JSON.stringify(data, null, 2));
115
- }
116
- async start() {
117
- this._running = true;
118
- this.loadStore();
119
- this.recomputeNextRuns();
120
- this.saveStore();
121
- this.armTimer();
122
- console.log(`Cron service started with ${this.store?.jobs.length ?? 0} jobs`);
123
- }
124
- stop() {
125
- this._running = false;
126
- if (this.timerHandle) {
127
- clearTimeout(this.timerHandle);
128
- this.timerHandle = null;
129
- }
130
- }
131
- recomputeNextRuns() {
132
- if (!this.store) return;
133
- const now = nowMs();
134
- for (const job of this.store.jobs) if (job.enabled) job.state.nextRunAtMs = computeNextRun(job.schedule, now);
135
- }
136
- getNextWakeMs() {
137
- if (!this.store) return null;
138
- const times = this.store.jobs.filter((j) => j.enabled && j.state.nextRunAtMs).map((j) => j.state.nextRunAtMs);
139
- return times.length > 0 ? Math.min(...times) : null;
140
- }
141
- armTimer() {
142
- if (this.timerHandle) {
143
- clearTimeout(this.timerHandle);
144
- this.timerHandle = null;
145
- }
146
- const nextWake = this.getNextWakeMs();
147
- if (!nextWake || !this._running) return;
148
- const delayMs = Math.max(0, nextWake - nowMs());
149
- this.timerHandle = setTimeout(() => {
150
- if (this._running) this.onTimer().catch(console.error);
151
- }, delayMs);
152
- }
153
- async onTimer() {
154
- if (!this.store) return;
155
- const now = nowMs();
156
- const dueJobs = this.store.jobs.filter((j) => j.enabled && j.state.nextRunAtMs && now >= j.state.nextRunAtMs);
157
- for (const job of dueJobs) await this.executeJob(job);
158
- this.saveStore();
159
- this.armTimer();
160
- }
161
- async executeJob(job) {
162
- const startMs = nowMs();
163
- console.log(`Cron: executing job '${job.name}' (${job.id})`);
164
- try {
165
- if (this.onJob) await this.onJob(job);
166
- job.state.lastStatus = "ok";
167
- job.state.lastError = null;
168
- console.log(`Cron: job '${job.name}' completed`);
169
- } catch (err) {
170
- job.state.lastStatus = "error";
171
- job.state.lastError = err instanceof Error ? err.message : String(err);
172
- console.error(`Cron: job '${job.name}' failed:`, err);
173
- }
174
- job.state.lastRunAtMs = startMs;
175
- job.updatedAtMs = nowMs();
176
- if (job.schedule.kind === "at") if (job.deleteAfterRun && this.store) this.store.jobs = this.store.jobs.filter((j) => j.id !== job.id);
177
- else {
178
- job.enabled = false;
179
- job.state.nextRunAtMs = null;
180
- }
181
- else job.state.nextRunAtMs = computeNextRun(job.schedule, nowMs());
182
- }
183
- listJobs(includeDisabled = false) {
184
- const store = this.loadStore();
185
- return (includeDisabled ? store.jobs : store.jobs.filter((j) => j.enabled)).sort((a, b) => (a.state.nextRunAtMs ?? Infinity) - (b.state.nextRunAtMs ?? Infinity));
186
- }
187
- addJob(params) {
188
- const store = this.loadStore();
189
- const now = nowMs();
190
- const job = {
191
- id: randomUUID().slice(0, 8),
192
- name: params.name,
193
- enabled: true,
194
- schedule: params.schedule,
195
- payload: createCronPayload({
196
- kind: "agent_turn",
197
- message: params.message,
198
- deliver: params.deliver ?? false,
199
- channel: params.channel,
200
- to: params.to
201
- }),
202
- state: createCronJobState({ nextRunAtMs: computeNextRun(params.schedule, now) }),
203
- createdAtMs: now,
204
- updatedAtMs: now,
205
- deleteAfterRun: params.deleteAfterRun ?? false
206
- };
207
- store.jobs.push(job);
208
- this.saveStore();
209
- this.armTimer();
210
- console.log(`Cron: added job '${params.name}' (${job.id})`);
211
- return job;
212
- }
213
- removeJob(jobId) {
214
- const store = this.loadStore();
215
- const before = store.jobs.length;
216
- store.jobs = store.jobs.filter((j) => j.id !== jobId);
217
- const removed = store.jobs.length < before;
218
- if (removed) {
219
- this.saveStore();
220
- this.armTimer();
221
- console.log(`Cron: removed job ${jobId}`);
222
- }
223
- return removed;
224
- }
225
- enableJob(jobId, enabled = true) {
226
- const store = this.loadStore();
227
- for (const job of store.jobs) if (job.id === jobId) {
228
- job.enabled = enabled;
229
- job.updatedAtMs = nowMs();
230
- if (enabled) job.state.nextRunAtMs = computeNextRun(job.schedule, nowMs());
231
- else job.state.nextRunAtMs = null;
232
- this.saveStore();
233
- this.armTimer();
234
- return job;
235
- }
236
- return null;
237
- }
238
- status() {
239
- const store = this.loadStore();
240
- return {
241
- enabled: this._running,
242
- jobs: store.jobs.length,
243
- nextWakeAtMs: this.getNextWakeMs()
244
- };
245
- }
246
- };
247
-
248
- //#endregion
249
- export { CronService };
250
- //# sourceMappingURL=service.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"service.mjs","names":[],"sources":["../../src/cron/service.ts"],"sourcesContent":["import { readFileSync, writeFileSync, existsSync, mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { randomUUID } from \"node:crypto\";\nimport cronParser from \"cron-parser\";\nimport type {\n CronJob,\n CronSchedule,\n CronStore,\n CronPayload,\n CronJobState,\n} from \"./types.js\";\nimport { createCronStore, createCronPayload, createCronJobState } from \"./types.js\";\n\nfunction nowMs(): number {\n return Date.now();\n}\n\nfunction computeNextRun(\n schedule: CronSchedule,\n currentMs: number,\n): number | null {\n if (schedule.kind === \"at\") {\n return schedule.atMs && schedule.atMs > currentMs\n ? schedule.atMs\n : null;\n }\n\n if (schedule.kind === \"every\") {\n if (!schedule.everyMs || schedule.everyMs <= 0) return null;\n return currentMs + schedule.everyMs;\n }\n\n if (schedule.kind === \"cron\" && schedule.expr) {\n try {\n const interval = cronParser.parseExpression(schedule.expr);\n const next = interval.next();\n return next.getTime();\n } catch {\n return null;\n }\n }\n\n return null;\n}\n\ntype JobCallback = (job: CronJob) => Promise<string | null>;\n\n/** Service for managing and executing scheduled jobs. */\nexport class CronService {\n private storePath: string;\n onJob: JobCallback | null;\n private store: CronStore | null = null;\n private timerHandle: ReturnType<typeof setTimeout> | null = null;\n private _running = false;\n\n constructor(storePath: string, onJob?: JobCallback) {\n this.storePath = storePath;\n this.onJob = onJob ?? null;\n }\n\n private loadStore(): CronStore {\n if (this.store) return this.store;\n\n if (existsSync(this.storePath)) {\n try {\n const raw = readFileSync(this.storePath, \"utf-8\");\n const data = JSON.parse(raw);\n const jobs: CronJob[] = (data.jobs ?? []).map(\n (j: Record<string, unknown>) => ({\n id: j.id as string,\n name: j.name as string,\n enabled: (j.enabled as boolean) ?? true,\n schedule: {\n kind: (j.schedule as Record<string, unknown>).kind as string,\n atMs: (j.schedule as Record<string, unknown>).atMs as number | undefined,\n everyMs: (j.schedule as Record<string, unknown>).everyMs as number | undefined,\n expr: (j.schedule as Record<string, unknown>).expr as string | undefined,\n tz: (j.schedule as Record<string, unknown>).tz as string | undefined,\n } as CronSchedule,\n payload: {\n kind:\n ((j.payload as Record<string, unknown>)?.kind as string) ??\n \"agent_turn\",\n message:\n ((j.payload as Record<string, unknown>)?.message as string) ??\n \"\",\n deliver:\n ((j.payload as Record<string, unknown>)?.deliver as boolean) ??\n false,\n channel: (j.payload as Record<string, unknown>)?.channel as\n | string\n | undefined,\n to: (j.payload as Record<string, unknown>)?.to as\n | string\n | undefined,\n } as CronPayload,\n state: {\n nextRunAtMs: (\n (j.state as Record<string, unknown>) ?? {}\n ).nextRunAtMs as number | undefined,\n lastRunAtMs: (\n (j.state as Record<string, unknown>) ?? {}\n ).lastRunAtMs as number | undefined,\n lastStatus: (\n (j.state as Record<string, unknown>) ?? {}\n ).lastStatus as string | undefined,\n lastError: (\n (j.state as Record<string, unknown>) ?? {}\n ).lastError as string | undefined,\n } as CronJobState,\n createdAtMs: (j.createdAtMs as number) ?? 0,\n updatedAtMs: (j.updatedAtMs as number) ?? 0,\n deleteAfterRun: (j.deleteAfterRun as boolean) ?? false,\n }),\n );\n this.store = { version: data.version ?? 1, jobs };\n } catch (err) {\n console.warn(\"Failed to load cron store:\", err);\n this.store = createCronStore();\n }\n } else {\n this.store = createCronStore();\n }\n\n return this.store;\n }\n\n private saveStore(): void {\n if (!this.store) return;\n\n const dir = dirname(this.storePath);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n\n const data = {\n version: this.store.version,\n jobs: this.store.jobs.map((j) => ({\n id: j.id,\n name: j.name,\n enabled: j.enabled,\n schedule: {\n kind: j.schedule.kind,\n atMs: j.schedule.atMs,\n everyMs: j.schedule.everyMs,\n expr: j.schedule.expr,\n tz: j.schedule.tz,\n },\n payload: {\n kind: j.payload.kind,\n message: j.payload.message,\n deliver: j.payload.deliver,\n channel: j.payload.channel,\n to: j.payload.to,\n },\n state: {\n nextRunAtMs: j.state.nextRunAtMs,\n lastRunAtMs: j.state.lastRunAtMs,\n lastStatus: j.state.lastStatus,\n lastError: j.state.lastError,\n },\n createdAtMs: j.createdAtMs,\n updatedAtMs: j.updatedAtMs,\n deleteAfterRun: j.deleteAfterRun,\n })),\n };\n\n writeFileSync(this.storePath, JSON.stringify(data, null, 2));\n }\n\n async start(): Promise<void> {\n this._running = true;\n this.loadStore();\n this.recomputeNextRuns();\n this.saveStore();\n this.armTimer();\n console.log(\n `Cron service started with ${this.store?.jobs.length ?? 0} jobs`,\n );\n }\n\n stop(): void {\n this._running = false;\n if (this.timerHandle) {\n clearTimeout(this.timerHandle);\n this.timerHandle = null;\n }\n }\n\n private recomputeNextRuns(): void {\n if (!this.store) return;\n const now = nowMs();\n for (const job of this.store.jobs) {\n if (job.enabled) {\n job.state.nextRunAtMs = computeNextRun(job.schedule, now);\n }\n }\n }\n\n private getNextWakeMs(): number | null {\n if (!this.store) return null;\n const times = this.store.jobs\n .filter((j) => j.enabled && j.state.nextRunAtMs)\n .map((j) => j.state.nextRunAtMs!);\n return times.length > 0 ? Math.min(...times) : null;\n }\n\n private armTimer(): void {\n if (this.timerHandle) {\n clearTimeout(this.timerHandle);\n this.timerHandle = null;\n }\n\n const nextWake = this.getNextWakeMs();\n if (!nextWake || !this._running) return;\n\n const delayMs = Math.max(0, nextWake - nowMs());\n this.timerHandle = setTimeout(() => {\n if (this._running) {\n this.onTimer().catch(console.error);\n }\n }, delayMs);\n }\n\n private async onTimer(): Promise<void> {\n if (!this.store) return;\n const now = nowMs();\n\n const dueJobs = this.store.jobs.filter(\n (j) => j.enabled && j.state.nextRunAtMs && now >= j.state.nextRunAtMs,\n );\n\n for (const job of dueJobs) {\n await this.executeJob(job);\n }\n\n this.saveStore();\n this.armTimer();\n }\n\n private async executeJob(job: CronJob): Promise<void> {\n const startMs = nowMs();\n console.log(`Cron: executing job '${job.name}' (${job.id})`);\n\n try {\n if (this.onJob) {\n await this.onJob(job);\n }\n job.state.lastStatus = \"ok\";\n job.state.lastError = null;\n console.log(`Cron: job '${job.name}' completed`);\n } catch (err) {\n job.state.lastStatus = \"error\";\n job.state.lastError = err instanceof Error ? err.message : String(err);\n console.error(`Cron: job '${job.name}' failed:`, err);\n }\n\n job.state.lastRunAtMs = startMs;\n job.updatedAtMs = nowMs();\n\n if (job.schedule.kind === \"at\") {\n if (job.deleteAfterRun && this.store) {\n this.store.jobs = this.store.jobs.filter((j) => j.id !== job.id);\n } else {\n job.enabled = false;\n job.state.nextRunAtMs = null;\n }\n } else {\n job.state.nextRunAtMs = computeNextRun(job.schedule, nowMs());\n }\n }\n\n // ========== Public API ==========\n\n listJobs(includeDisabled = false): CronJob[] {\n const store = this.loadStore();\n const jobs = includeDisabled\n ? store.jobs\n : store.jobs.filter((j) => j.enabled);\n return jobs.sort(\n (a, b) =>\n (a.state.nextRunAtMs ?? Infinity) - (b.state.nextRunAtMs ?? Infinity),\n );\n }\n\n addJob(params: {\n name: string;\n schedule: CronSchedule;\n message: string;\n deliver?: boolean;\n channel?: string;\n to?: string;\n deleteAfterRun?: boolean;\n }): CronJob {\n const store = this.loadStore();\n const now = nowMs();\n\n const job: CronJob = {\n id: randomUUID().slice(0, 8),\n name: params.name,\n enabled: true,\n schedule: params.schedule,\n payload: createCronPayload({\n kind: \"agent_turn\",\n message: params.message,\n deliver: params.deliver ?? false,\n channel: params.channel,\n to: params.to,\n }),\n state: createCronJobState({\n nextRunAtMs: computeNextRun(params.schedule, now),\n }),\n createdAtMs: now,\n updatedAtMs: now,\n deleteAfterRun: params.deleteAfterRun ?? false,\n };\n\n store.jobs.push(job);\n this.saveStore();\n this.armTimer();\n\n console.log(`Cron: added job '${params.name}' (${job.id})`);\n return job;\n }\n\n removeJob(jobId: string): boolean {\n const store = this.loadStore();\n const before = store.jobs.length;\n store.jobs = store.jobs.filter((j) => j.id !== jobId);\n const removed = store.jobs.length < before;\n\n if (removed) {\n this.saveStore();\n this.armTimer();\n console.log(`Cron: removed job ${jobId}`);\n }\n return removed;\n }\n\n enableJob(jobId: string, enabled = true): CronJob | null {\n const store = this.loadStore();\n for (const job of store.jobs) {\n if (job.id === jobId) {\n job.enabled = enabled;\n job.updatedAtMs = nowMs();\n if (enabled) {\n job.state.nextRunAtMs = computeNextRun(job.schedule, nowMs());\n } else {\n job.state.nextRunAtMs = null;\n }\n this.saveStore();\n this.armTimer();\n return job;\n }\n }\n return null;\n }\n\n status(): { enabled: boolean; jobs: number; nextWakeAtMs: number | null } {\n const store = this.loadStore();\n return {\n enabled: this._running,\n jobs: store.jobs.length,\n nextWakeAtMs: this.getNextWakeMs(),\n };\n }\n}\n"],"mappings":"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"}
@@ -1,55 +0,0 @@
1
- //#region src/cron/types.d.ts
2
- /** Schedule definition for a cron job. */
3
- interface CronSchedule {
4
- kind: "at" | "every" | "cron";
5
- /** For "at": timestamp in ms */
6
- atMs?: number | null;
7
- /** For "every": interval in ms */
8
- everyMs?: number | null;
9
- /** For "cron": cron expression (e.g. "0 9 * * *") */
10
- expr?: string | null;
11
- /** Timezone for cron expressions */
12
- tz?: string | null;
13
- }
14
- /** What to do when the job runs. */
15
- interface CronPayload {
16
- kind: "system_event" | "agent_turn";
17
- message: string;
18
- /** Deliver response to channel */
19
- deliver: boolean;
20
- channel?: string | null;
21
- to?: string | null;
22
- }
23
- /** Runtime state of a job. */
24
- interface CronJobState {
25
- nextRunAtMs?: number | null;
26
- lastRunAtMs?: number | null;
27
- lastStatus?: "ok" | "error" | "skipped" | null;
28
- lastError?: string | null;
29
- }
30
- /** A scheduled job. */
31
- interface CronJob {
32
- id: string;
33
- name: string;
34
- enabled: boolean;
35
- schedule: CronSchedule;
36
- payload: CronPayload;
37
- state: CronJobState;
38
- createdAtMs: number;
39
- updatedAtMs: number;
40
- deleteAfterRun: boolean;
41
- }
42
- /** Persistent store for cron jobs. */
43
- interface CronStore {
44
- version: number;
45
- jobs: CronJob[];
46
- }
47
- /** Create a default CronPayload. */
48
- declare function createCronPayload(partial?: Partial<CronPayload>): CronPayload;
49
- /** Create a default CronJobState. */
50
- declare function createCronJobState(partial?: Partial<CronJobState>): CronJobState;
51
- /** Create a default CronStore. */
52
- declare function createCronStore(partial?: Partial<CronStore>): CronStore;
53
- //#endregion
54
- export { CronJob, CronJobState, CronPayload, CronSchedule, CronStore, createCronJobState, createCronPayload, createCronStore };
55
- //# sourceMappingURL=types.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.d.mts","names":[],"sources":["../../src/cron/types.ts"],"mappings":";;UACiB,YAAA;EACf,IAAA;;EAEA,IAAA;EAFA;EAIA,OAAA;EAAA;EAEA,IAAA;EAEA;EAAA,EAAA;AAAA;AAIF;AAAA,UAAiB,WAAA;EACf,IAAA;EACA,OAAA;EADA;EAGA,OAAA;EACA,OAAA;EACA,EAAA;AAAA;;UAIe,YAAA;EACf,WAAA;EACA,WAAA;EACA,UAAA;EACA,SAAA;AAAA;;UAIe,OAAA;EACf,EAAA;EACA,IAAA;EACA,OAAA;EACA,QAAA,EAAU,YAAA;EACV,OAAA,EAAS,WAAA;EACT,KAAA,EAAO,YAAA;EACP,WAAA;EACA,WAAA;EACA,cAAA;AAAA;;UAIe,SAAA;EACf,OAAA;EACA,IAAA,EAAM,OAAA;AAAA;;iBAIQ,iBAAA,CACd,OAAA,GAAU,OAAA,CAAQ,WAAA,IACjB,WAAA;;iBAUa,kBAAA,CACd,OAAA,GAAU,OAAA,CAAQ,YAAA,IACjB,YAAA;;iBAKa,eAAA,CAAgB,OAAA,GAAU,OAAA,CAAQ,SAAA,IAAa,SAAA"}
@@ -1,26 +0,0 @@
1
- //#region src/cron/types.ts
2
- /** Create a default CronPayload. */
3
- function createCronPayload(partial) {
4
- return {
5
- kind: "agent_turn",
6
- message: "",
7
- deliver: false,
8
- ...partial
9
- };
10
- }
11
- /** Create a default CronJobState. */
12
- function createCronJobState(partial) {
13
- return { ...partial };
14
- }
15
- /** Create a default CronStore. */
16
- function createCronStore(partial) {
17
- return {
18
- version: 1,
19
- jobs: [],
20
- ...partial
21
- };
22
- }
23
-
24
- //#endregion
25
- export { createCronJobState, createCronPayload, createCronStore };
26
- //# sourceMappingURL=types.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.mjs","names":[],"sources":["../../src/cron/types.ts"],"sourcesContent":["/** Schedule definition for a cron job. */\nexport interface CronSchedule {\n kind: \"at\" | \"every\" | \"cron\";\n /** For \"at\": timestamp in ms */\n atMs?: number | null;\n /** For \"every\": interval in ms */\n everyMs?: number | null;\n /** For \"cron\": cron expression (e.g. \"0 9 * * *\") */\n expr?: string | null;\n /** Timezone for cron expressions */\n tz?: string | null;\n}\n\n/** What to do when the job runs. */\nexport interface CronPayload {\n kind: \"system_event\" | \"agent_turn\";\n message: string;\n /** Deliver response to channel */\n deliver: boolean;\n channel?: string | null;\n to?: string | null;\n}\n\n/** Runtime state of a job. */\nexport interface CronJobState {\n nextRunAtMs?: number | null;\n lastRunAtMs?: number | null;\n lastStatus?: \"ok\" | \"error\" | \"skipped\" | null;\n lastError?: string | null;\n}\n\n/** A scheduled job. */\nexport interface CronJob {\n id: string;\n name: string;\n enabled: boolean;\n schedule: CronSchedule;\n payload: CronPayload;\n state: CronJobState;\n createdAtMs: number;\n updatedAtMs: number;\n deleteAfterRun: boolean;\n}\n\n/** Persistent store for cron jobs. */\nexport interface CronStore {\n version: number;\n jobs: CronJob[];\n}\n\n/** Create a default CronPayload. */\nexport function createCronPayload(\n partial?: Partial<CronPayload>,\n): CronPayload {\n return {\n kind: \"agent_turn\",\n message: \"\",\n deliver: false,\n ...partial,\n };\n}\n\n/** Create a default CronJobState. */\nexport function createCronJobState(\n partial?: Partial<CronJobState>,\n): CronJobState {\n return { ...partial };\n}\n\n/** Create a default CronStore. */\nexport function createCronStore(partial?: Partial<CronStore>): CronStore {\n return {\n version: 1,\n jobs: [],\n ...partial,\n };\n}\n"],"mappings":";;AAmDA,SAAgB,kBACd,SACa;AACb,QAAO;EACL,MAAM;EACN,SAAS;EACT,SAAS;EACT,GAAG;EACJ;;;AAIH,SAAgB,mBACd,SACc;AACd,QAAO,EAAE,GAAG,SAAS;;;AAIvB,SAAgB,gBAAgB,SAAyC;AACvE,QAAO;EACL,SAAS;EACT,MAAM,EAAE;EACR,GAAG;EACJ"}