@jcheesepkg/nanobot 0.6.0 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/loop.d.mts +0 -2
- package/dist/agent/loop.d.mts.map +1 -1
- package/dist/agent/loop.mjs +0 -17
- package/dist/agent/loop.mjs.map +1 -1
- package/dist/agent/tools/cron.d.mts +5 -0
- package/dist/agent/tools/cron.d.mts.map +1 -1
- package/dist/agent/tools/cron.mjs +9 -2
- package/dist/agent/tools/cron.mjs.map +1 -1
- package/dist/cli/index.mjs +1 -0
- package/dist/cli/index.mjs.map +1 -1
- package/dist/config/schema.d.mts +36 -36
- package/dist/gateway/server.d.mts +2 -0
- package/dist/gateway/server.d.mts.map +1 -1
- package/dist/gateway/server.mjs +12 -27
- package/dist/gateway/server.mjs.map +1 -1
- package/dist/index.d.mts +1 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/skills/cron/SKILL.md +8 -6
- package/dist/cron/service.d.mts +0 -42
- package/dist/cron/service.d.mts.map +0 -1
- package/dist/cron/service.mjs +0 -250
- package/dist/cron/service.mjs.map +0 -1
- package/dist/cron/types.d.mts +0 -55
- package/dist/cron/types.d.mts.map +0 -1
- package/dist/cron/types.mjs +0 -26
- package/dist/cron/types.mjs.map +0 -1
package/dist/agent/loop.d.mts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/agent/loop.mjs
CHANGED
|
@@ -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({
|
package/dist/agent/loop.mjs.map
CHANGED
|
@@ -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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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cron.d.mts","names":[],"sources":["../../../src/agent/tools/cron.ts"],"mappings":";;;;cAGa,QAAA,SAAiB,IAAA;EAAA,SACnB,IAAA;EAAA,SACA,WAAA;EAAA,SAEA,UAAA
|
|
1
|
+
{"version":3,"file":"cron.d.mts","names":[],"sources":["../../../src/agent/tools/cron.ts"],"mappings":";;;;cAGa,QAAA,SAAiB,IAAA;EAAA,SACnB,IAAA;EAAA,SACA,WAAA;EAAA,SAEA,UAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAuCD,OAAA;EAAA,QACA,MAAA;EAGA;EAAA,QAAA,SAAA;;UAEA,MAAA;;EASoB;EAA5B,UAAA,CAAW,OAAA,UAAiB,MAAA;EAKtB,OAAA,CAAQ,IAAA,EAAM,MAAA,oBAA0B,OAAA;EAAA,QAkBhC,MAAA;EAAA,QAwDA,QAAA;EAAA,QAyBA,SAAA;AAAA"}
|
|
@@ -19,7 +19,12 @@ var CronTool = class extends Tool {
|
|
|
19
19
|
},
|
|
20
20
|
message: {
|
|
21
21
|
type: "string",
|
|
22
|
-
description: "Reminder message (for add)"
|
|
22
|
+
description: "Reminder message or task prompt (for add)"
|
|
23
|
+
},
|
|
24
|
+
kind: {
|
|
25
|
+
type: "string",
|
|
26
|
+
enum: ["direct", "task"],
|
|
27
|
+
description: "direct: send message verbatim to user (default, for reminders). task: run message through the agent and deliver result."
|
|
23
28
|
},
|
|
24
29
|
every_seconds: {
|
|
25
30
|
type: "integer",
|
|
@@ -68,6 +73,7 @@ var CronTool = class extends Tool {
|
|
|
68
73
|
}
|
|
69
74
|
async addJob(args) {
|
|
70
75
|
const message = args.message ? String(args.message) : "";
|
|
76
|
+
const kind = args.kind === "task" ? "task" : "direct";
|
|
71
77
|
const everySeconds = args.every_seconds ? Number(args.every_seconds) : null;
|
|
72
78
|
const cronExpr = args.cron_expr ? String(args.cron_expr) : null;
|
|
73
79
|
const atRaw = args.at ? String(args.at) : null;
|
|
@@ -91,6 +97,7 @@ var CronTool = class extends Tool {
|
|
|
91
97
|
type: everySeconds ? "every" : cronExpr ? "cron" : "scheduled",
|
|
92
98
|
schedule,
|
|
93
99
|
message,
|
|
100
|
+
kind,
|
|
94
101
|
deliver: true,
|
|
95
102
|
channel: this.channel,
|
|
96
103
|
chatId: this.chatId
|
|
@@ -99,7 +106,7 @@ var CronTool = class extends Tool {
|
|
|
99
106
|
});
|
|
100
107
|
const data = await res.json();
|
|
101
108
|
if (!res.ok) return `Error: ${data.error ?? res.statusText}`;
|
|
102
|
-
return `Created job (id: ${data.id})`;
|
|
109
|
+
return `Created ${kind} job (id: ${data.id})`;
|
|
103
110
|
} catch (err) {
|
|
104
111
|
return `Error scheduling job: ${err instanceof Error ? err.message : err}`;
|
|
105
112
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cron.mjs","names":[],"sources":["../../../src/agent/tools/cron.ts"],"sourcesContent":["import { Tool } from \"./base.js\";\n\n/** Tool to schedule reminders and recurring tasks via the DO scheduler. */\nexport class CronTool extends Tool {\n readonly name = \"cron\";\n readonly description =\n \"Schedule one-off or recurring tasks. Actions: add, list, remove.\";\n readonly parameters = {\n type: \"object\",\n properties: {\n action: {\n type: \"string\",\n enum: [\"add\", \"list\", \"remove\"],\n description: \"Action to perform\",\n },\n message: {\n type: \"string\",\n description: \"Reminder message (for add)\",\n },\n every_seconds: {\n type: \"integer\",\n description: \"Interval in seconds (for recurring tasks)\",\n },\n cron_expr: {\n type: \"string\",\n description: \"Cron expression like '0 9 * * *' (for scheduled tasks)\",\n },\n at: {\n type: \"string\",\n description:\n \"Run once at a specific time. ISO 8601 string (e.g. '2025-03-01T09:00:00Z')\",\n },\n job_id: {\n type: \"string\",\n description: \"Job ID (for remove)\",\n },\n },\n required: [\"action\"],\n };\n\n private channel = \"\";\n private chatId = \"\";\n\n /** Worker URL for DO scheduling. */\n private workerUrl: string;\n /** User ID for DO scheduling. */\n private userId: string;\n\n constructor() {\n super();\n this.workerUrl = process.env.WORKER_URL ?? \"\";\n this.userId = process.env.NANOBOT_USER_ID ?? \"\";\n }\n\n /** Set the current session context for delivery. */\n setContext(channel: string, chatId: string): void {\n this.channel = channel;\n this.chatId = chatId;\n }\n\n async execute(args: Record<string, unknown>): Promise<string> {\n if (!this.workerUrl || !this.userId) {\n return \"Error: cron scheduling requires WORKER_URL and NANOBOT_USER_ID\";\n }\n\n const action = String(args.action);\n switch (action) {\n case \"add\":\n return this.addJob(args);\n case \"list\":\n return this.listJobs();\n case \"remove\":\n return this.removeJob(args);\n default:\n return `Unknown action: ${action}`;\n }\n }\n\n private async addJob(args: Record<string, unknown>): Promise<string> {\n const message = args.message ? String(args.message) : \"\";\n const everySeconds = args.every_seconds\n ? Number(args.every_seconds)\n : null;\n const cronExpr = args.cron_expr ? String(args.cron_expr) : null;\n const atRaw = args.at ? String(args.at) : null;\n\n if (!message) return \"Error: message is required for add\";\n if (!this.channel || !this.chatId)\n return \"Error: no session context (channel/chatId)\";\n\n let schedule: string | number;\n if (atRaw) {\n const atMs = Date.parse(atRaw);\n if (isNaN(atMs)) return `Error: could not parse time '${atRaw}' — use ISO 8601 format`;\n if (atMs <= Date.now()) return \"Error: scheduled time is in the past\";\n\n schedule = atRaw;\n } else if (everySeconds) {\n // For recurring by interval, use seconds\n schedule = everySeconds;\n } else if (cronExpr) {\n schedule = cronExpr;\n } else {\n return \"Error: one of 'at', 'every_seconds', or 'cron_expr' is required\";\n }\n\n try {\n const res = await fetch(\n `${this.workerUrl}/api/users/${this.userId}/cron`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n name: message,\n type: everySeconds ? \"every\" : cronExpr ? \"cron\" : \"scheduled\",\n schedule,\n message,\n deliver: true,\n channel: this.channel,\n chatId: this.chatId,\n }),\n signal: AbortSignal.timeout(10_000),\n },\n );\n const data = await res.json() as { id?: string; error?: string };\n if (!res.ok) return `Error: ${data.error ?? res.statusText}`;\n return `Created job (id: ${data.id})`;\n } catch (err) {\n return `Error scheduling job: ${err instanceof Error ? err.message : err}`;\n }\n }\n\n private async listJobs(): Promise<string> {\n try {\n const res = await fetch(\n `${this.workerUrl}/api/users/${this.userId}/cron`,\n { signal: AbortSignal.timeout(10_000) },\n );\n const data = await res.json() as {\n crons: Array<{\n id: string;\n type: string;\n name: string;\n message: string;\n nextRunAt: number;\n }>;\n };\n if (data.crons.length === 0) return \"No scheduled jobs.\";\n const lines = data.crons.map(\n (c) => `- ${c.name} (id: ${c.id}, ${c.type}, next: ${new Date(c.nextRunAt).toISOString()})`,\n );\n return \"Scheduled jobs:\\n\" + lines.join(\"\\n\");\n } catch (err) {\n return `Error listing jobs: ${err instanceof Error ? err.message : err}`;\n }\n }\n\n private async removeJob(args: Record<string, unknown>): Promise<string> {\n const jobId = args.job_id ? String(args.job_id) : null;\n if (!jobId) return \"Error: job_id is required for remove\";\n\n try {\n const res = await fetch(\n `${this.workerUrl}/api/users/${this.userId}/cron/${jobId}`,\n {\n method: \"DELETE\",\n signal: AbortSignal.timeout(10_000),\n },\n );\n const data = await res.json() as { ok: boolean };\n return data.ok ? `Removed job ${jobId}` : `Job ${jobId} not found`;\n } catch (err) {\n return `Error removing job: ${err instanceof Error ? err.message : err}`;\n }\n }\n}\n"],"mappings":";;;;AAGA,IAAa,WAAb,cAA8B,KAAK;CACjC,AAAS,OAAO;CAChB,AAAS,cACP;CACF,AAAS,aAAa;EACpB,MAAM;EACN,YAAY;GACV,QAAQ;IACN,MAAM;IACN,MAAM;KAAC;KAAO;KAAQ;KAAS;IAC/B,aAAa;IACd;GACD,SAAS;IACP,MAAM;IACN,aAAa;IACd;GACD,eAAe;IACb,MAAM;IACN,aAAa;IACd;GACD,WAAW;IACT,MAAM;IACN,aAAa;IACd;GACD,IAAI;IACF,MAAM;IACN,aACE;IACH;GACD,QAAQ;IACN,MAAM;IACN,aAAa;IACd;GACF;EACD,UAAU,CAAC,SAAS;EACrB;CAED,AAAQ,UAAU;CAClB,AAAQ,SAAS;;CAGjB,AAAQ;;CAER,AAAQ;CAER,cAAc;AACZ,SAAO;AACP,OAAK,YAAY,QAAQ,IAAI,cAAc;AAC3C,OAAK,SAAS,QAAQ,IAAI,mBAAmB;;;CAI/C,WAAW,SAAiB,QAAsB;AAChD,OAAK,UAAU;AACf,OAAK,SAAS;;CAGhB,MAAM,QAAQ,MAAgD;AAC5D,MAAI,CAAC,KAAK,aAAa,CAAC,KAAK,OAC3B,QAAO;EAGT,MAAM,SAAS,OAAO,KAAK,OAAO;AAClC,UAAQ,QAAR;GACE,KAAK,MACH,QAAO,KAAK,OAAO,KAAK;GAC1B,KAAK,OACH,QAAO,KAAK,UAAU;GACxB,KAAK,SACH,QAAO,KAAK,UAAU,KAAK;GAC7B,QACE,QAAO,mBAAmB;;;CAIhC,MAAc,OAAO,MAAgD;EACnE,MAAM,UAAU,KAAK,UAAU,OAAO,KAAK,QAAQ,GAAG;EACtD,MAAM,eAAe,KAAK,gBACtB,OAAO,KAAK,cAAc,GAC1B;EACJ,MAAM,WAAW,KAAK,YAAY,OAAO,KAAK,UAAU,GAAG;EAC3D,MAAM,QAAQ,KAAK,KAAK,OAAO,KAAK,GAAG,GAAG;AAE1C,MAAI,CAAC,QAAS,QAAO;AACrB,MAAI,CAAC,KAAK,WAAW,CAAC,KAAK,OACzB,QAAO;EAET,IAAI;AACJ,MAAI,OAAO;GACT,MAAM,OAAO,KAAK,MAAM,MAAM;AAC9B,OAAI,MAAM,KAAK,CAAE,QAAO,gCAAgC,MAAM;AAC9D,OAAI,QAAQ,KAAK,KAAK,CAAE,QAAO;AAE/B,cAAW;aACF,aAET,YAAW;WACF,SACT,YAAW;MAEX,QAAO;AAGT,MAAI;GACF,MAAM,MAAM,MAAM,MAChB,GAAG,KAAK,UAAU,aAAa,KAAK,OAAO,QAC3C;IACE,QAAQ;IACR,SAAS,EAAE,gBAAgB,oBAAoB;IAC/C,MAAM,KAAK,UAAU;KACnB,MAAM;KACN,MAAM,eAAe,UAAU,WAAW,SAAS;KACnD;KACA;KACA,SAAS;KACT,SAAS,KAAK;KACd,QAAQ,KAAK;KACd,CAAC;IACF,QAAQ,YAAY,QAAQ,IAAO;IACpC,CACF;GACD,MAAM,OAAO,MAAM,IAAI,MAAM;AAC7B,OAAI,CAAC,IAAI,GAAI,QAAO,UAAU,KAAK,SAAS,IAAI;AAChD,UAAO,
|
|
1
|
+
{"version":3,"file":"cron.mjs","names":[],"sources":["../../../src/agent/tools/cron.ts"],"sourcesContent":["import { Tool } from \"./base.js\";\n\n/** Tool to schedule reminders and recurring tasks via the DO scheduler. */\nexport class CronTool extends Tool {\n readonly name = \"cron\";\n readonly description =\n \"Schedule one-off or recurring tasks. Actions: add, list, remove.\";\n readonly parameters = {\n type: \"object\",\n properties: {\n action: {\n type: \"string\",\n enum: [\"add\", \"list\", \"remove\"],\n description: \"Action to perform\",\n },\n message: {\n type: \"string\",\n description: \"Reminder message or task prompt (for add)\",\n },\n kind: {\n type: \"string\",\n enum: [\"direct\", \"task\"],\n description:\n \"direct: send message verbatim to user (default, for reminders). task: run message through the agent and deliver result.\",\n },\n every_seconds: {\n type: \"integer\",\n description: \"Interval in seconds (for recurring tasks)\",\n },\n cron_expr: {\n type: \"string\",\n description: \"Cron expression like '0 9 * * *' (for scheduled tasks)\",\n },\n at: {\n type: \"string\",\n description:\n \"Run once at a specific time. ISO 8601 string (e.g. '2025-03-01T09:00:00Z')\",\n },\n job_id: {\n type: \"string\",\n description: \"Job ID (for remove)\",\n },\n },\n required: [\"action\"],\n };\n\n private channel = \"\";\n private chatId = \"\";\n\n /** Worker URL for DO scheduling. */\n private workerUrl: string;\n /** User ID for DO scheduling. */\n private userId: string;\n\n constructor() {\n super();\n this.workerUrl = process.env.WORKER_URL ?? \"\";\n this.userId = process.env.NANOBOT_USER_ID ?? \"\";\n }\n\n /** Set the current session context for delivery. */\n setContext(channel: string, chatId: string): void {\n this.channel = channel;\n this.chatId = chatId;\n }\n\n async execute(args: Record<string, unknown>): Promise<string> {\n if (!this.workerUrl || !this.userId) {\n return \"Error: cron scheduling requires WORKER_URL and NANOBOT_USER_ID\";\n }\n\n const action = String(args.action);\n switch (action) {\n case \"add\":\n return this.addJob(args);\n case \"list\":\n return this.listJobs();\n case \"remove\":\n return this.removeJob(args);\n default:\n return `Unknown action: ${action}`;\n }\n }\n\n private async addJob(args: Record<string, unknown>): Promise<string> {\n const message = args.message ? String(args.message) : \"\";\n const kind = args.kind === \"task\" ? \"task\" : \"direct\"; // default: direct\n const everySeconds = args.every_seconds\n ? Number(args.every_seconds)\n : null;\n const cronExpr = args.cron_expr ? String(args.cron_expr) : null;\n const atRaw = args.at ? String(args.at) : null;\n\n if (!message) return \"Error: message is required for add\";\n if (!this.channel || !this.chatId)\n return \"Error: no session context (channel/chatId)\";\n\n let schedule: string | number;\n if (atRaw) {\n const atMs = Date.parse(atRaw);\n if (isNaN(atMs)) return `Error: could not parse time '${atRaw}' — use ISO 8601 format`;\n if (atMs <= Date.now()) return \"Error: scheduled time is in the past\";\n\n schedule = atRaw;\n } else if (everySeconds) {\n // For recurring by interval, use seconds\n schedule = everySeconds;\n } else if (cronExpr) {\n schedule = cronExpr;\n } else {\n return \"Error: one of 'at', 'every_seconds', or 'cron_expr' is required\";\n }\n\n try {\n const res = await fetch(\n `${this.workerUrl}/api/users/${this.userId}/cron`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n name: message,\n type: everySeconds ? \"every\" : cronExpr ? \"cron\" : \"scheduled\",\n schedule,\n message,\n kind,\n deliver: true,\n channel: this.channel,\n chatId: this.chatId,\n }),\n signal: AbortSignal.timeout(10_000),\n },\n );\n const data = await res.json() as { id?: string; error?: string };\n if (!res.ok) return `Error: ${data.error ?? res.statusText}`;\n return `Created ${kind} job (id: ${data.id})`;\n } catch (err) {\n return `Error scheduling job: ${err instanceof Error ? err.message : err}`;\n }\n }\n\n private async listJobs(): Promise<string> {\n try {\n const res = await fetch(\n `${this.workerUrl}/api/users/${this.userId}/cron`,\n { signal: AbortSignal.timeout(10_000) },\n );\n const data = await res.json() as {\n crons: Array<{\n id: string;\n type: string;\n name: string;\n message: string;\n nextRunAt: number;\n }>;\n };\n if (data.crons.length === 0) return \"No scheduled jobs.\";\n const lines = data.crons.map(\n (c) => `- ${c.name} (id: ${c.id}, ${c.type}, next: ${new Date(c.nextRunAt).toISOString()})`,\n );\n return \"Scheduled jobs:\\n\" + lines.join(\"\\n\");\n } catch (err) {\n return `Error listing jobs: ${err instanceof Error ? err.message : err}`;\n }\n }\n\n private async removeJob(args: Record<string, unknown>): Promise<string> {\n const jobId = args.job_id ? String(args.job_id) : null;\n if (!jobId) return \"Error: job_id is required for remove\";\n\n try {\n const res = await fetch(\n `${this.workerUrl}/api/users/${this.userId}/cron/${jobId}`,\n {\n method: \"DELETE\",\n signal: AbortSignal.timeout(10_000),\n },\n );\n const data = await res.json() as { ok: boolean };\n return data.ok ? `Removed job ${jobId}` : `Job ${jobId} not found`;\n } catch (err) {\n return `Error removing job: ${err instanceof Error ? err.message : err}`;\n }\n }\n}\n"],"mappings":"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"}
|
package/dist/cli/index.mjs
CHANGED
|
@@ -190,6 +190,7 @@ program.command("gateway").description("Start the nanobot gateway").option("-p,
|
|
|
190
190
|
const { createGatewayServer } = await import("../gateway/server.mjs");
|
|
191
191
|
createGatewayServer({
|
|
192
192
|
agent,
|
|
193
|
+
bus,
|
|
193
194
|
port: Number(opts.port),
|
|
194
195
|
channels,
|
|
195
196
|
heartbeat
|
package/dist/cli/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * nanobot CLI - Personal AI Assistant\n */\n\nimport { Command } from \"commander\";\nimport { existsSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { join, resolve, isAbsolute } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { VERSION, LOGO } from \"../index.js\";\nimport { loadConfig, saveConfig } from \"../config/loader.js\";\nimport { getConfigPath } from \"../config/loader.js\";\nimport {\n ConfigSchema,\n getConfigWorkspacePath,\n getApiKey,\n getApiBase,\n getExtraHeaders,\n} from \"../config/schema.js\";\nimport type { Config } from \"../config/schema.js\";\nimport { PROVIDERS } from \"../providers/registry.js\";\nimport type { Tool } from \"../agent/tools/base.js\";\n\nconst program = new Command();\n\nasync function loadCustomTools(config: Config): Promise<Tool[]> {\n const customConfigs = config.tools.custom ?? [];\n if (customConfigs.length === 0) return [];\n\n const tools: Tool[] = [];\n for (const entry of customConfigs) {\n try {\n const modulePath = isAbsolute(entry.module)\n ? entry.module\n : resolve(process.cwd(), entry.module);\n const mod = await import(pathToFileURL(modulePath).href);\n const exportName = entry.export ?? \"default\";\n const exported = exportName === \"default\" ? mod.default : mod[exportName];\n\n if (!exported) {\n throw new Error(`Export '${exportName}' not found`);\n }\n\n let instance: unknown;\n if (typeof exported === \"function\") {\n try {\n instance = new exported(entry.options ?? {});\n } catch {\n instance = exported(entry.options ?? {});\n }\n } else {\n throw new Error(`Export '${exportName}' is not a function`);\n }\n\n if (\n instance &&\n typeof instance === \"object\" &&\n \"execute\" in instance &&\n \"name\" in instance\n ) {\n tools.push(instance as Tool);\n } else {\n throw new Error(`Export '${exportName}' did not return a Tool instance`);\n }\n } catch (err) {\n console.warn(\n `Warning: Failed to load custom tool '${entry.module}': ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n }\n\n return tools;\n}\n\nprogram\n .name(\"nanobot\")\n .description(`${LOGO} nanobot - Personal AI Assistant`)\n .version(`${LOGO} nanobot v${VERSION}`, \"-v, --version\");\n\n// ============================================================================\n// Onboard / Setup\n// ============================================================================\n\nprogram\n .command(\"onboard\")\n .description(\"Initialize nanobot configuration and workspace\")\n .action(() => {\n const configPath = getConfigPath();\n\n if (existsSync(configPath)) {\n console.log(`Config already exists at ${configPath}`);\n console.log(\"Delete it first if you want to start fresh.\");\n return;\n }\n\n // Create default config\n const config = ConfigSchema.parse({});\n saveConfig(config);\n console.log(`Created config at ${configPath}`);\n\n // Create workspace\n const workspace = getConfigWorkspacePath(config);\n createWorkspaceTemplates(workspace);\n\n console.log(`\\n${LOGO} nanobot is ready!`);\n console.log(\"\\nNext steps:\");\n console.log(\" 1. Add your API key to ~/.nanobot/config.json\");\n console.log(\" Get one at: https://openrouter.ai/keys\");\n console.log(' 2. Chat: nanobot agent -m \"Hello!\"');\n console.log(\n \"\\nWant Telegram? See: https://github.com/HKUDS/nanobot#-chat-apps\",\n );\n });\n\nfunction createWorkspaceTemplates(workspace: string): void {\n if (!existsSync(workspace)) {\n mkdirSync(workspace, { recursive: true });\n }\n\n const templates: Record<string, string> = {\n \"AGENTS.md\": `# Agent Instructions\n\nYou are a helpful AI assistant. Be concise, accurate, and friendly.\n\n## Guidelines\n\n- Always explain what you're doing before taking actions\n- Ask for clarification when the request is ambiguous\n- Use tools to help accomplish tasks\n- Remember important information in your memory files\n`,\n \"SOUL.md\": `# Soul\n\nI am nanobot, a lightweight AI assistant.\n\n## Personality\n\n- Helpful and friendly\n- Concise and to the point\n- Curious and eager to learn\n\n## Values\n\n- Accuracy over speed\n- User privacy and safety\n- Transparency in actions\n`,\n \"USER.md\": `# User\n\nInformation about the user goes here.\n\n## Preferences\n\n- Communication style: (casual/formal)\n- Timezone: (your timezone)\n- Language: (your preferred language)\n`,\n \"HEARTBEAT.md\": `# Heartbeat\n\nThis file is checked every 30 minutes. Add tasks or instructions below and the agent will act on them automatically.\n\n## Tasks\n\n`,\n };\n\n for (const [filename, content] of Object.entries(templates)) {\n const filePath = join(workspace, filename);\n if (!existsSync(filePath)) {\n writeFileSync(filePath, content);\n console.log(` Created ${filename}`);\n }\n }\n\n // Create memory directory and MEMORY.md\n const memoryDir = join(workspace, \"memory\");\n if (!existsSync(memoryDir)) {\n mkdirSync(memoryDir, { recursive: true });\n }\n const memoryFile = join(memoryDir, \"MEMORY.md\");\n if (!existsSync(memoryFile)) {\n writeFileSync(\n memoryFile,\n `# Long-term Memory\n\nThis file stores important information that should persist across sessions.\n\n## User Information\n\n(Important facts about the user)\n\n## Preferences\n\n(User preferences learned over time)\n\n## Important Notes\n\n(Things to remember)\n`,\n );\n console.log(\" Created memory/MEMORY.md\");\n }\n}\n\n// ============================================================================\n// Gateway / Server\n// ============================================================================\n\nprogram\n .command(\"gateway\")\n .description(\"Start the nanobot gateway\")\n .option(\"-p, --port <number>\", \"Gateway port\", \"18790\")\n .option(\"--verbose\", \"Verbose output\", false)\n .action(async (opts) => {\n console.log(\n `${LOGO} Starting nanobot gateway on port ${opts.port}...`,\n );\n\n const config = loadConfig();\n const model = config.agents.defaults.model;\n const apiKey = getApiKey(config, model);\n const apiBase = getApiBase(config, model);\n const extraHeaders = getExtraHeaders(config, model);\n\n if (!apiKey) {\n console.error(\"Error: No API key configured.\");\n console.error(\n \"Set one in ~/.nanobot/config.json under providers.<provider>.apiKey\",\n );\n process.exit(1);\n }\n\n // Dynamic imports to avoid loading heavy deps up front\n const { MessageBus } = await import(\"../bus/queue.js\");\n const { OpenAIProvider } = await import(\n \"../providers/openai-provider.js\"\n );\n const { AgentLoop } = await import(\"../agent/loop.js\");\n const { ChannelManager } = await import(\"../channels/manager.js\");\n\n const bus = new MessageBus();\n const workspace = getConfigWorkspacePath(config);\n\n const provider = new OpenAIProvider({\n apiKey,\n apiBase: apiBase ?? undefined,\n defaultModel: model,\n extraHeaders,\n });\n\n const customTools = await loadCustomTools(config);\n\n // Create agent\n const agent = new AgentLoop({\n bus,\n provider,\n workspace,\n model,\n maxTokens: config.agents.defaults.maxTokens,\n maxIterations: config.agents.defaults.maxToolIterations,\n braveApiKey: config.tools.web.search.apiKey || undefined,\n execConfig: config.tools.exec,\n restrictToWorkspace: config.tools.restrictToWorkspace,\n toolsEnabled: config.tools.enabled,\n toolsDisabled: config.tools.disabled,\n customTools,\n });\n\n // Create channel manager\n const channels = new ChannelManager(config, bus);\n\n // Heartbeat service — no timer (DO drives the schedule),\n // but provides smart file-reading logic for /api/heartbeat\n const { HeartbeatService } = await import(\"../heartbeat/service.js\");\n const heartbeat = new HeartbeatService({\n workspace,\n onHeartbeat: (prompt) => agent.processDirect(prompt, \"heartbeat\"),\n });\n\n console.log(\"Heartbeat: managed by DO (local endpoint /api/heartbeat)\");\n console.log(\"Cron: managed by DO\");\n\n // Handle graceful shutdown\n const shutdown = async () => {\n console.log(\"\\nShutting down...\");\n agent.stop();\n await channels.stopAll();\n process.exit(0);\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n\n // Start HTTP gateway server\n const { createGatewayServer } = await import(\n \"../gateway/server.js\"\n );\n createGatewayServer({\n agent,\n port: Number(opts.port),\n channels,\n heartbeat,\n });\n\n try {\n // Initialise channels first (non-blocking) so enabledChannels is populated\n await channels.init();\n // Both agent.run() and channels.startAll() block until stopped\n await Promise.all([agent.run(), channels.startAll()]);\n } catch (err) {\n console.error(\"Gateway error:\", err);\n process.exit(1);\n }\n });\n\n// ============================================================================\n// Agent Commands\n// ============================================================================\n\nprogram\n .command(\"agent\")\n .description(\"Interact with the agent directly\")\n .option(\"-m, --message <text>\", \"Message to send to the agent\")\n .option(\n \"-s, --session <id>\",\n \"Session ID\",\n \"cli:default\",\n )\n .action(async (opts) => {\n const config = loadConfig();\n const model = config.agents.defaults.model;\n const apiKey = getApiKey(config, model);\n const apiBase = getApiBase(config, model);\n const extraHeaders = getExtraHeaders(config, model);\n\n if (!apiKey) {\n console.error(\"Error: No API key configured.\");\n process.exit(1);\n }\n\n const { MessageBus } = await import(\"../bus/queue.js\");\n const { OpenAIProvider } = await import(\n \"../providers/openai-provider.js\"\n );\n const { AgentLoop } = await import(\"../agent/loop.js\");\n\n const bus = new MessageBus();\n const workspace = getConfigWorkspacePath(config);\n\n const provider = new OpenAIProvider({\n apiKey,\n apiBase: apiBase ?? undefined,\n defaultModel: model,\n extraHeaders,\n });\n\n const customTools = await loadCustomTools(config);\n\n const agentLoop = new AgentLoop({\n bus,\n provider,\n workspace,\n maxTokens: config.agents.defaults.maxTokens,\n braveApiKey: config.tools.web.search.apiKey || undefined,\n execConfig: config.tools.exec,\n restrictToWorkspace: config.tools.restrictToWorkspace,\n toolsEnabled: config.tools.enabled,\n toolsDisabled: config.tools.disabled,\n customTools,\n });\n\n if (opts.message) {\n // Single message mode\n const response = await agentLoop.processDirect(\n opts.message,\n opts.session,\n );\n console.log(`\\n${LOGO} ${response}`);\n } else {\n // Interactive mode\n console.log(`${LOGO} Interactive mode (Ctrl+C to exit)\\n`);\n\n const readline = await import(\"node:readline\");\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n const ask = (): void => {\n rl.question(\"You: \", async (input) => {\n const trimmed = input.trim();\n if (!trimmed) {\n ask();\n return;\n }\n\n try {\n const response = await agentLoop.processDirect(\n trimmed,\n opts.session,\n );\n console.log(`\\n${LOGO} ${response}\\n`);\n } catch (err) {\n console.error(\"Error:\", err);\n }\n ask();\n });\n };\n\n rl.on(\"close\", () => {\n console.log(\"\\nGoodbye!\");\n process.exit(0);\n });\n\n ask();\n }\n });\n\n// ============================================================================\n// Channel Commands\n// ============================================================================\n\nconst channelsCmd = program\n .command(\"channels\")\n .description(\"Manage channels\");\n\nchannelsCmd\n .command(\"status\")\n .description(\"Show channel status\")\n .action(() => {\n const config = loadConfig();\n\n console.log(\"Channel Status\");\n console.log(\"─\".repeat(50));\n\n const tg = config.channels.telegram;\n const tgToken = tg.token\n ? `token: ${tg.token.slice(0, 10)}...`\n : \"not configured\";\n console.log(\n ` Telegram ${tg.enabled ? \"[enabled]\" : \"[disabled]\"} ${tgToken}`,\n );\n\n const ln = config.channels.line;\n const lnToken = ln.channelAccessToken\n ? `token: ${ln.channelAccessToken.slice(0, 10)}...`\n : \"not configured\";\n console.log(\n ` LINE ${ln.enabled ? \"[enabled]\" : \"[disabled]\"} ${lnToken}`,\n );\n });\n\n// ============================================================================\n// Status\n// ============================================================================\n\nprogram\n .command(\"status\")\n .description(\"Show nanobot status\")\n .action(() => {\n const configPath = getConfigPath();\n const config = loadConfig();\n const workspace = getConfigWorkspacePath(config);\n\n console.log(`${LOGO} nanobot Status\\n`);\n\n console.log(\n `Config: ${configPath} ${existsSync(configPath) ? \"[ok]\" : \"[missing]\"}`,\n );\n console.log(\n `Workspace: ${workspace} ${existsSync(workspace) ? \"[ok]\" : \"[missing]\"}`,\n );\n\n if (existsSync(configPath)) {\n console.log(`Model: ${config.agents.defaults.model}`);\n console.log(\"\");\n console.log(\"Providers\");\n console.log(\"─\".repeat(50));\n\n const providers = config.providers as Record<string, { apiKey: string }>;\n for (const spec of PROVIDERS) {\n const p = providers[spec.name];\n if (!p) continue;\n const status = p.apiKey ? \"[set]\" : \"[not set]\";\n console.log(` ${spec.displayName.padEnd(16)} ${status}`);\n }\n }\n });\n\nprogram.parse();\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * nanobot CLI - Personal AI Assistant\n */\n\nimport { Command } from \"commander\";\nimport { existsSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { join, resolve, isAbsolute } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { VERSION, LOGO } from \"../index.js\";\nimport { loadConfig, saveConfig } from \"../config/loader.js\";\nimport { getConfigPath } from \"../config/loader.js\";\nimport {\n ConfigSchema,\n getConfigWorkspacePath,\n getApiKey,\n getApiBase,\n getExtraHeaders,\n} from \"../config/schema.js\";\nimport type { Config } from \"../config/schema.js\";\nimport { PROVIDERS } from \"../providers/registry.js\";\nimport type { Tool } from \"../agent/tools/base.js\";\n\nconst program = new Command();\n\nasync function loadCustomTools(config: Config): Promise<Tool[]> {\n const customConfigs = config.tools.custom ?? [];\n if (customConfigs.length === 0) return [];\n\n const tools: Tool[] = [];\n for (const entry of customConfigs) {\n try {\n const modulePath = isAbsolute(entry.module)\n ? entry.module\n : resolve(process.cwd(), entry.module);\n const mod = await import(pathToFileURL(modulePath).href);\n const exportName = entry.export ?? \"default\";\n const exported = exportName === \"default\" ? mod.default : mod[exportName];\n\n if (!exported) {\n throw new Error(`Export '${exportName}' not found`);\n }\n\n let instance: unknown;\n if (typeof exported === \"function\") {\n try {\n instance = new exported(entry.options ?? {});\n } catch {\n instance = exported(entry.options ?? {});\n }\n } else {\n throw new Error(`Export '${exportName}' is not a function`);\n }\n\n if (\n instance &&\n typeof instance === \"object\" &&\n \"execute\" in instance &&\n \"name\" in instance\n ) {\n tools.push(instance as Tool);\n } else {\n throw new Error(`Export '${exportName}' did not return a Tool instance`);\n }\n } catch (err) {\n console.warn(\n `Warning: Failed to load custom tool '${entry.module}': ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n }\n\n return tools;\n}\n\nprogram\n .name(\"nanobot\")\n .description(`${LOGO} nanobot - Personal AI Assistant`)\n .version(`${LOGO} nanobot v${VERSION}`, \"-v, --version\");\n\n// ============================================================================\n// Onboard / Setup\n// ============================================================================\n\nprogram\n .command(\"onboard\")\n .description(\"Initialize nanobot configuration and workspace\")\n .action(() => {\n const configPath = getConfigPath();\n\n if (existsSync(configPath)) {\n console.log(`Config already exists at ${configPath}`);\n console.log(\"Delete it first if you want to start fresh.\");\n return;\n }\n\n // Create default config\n const config = ConfigSchema.parse({});\n saveConfig(config);\n console.log(`Created config at ${configPath}`);\n\n // Create workspace\n const workspace = getConfigWorkspacePath(config);\n createWorkspaceTemplates(workspace);\n\n console.log(`\\n${LOGO} nanobot is ready!`);\n console.log(\"\\nNext steps:\");\n console.log(\" 1. Add your API key to ~/.nanobot/config.json\");\n console.log(\" Get one at: https://openrouter.ai/keys\");\n console.log(' 2. Chat: nanobot agent -m \"Hello!\"');\n console.log(\n \"\\nWant Telegram? See: https://github.com/HKUDS/nanobot#-chat-apps\",\n );\n });\n\nfunction createWorkspaceTemplates(workspace: string): void {\n if (!existsSync(workspace)) {\n mkdirSync(workspace, { recursive: true });\n }\n\n const templates: Record<string, string> = {\n \"AGENTS.md\": `# Agent Instructions\n\nYou are a helpful AI assistant. Be concise, accurate, and friendly.\n\n## Guidelines\n\n- Always explain what you're doing before taking actions\n- Ask for clarification when the request is ambiguous\n- Use tools to help accomplish tasks\n- Remember important information in your memory files\n`,\n \"SOUL.md\": `# Soul\n\nI am nanobot, a lightweight AI assistant.\n\n## Personality\n\n- Helpful and friendly\n- Concise and to the point\n- Curious and eager to learn\n\n## Values\n\n- Accuracy over speed\n- User privacy and safety\n- Transparency in actions\n`,\n \"USER.md\": `# User\n\nInformation about the user goes here.\n\n## Preferences\n\n- Communication style: (casual/formal)\n- Timezone: (your timezone)\n- Language: (your preferred language)\n`,\n \"HEARTBEAT.md\": `# Heartbeat\n\nThis file is checked every 30 minutes. Add tasks or instructions below and the agent will act on them automatically.\n\n## Tasks\n\n`,\n };\n\n for (const [filename, content] of Object.entries(templates)) {\n const filePath = join(workspace, filename);\n if (!existsSync(filePath)) {\n writeFileSync(filePath, content);\n console.log(` Created ${filename}`);\n }\n }\n\n // Create memory directory and MEMORY.md\n const memoryDir = join(workspace, \"memory\");\n if (!existsSync(memoryDir)) {\n mkdirSync(memoryDir, { recursive: true });\n }\n const memoryFile = join(memoryDir, \"MEMORY.md\");\n if (!existsSync(memoryFile)) {\n writeFileSync(\n memoryFile,\n `# Long-term Memory\n\nThis file stores important information that should persist across sessions.\n\n## User Information\n\n(Important facts about the user)\n\n## Preferences\n\n(User preferences learned over time)\n\n## Important Notes\n\n(Things to remember)\n`,\n );\n console.log(\" Created memory/MEMORY.md\");\n }\n}\n\n// ============================================================================\n// Gateway / Server\n// ============================================================================\n\nprogram\n .command(\"gateway\")\n .description(\"Start the nanobot gateway\")\n .option(\"-p, --port <number>\", \"Gateway port\", \"18790\")\n .option(\"--verbose\", \"Verbose output\", false)\n .action(async (opts) => {\n console.log(\n `${LOGO} Starting nanobot gateway on port ${opts.port}...`,\n );\n\n const config = loadConfig();\n const model = config.agents.defaults.model;\n const apiKey = getApiKey(config, model);\n const apiBase = getApiBase(config, model);\n const extraHeaders = getExtraHeaders(config, model);\n\n if (!apiKey) {\n console.error(\"Error: No API key configured.\");\n console.error(\n \"Set one in ~/.nanobot/config.json under providers.<provider>.apiKey\",\n );\n process.exit(1);\n }\n\n // Dynamic imports to avoid loading heavy deps up front\n const { MessageBus } = await import(\"../bus/queue.js\");\n const { OpenAIProvider } = await import(\n \"../providers/openai-provider.js\"\n );\n const { AgentLoop } = await import(\"../agent/loop.js\");\n const { ChannelManager } = await import(\"../channels/manager.js\");\n\n const bus = new MessageBus();\n const workspace = getConfigWorkspacePath(config);\n\n const provider = new OpenAIProvider({\n apiKey,\n apiBase: apiBase ?? undefined,\n defaultModel: model,\n extraHeaders,\n });\n\n const customTools = await loadCustomTools(config);\n\n // Create agent\n const agent = new AgentLoop({\n bus,\n provider,\n workspace,\n model,\n maxTokens: config.agents.defaults.maxTokens,\n maxIterations: config.agents.defaults.maxToolIterations,\n braveApiKey: config.tools.web.search.apiKey || undefined,\n execConfig: config.tools.exec,\n restrictToWorkspace: config.tools.restrictToWorkspace,\n toolsEnabled: config.tools.enabled,\n toolsDisabled: config.tools.disabled,\n customTools,\n });\n\n // Create channel manager\n const channels = new ChannelManager(config, bus);\n\n // Heartbeat service — no timer (DO drives the schedule),\n // but provides smart file-reading logic for /api/heartbeat\n const { HeartbeatService } = await import(\"../heartbeat/service.js\");\n const heartbeat = new HeartbeatService({\n workspace,\n onHeartbeat: (prompt) => agent.processDirect(prompt, \"heartbeat\"),\n });\n\n console.log(\"Heartbeat: managed by DO (local endpoint /api/heartbeat)\");\n console.log(\"Cron: managed by DO\");\n\n // Handle graceful shutdown\n const shutdown = async () => {\n console.log(\"\\nShutting down...\");\n agent.stop();\n await channels.stopAll();\n process.exit(0);\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n\n // Start HTTP gateway server\n const { createGatewayServer } = await import(\n \"../gateway/server.js\"\n );\n createGatewayServer({\n agent,\n bus,\n port: Number(opts.port),\n channels,\n heartbeat,\n });\n\n try {\n // Initialise channels first (non-blocking) so enabledChannels is populated\n await channels.init();\n // Both agent.run() and channels.startAll() block until stopped\n await Promise.all([agent.run(), channels.startAll()]);\n } catch (err) {\n console.error(\"Gateway error:\", err);\n process.exit(1);\n }\n });\n\n// ============================================================================\n// Agent Commands\n// ============================================================================\n\nprogram\n .command(\"agent\")\n .description(\"Interact with the agent directly\")\n .option(\"-m, --message <text>\", \"Message to send to the agent\")\n .option(\n \"-s, --session <id>\",\n \"Session ID\",\n \"cli:default\",\n )\n .action(async (opts) => {\n const config = loadConfig();\n const model = config.agents.defaults.model;\n const apiKey = getApiKey(config, model);\n const apiBase = getApiBase(config, model);\n const extraHeaders = getExtraHeaders(config, model);\n\n if (!apiKey) {\n console.error(\"Error: No API key configured.\");\n process.exit(1);\n }\n\n const { MessageBus } = await import(\"../bus/queue.js\");\n const { OpenAIProvider } = await import(\n \"../providers/openai-provider.js\"\n );\n const { AgentLoop } = await import(\"../agent/loop.js\");\n\n const bus = new MessageBus();\n const workspace = getConfigWorkspacePath(config);\n\n const provider = new OpenAIProvider({\n apiKey,\n apiBase: apiBase ?? undefined,\n defaultModel: model,\n extraHeaders,\n });\n\n const customTools = await loadCustomTools(config);\n\n const agentLoop = new AgentLoop({\n bus,\n provider,\n workspace,\n maxTokens: config.agents.defaults.maxTokens,\n braveApiKey: config.tools.web.search.apiKey || undefined,\n execConfig: config.tools.exec,\n restrictToWorkspace: config.tools.restrictToWorkspace,\n toolsEnabled: config.tools.enabled,\n toolsDisabled: config.tools.disabled,\n customTools,\n });\n\n if (opts.message) {\n // Single message mode\n const response = await agentLoop.processDirect(\n opts.message,\n opts.session,\n );\n console.log(`\\n${LOGO} ${response}`);\n } else {\n // Interactive mode\n console.log(`${LOGO} Interactive mode (Ctrl+C to exit)\\n`);\n\n const readline = await import(\"node:readline\");\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n const ask = (): void => {\n rl.question(\"You: \", async (input) => {\n const trimmed = input.trim();\n if (!trimmed) {\n ask();\n return;\n }\n\n try {\n const response = await agentLoop.processDirect(\n trimmed,\n opts.session,\n );\n console.log(`\\n${LOGO} ${response}\\n`);\n } catch (err) {\n console.error(\"Error:\", err);\n }\n ask();\n });\n };\n\n rl.on(\"close\", () => {\n console.log(\"\\nGoodbye!\");\n process.exit(0);\n });\n\n ask();\n }\n });\n\n// ============================================================================\n// Channel Commands\n// ============================================================================\n\nconst channelsCmd = program\n .command(\"channels\")\n .description(\"Manage channels\");\n\nchannelsCmd\n .command(\"status\")\n .description(\"Show channel status\")\n .action(() => {\n const config = loadConfig();\n\n console.log(\"Channel Status\");\n console.log(\"─\".repeat(50));\n\n const tg = config.channels.telegram;\n const tgToken = tg.token\n ? `token: ${tg.token.slice(0, 10)}...`\n : \"not configured\";\n console.log(\n ` Telegram ${tg.enabled ? \"[enabled]\" : \"[disabled]\"} ${tgToken}`,\n );\n\n const ln = config.channels.line;\n const lnToken = ln.channelAccessToken\n ? `token: ${ln.channelAccessToken.slice(0, 10)}...`\n : \"not configured\";\n console.log(\n ` LINE ${ln.enabled ? \"[enabled]\" : \"[disabled]\"} ${lnToken}`,\n );\n });\n\n// ============================================================================\n// Status\n// ============================================================================\n\nprogram\n .command(\"status\")\n .description(\"Show nanobot status\")\n .action(() => {\n const configPath = getConfigPath();\n const config = loadConfig();\n const workspace = getConfigWorkspacePath(config);\n\n console.log(`${LOGO} nanobot Status\\n`);\n\n console.log(\n `Config: ${configPath} ${existsSync(configPath) ? \"[ok]\" : \"[missing]\"}`,\n );\n console.log(\n `Workspace: ${workspace} ${existsSync(workspace) ? \"[ok]\" : \"[missing]\"}`,\n );\n\n if (existsSync(configPath)) {\n console.log(`Model: ${config.agents.defaults.model}`);\n console.log(\"\");\n console.log(\"Providers\");\n console.log(\"─\".repeat(50));\n\n const providers = config.providers as Record<string, { apiKey: string }>;\n for (const spec of PROVIDERS) {\n const p = providers[spec.name];\n if (!p) continue;\n const status = p.apiKey ? \"[set]\" : \"[not set]\";\n console.log(` ${spec.displayName.padEnd(16)} ${status}`);\n }\n }\n });\n\nprogram.parse();\n"],"mappings":";;;;;;;;;;;;;;AAuBA,MAAM,UAAU,IAAI,SAAS;AAE7B,eAAe,gBAAgB,QAAiC;CAC9D,MAAM,gBAAgB,OAAO,MAAM,UAAU,EAAE;AAC/C,KAAI,cAAc,WAAW,EAAG,QAAO,EAAE;CAEzC,MAAM,QAAgB,EAAE;AACxB,MAAK,MAAM,SAAS,cAClB,KAAI;EAIF,MAAM,MAAM,MAAM,OAAO,cAHN,WAAW,MAAM,OAAO,GACvC,MAAM,SACN,QAAQ,QAAQ,KAAK,EAAE,MAAM,OAAO,CACU,CAAC;EACnD,MAAM,aAAa,MAAM,UAAU;EACnC,MAAM,WAAW,eAAe,YAAY,IAAI,UAAU,IAAI;AAE9D,MAAI,CAAC,SACH,OAAM,IAAI,MAAM,WAAW,WAAW,aAAa;EAGrD,IAAI;AACJ,MAAI,OAAO,aAAa,WACtB,KAAI;AACF,cAAW,IAAI,SAAS,MAAM,WAAW,EAAE,CAAC;UACtC;AACN,cAAW,SAAS,MAAM,WAAW,EAAE,CAAC;;MAG1C,OAAM,IAAI,MAAM,WAAW,WAAW,qBAAqB;AAG7D,MACE,YACA,OAAO,aAAa,YACpB,aAAa,YACb,UAAU,SAEV,OAAM,KAAK,SAAiB;MAE5B,OAAM,IAAI,MAAM,WAAW,WAAW,kCAAkC;UAEnE,KAAK;AACZ,UAAQ,KACN,wCAAwC,MAAM,OAAO,KACnD,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAEnD;;AAIL,QAAO;;AAGT,QACG,KAAK,UAAU,CACf,YAAY,GAAG,KAAK,kCAAkC,CACtD,QAAQ,GAAG,KAAK,YAAY,WAAW,gBAAgB;AAM1D,QACG,QAAQ,UAAU,CAClB,YAAY,iDAAiD,CAC7D,aAAa;CACZ,MAAM,aAAa,eAAe;AAElC,KAAI,WAAW,WAAW,EAAE;AAC1B,UAAQ,IAAI,4BAA4B,aAAa;AACrD,UAAQ,IAAI,8CAA8C;AAC1D;;CAIF,MAAM,SAAS,aAAa,MAAM,EAAE,CAAC;AACrC,YAAW,OAAO;AAClB,SAAQ,IAAI,qBAAqB,aAAa;AAI9C,0BADkB,uBAAuB,OAAO,CACb;AAEnC,SAAQ,IAAI,KAAK,KAAK,oBAAoB;AAC1C,SAAQ,IAAI,gBAAgB;AAC5B,SAAQ,IAAI,kDAAkD;AAC9D,SAAQ,IAAI,8CAA8C;AAC1D,SAAQ,IAAI,yCAAuC;AACnD,SAAQ,IACN,oEACD;EACD;AAEJ,SAAS,yBAAyB,WAAyB;AACzD,KAAI,CAAC,WAAW,UAAU,CACxB,WAAU,WAAW,EAAE,WAAW,MAAM,CAAC;AAkD3C,MAAK,MAAM,CAAC,UAAU,YAAY,OAAO,QA/CC;EACxC,aAAa;;;;;;;;;;;EAWb,WAAW;;;;;;;;;;;;;;;;EAgBX,WAAW;;;;;;;;;;EAUX,gBAAgB;;;;;;;EAOjB,CAE0D,EAAE;EAC3D,MAAM,WAAW,KAAK,WAAW,SAAS;AAC1C,MAAI,CAAC,WAAW,SAAS,EAAE;AACzB,iBAAc,UAAU,QAAQ;AAChC,WAAQ,IAAI,aAAa,WAAW;;;CAKxC,MAAM,YAAY,KAAK,WAAW,SAAS;AAC3C,KAAI,CAAC,WAAW,UAAU,CACxB,WAAU,WAAW,EAAE,WAAW,MAAM,CAAC;CAE3C,MAAM,aAAa,KAAK,WAAW,YAAY;AAC/C,KAAI,CAAC,WAAW,WAAW,EAAE;AAC3B,gBACE,YACA;;;;;;;;;;;;;;;EAgBD;AACD,UAAQ,IAAI,6BAA6B;;;AAQ7C,QACG,QAAQ,UAAU,CAClB,YAAY,4BAA4B,CACxC,OAAO,uBAAuB,gBAAgB,QAAQ,CACtD,OAAO,aAAa,kBAAkB,MAAM,CAC5C,OAAO,OAAO,SAAS;AACtB,SAAQ,IACN,GAAG,KAAK,oCAAoC,KAAK,KAAK,KACvD;CAED,MAAM,SAAS,YAAY;CAC3B,MAAM,QAAQ,OAAO,OAAO,SAAS;CACrC,MAAM,SAAS,UAAU,QAAQ,MAAM;CACvC,MAAM,UAAU,WAAW,QAAQ,MAAM;CACzC,MAAM,eAAe,gBAAgB,QAAQ,MAAM;AAEnD,KAAI,CAAC,QAAQ;AACX,UAAQ,MAAM,gCAAgC;AAC9C,UAAQ,MACN,sEACD;AACD,UAAQ,KAAK,EAAE;;CAIjB,MAAM,EAAE,eAAe,MAAM,OAAO;CACpC,MAAM,EAAE,mBAAmB,MAAM,OAC/B;CAEF,MAAM,EAAE,cAAc,MAAM,OAAO;CACnC,MAAM,EAAE,mBAAmB,MAAM,OAAO;CAExC,MAAM,MAAM,IAAI,YAAY;CAC5B,MAAM,YAAY,uBAAuB,OAAO;CAEhD,MAAM,WAAW,IAAI,eAAe;EAClC;EACA,SAAS,WAAW;EACpB,cAAc;EACd;EACD,CAAC;CAEF,MAAM,cAAc,MAAM,gBAAgB,OAAO;CAGjD,MAAM,QAAQ,IAAI,UAAU;EAC1B;EACA;EACA;EACA;EACA,WAAW,OAAO,OAAO,SAAS;EAClC,eAAe,OAAO,OAAO,SAAS;EACtC,aAAa,OAAO,MAAM,IAAI,OAAO,UAAU;EAC/C,YAAY,OAAO,MAAM;EACzB,qBAAqB,OAAO,MAAM;EAClC,cAAc,OAAO,MAAM;EAC3B,eAAe,OAAO,MAAM;EAC5B;EACD,CAAC;CAGF,MAAM,WAAW,IAAI,eAAe,QAAQ,IAAI;CAIhD,MAAM,EAAE,qBAAqB,MAAM,OAAO;CAC1C,MAAM,YAAY,IAAI,iBAAiB;EACrC;EACA,cAAc,WAAW,MAAM,cAAc,QAAQ,YAAY;EAClE,CAAC;AAEF,SAAQ,IAAI,2DAA2D;AACvE,SAAQ,IAAI,sBAAsB;CAGlC,MAAM,WAAW,YAAY;AAC3B,UAAQ,IAAI,qBAAqB;AACjC,QAAM,MAAM;AACZ,QAAM,SAAS,SAAS;AACxB,UAAQ,KAAK,EAAE;;AAEjB,SAAQ,GAAG,UAAU,SAAS;AAC9B,SAAQ,GAAG,WAAW,SAAS;CAG/B,MAAM,EAAE,wBAAwB,MAAM,OACpC;AAEF,qBAAoB;EAClB;EACA;EACA,MAAM,OAAO,KAAK,KAAK;EACvB;EACA;EACD,CAAC;AAEF,KAAI;AAEF,QAAM,SAAS,MAAM;AAErB,QAAM,QAAQ,IAAI,CAAC,MAAM,KAAK,EAAE,SAAS,UAAU,CAAC,CAAC;UAC9C,KAAK;AACZ,UAAQ,MAAM,kBAAkB,IAAI;AACpC,UAAQ,KAAK,EAAE;;EAEjB;AAMJ,QACG,QAAQ,QAAQ,CAChB,YAAY,mCAAmC,CAC/C,OAAO,wBAAwB,+BAA+B,CAC9D,OACC,sBACA,cACA,cACD,CACA,OAAO,OAAO,SAAS;CACtB,MAAM,SAAS,YAAY;CAC3B,MAAM,QAAQ,OAAO,OAAO,SAAS;CACrC,MAAM,SAAS,UAAU,QAAQ,MAAM;CACvC,MAAM,UAAU,WAAW,QAAQ,MAAM;CACzC,MAAM,eAAe,gBAAgB,QAAQ,MAAM;AAEnD,KAAI,CAAC,QAAQ;AACX,UAAQ,MAAM,gCAAgC;AAC9C,UAAQ,KAAK,EAAE;;CAGjB,MAAM,EAAE,eAAe,MAAM,OAAO;CACpC,MAAM,EAAE,mBAAmB,MAAM,OAC/B;CAEF,MAAM,EAAE,cAAc,MAAM,OAAO;CAEnC,MAAM,MAAM,IAAI,YAAY;CAC5B,MAAM,YAAY,uBAAuB,OAAO;CAEhD,MAAM,WAAW,IAAI,eAAe;EAClC;EACA,SAAS,WAAW;EACpB,cAAc;EACd;EACD,CAAC;CAEF,MAAM,cAAc,MAAM,gBAAgB,OAAO;CAEjD,MAAM,YAAY,IAAI,UAAU;EAC9B;EACA;EACA;EACA,WAAW,OAAO,OAAO,SAAS;EAClC,aAAa,OAAO,MAAM,IAAI,OAAO,UAAU;EAC/C,YAAY,OAAO,MAAM;EACzB,qBAAqB,OAAO,MAAM;EAClC,cAAc,OAAO,MAAM;EAC3B,eAAe,OAAO,MAAM;EAC5B;EACD,CAAC;AAEF,KAAI,KAAK,SAAS;EAEhB,MAAM,WAAW,MAAM,UAAU,cAC/B,KAAK,SACL,KAAK,QACN;AACD,UAAQ,IAAI,KAAK,KAAK,GAAG,WAAW;QAC/B;AAEL,UAAQ,IAAI,GAAG,KAAK,sCAAsC;EAG1D,MAAM,MADW,MAAM,OAAO,kBACV,gBAAgB;GAClC,OAAO,QAAQ;GACf,QAAQ,QAAQ;GACjB,CAAC;EAEF,MAAM,YAAkB;AACtB,MAAG,SAAS,SAAS,OAAO,UAAU;IACpC,MAAM,UAAU,MAAM,MAAM;AAC5B,QAAI,CAAC,SAAS;AACZ,UAAK;AACL;;AAGF,QAAI;KACF,MAAM,WAAW,MAAM,UAAU,cAC/B,SACA,KAAK,QACN;AACD,aAAQ,IAAI,KAAK,KAAK,GAAG,SAAS,IAAI;aAC/B,KAAK;AACZ,aAAQ,MAAM,UAAU,IAAI;;AAE9B,SAAK;KACL;;AAGJ,KAAG,GAAG,eAAe;AACnB,WAAQ,IAAI,aAAa;AACzB,WAAQ,KAAK,EAAE;IACf;AAEF,OAAK;;EAEP;AAMgB,QACjB,QAAQ,WAAW,CACnB,YAAY,kBAAkB,CAG9B,QAAQ,SAAS,CACjB,YAAY,sBAAsB,CAClC,aAAa;CACZ,MAAM,SAAS,YAAY;AAE3B,SAAQ,IAAI,iBAAiB;AAC7B,SAAQ,IAAI,IAAI,OAAO,GAAG,CAAC;CAE3B,MAAM,KAAK,OAAO,SAAS;CAC3B,MAAM,UAAU,GAAG,QACf,UAAU,GAAG,MAAM,MAAM,GAAG,GAAG,CAAC,OAChC;AACJ,SAAQ,IACN,eAAe,GAAG,UAAU,cAAc,aAAa,IAAI,UAC5D;CAED,MAAM,KAAK,OAAO,SAAS;CAC3B,MAAM,UAAU,GAAG,qBACf,UAAU,GAAG,mBAAmB,MAAM,GAAG,GAAG,CAAC,OAC7C;AACJ,SAAQ,IACN,eAAe,GAAG,UAAU,cAAc,aAAa,IAAI,UAC5D;EACD;AAMJ,QACG,QAAQ,SAAS,CACjB,YAAY,sBAAsB,CAClC,aAAa;CACZ,MAAM,aAAa,eAAe;CAClC,MAAM,SAAS,YAAY;CAC3B,MAAM,YAAY,uBAAuB,OAAO;AAEhD,SAAQ,IAAI,GAAG,KAAK,mBAAmB;AAEvC,SAAQ,IACN,WAAW,WAAW,GAAG,WAAW,WAAW,GAAG,SAAS,cAC5D;AACD,SAAQ,IACN,cAAc,UAAU,GAAG,WAAW,UAAU,GAAG,SAAS,cAC7D;AAED,KAAI,WAAW,WAAW,EAAE;AAC1B,UAAQ,IAAI,UAAU,OAAO,OAAO,SAAS,QAAQ;AACrD,UAAQ,IAAI,GAAG;AACf,UAAQ,IAAI,YAAY;AACxB,UAAQ,IAAI,IAAI,OAAO,GAAG,CAAC;EAE3B,MAAM,YAAY,OAAO;AACzB,OAAK,MAAM,QAAQ,WAAW;GAC5B,MAAM,IAAI,UAAU,KAAK;AACzB,OAAI,CAAC,EAAG;GACR,MAAM,SAAS,EAAE,SAAS,UAAU;AACpC,WAAQ,IAAI,KAAK,KAAK,YAAY,OAAO,GAAG,CAAC,GAAG,SAAS;;;EAG7D;AAEJ,QAAQ,OAAO"}
|
package/dist/config/schema.d.mts
CHANGED
|
@@ -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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
allowFrom?: string[] | undefined;
|
|
79
|
+
telegram: {
|
|
80
|
+
enabled: boolean;
|
|
81
|
+
token: string;
|
|
82
|
+
allowFrom: string[];
|
|
90
83
|
proxy?: string | null | undefined;
|
|
91
|
-
}
|
|
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<{
|
|
@@ -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
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
allowFrom?: string[] | undefined;
|
|
657
|
+
telegram: {
|
|
658
|
+
enabled: boolean;
|
|
659
|
+
token: string;
|
|
660
|
+
allowFrom: string[];
|
|
668
661
|
proxy?: string | null | undefined;
|
|
669
|
-
}
|
|
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<{
|
|
@@ -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: {
|
|
@@ -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?: {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { MessageBus } from "../bus/queue.mjs";
|
|
1
2
|
import { AgentLoop } from "../agent/loop.mjs";
|
|
2
3
|
import { ChannelManager } from "../channels/manager.mjs";
|
|
3
4
|
import { HeartbeatService } from "../heartbeat/service.mjs";
|
|
@@ -5,6 +6,7 @@ import { HeartbeatService } from "../heartbeat/service.mjs";
|
|
|
5
6
|
//#region src/gateway/server.d.ts
|
|
6
7
|
interface GatewayServerOptions {
|
|
7
8
|
agent: AgentLoop;
|
|
9
|
+
bus: MessageBus;
|
|
8
10
|
port: number;
|
|
9
11
|
channels?: ChannelManager;
|
|
10
12
|
heartbeat?: HeartbeatService;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.mts","names":[],"sources":["../../src/gateway/server.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"server.d.mts","names":[],"sources":["../../src/gateway/server.ts"],"mappings":";;;;;;UAWiB,oBAAA;EACf,KAAA,EAAO,SAAA;EACP,GAAA,EAAK,UAAA;EACL,IAAA;EACA,QAAA,GAAW,cAAA;EACX,SAAA,GAAY,gBAAA;AAAA;AAAA,UAGG,aAAA;EAHH;EAKZ,MAAA,CAAO,IAAA,UAAc,OAAA,UAAiB,MAAA;EALV;EAO5B,cAAA,CAAe,IAAA,UAAc,IAAA;EAXtB;EAaP,gBAAA,CAAiB,IAAA,UAAc,MAAA;AAAA;AAAA,iBAGjB,mBAAA,CAAoB,IAAA,EAAM,oBAAA,GAAuB,aAAA"}
|
package/dist/gateway/server.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createOutboundMessage } from "../bus/events.mjs";
|
|
1
2
|
import { VERSION } from "../index.mjs";
|
|
2
3
|
import { getControlHtml } from "./ui.mjs";
|
|
3
4
|
import { Hono } from "hono";
|
|
@@ -6,7 +7,7 @@ import { streamSSE } from "hono/streaming";
|
|
|
6
7
|
|
|
7
8
|
//#region src/gateway/server.ts
|
|
8
9
|
function createGatewayServer(opts) {
|
|
9
|
-
const { agent, port, channels, heartbeat } = opts;
|
|
10
|
+
const { agent, bus, port, channels, heartbeat } = opts;
|
|
10
11
|
const app = new Hono();
|
|
11
12
|
const sseClients = /* @__PURE__ */ new Set();
|
|
12
13
|
function broadcast(payload) {
|
|
@@ -43,33 +44,17 @@ function createGatewayServer(opts) {
|
|
|
43
44
|
const body = await c.req.json();
|
|
44
45
|
const message = body.message?.trim();
|
|
45
46
|
if (!message) return c.json({ error: "message is required" }, 400);
|
|
46
|
-
const
|
|
47
|
+
const chatId = body.chatId ?? "default";
|
|
48
|
+
const sessionKey = body.session ?? `line:${chatId}`;
|
|
49
|
+
const deliver = body.deliver ?? false;
|
|
47
50
|
try {
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
name: tc.function.name,
|
|
56
|
-
arguments: tc.function.arguments
|
|
57
|
-
});
|
|
58
|
-
else if (msg.role === "tool") {
|
|
59
|
-
const content = typeof msg.content === "string" ? msg.content : "";
|
|
60
|
-
turn.push({
|
|
61
|
-
type: "tool_result",
|
|
62
|
-
name: msg.name ?? "",
|
|
63
|
-
output: content
|
|
64
|
-
});
|
|
65
|
-
} else if (msg.role === "assistant" && msg.content) turn.push({
|
|
66
|
-
type: "text",
|
|
67
|
-
content: typeof msg.content === "string" ? msg.content : ""
|
|
68
|
-
});
|
|
69
|
-
return c.json({
|
|
70
|
-
response,
|
|
71
|
-
turn
|
|
72
|
-
});
|
|
51
|
+
const response = await agent.processDirect(message, sessionKey, "line", chatId);
|
|
52
|
+
if (deliver && response) await bus.publishOutbound(createOutboundMessage({
|
|
53
|
+
channel: "line",
|
|
54
|
+
chatId,
|
|
55
|
+
content: response
|
|
56
|
+
}));
|
|
57
|
+
return c.json({ response });
|
|
73
58
|
} catch (err) {
|
|
74
59
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
75
60
|
console.error("Chat error:", errMsg);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.mjs","names":[],"sources":["../../src/gateway/server.ts"],"sourcesContent":["import { Hono } from \"hono\";\nimport { serve } from \"@hono/node-server\";\nimport { streamSSE } from \"hono/streaming\";\nimport { getControlHtml } from \"./ui.js\";\nimport type { AgentLoop } from \"../agent/loop.js\";\nimport type { ChannelManager } from \"../channels/manager.js\";\nimport type { HeartbeatService } from \"../heartbeat/service.js\";\nimport { VERSION } from \"../index.js\";\n\nexport interface GatewayServerOptions {\n agent: AgentLoop;\n port: number;\n channels?: ChannelManager;\n heartbeat?: HeartbeatService;\n}\n\nexport interface GatewayServer {\n /** Push a message to all connected SSE clients. */\n notify(role: string, content: string, chatId?: string): void;\n /** Push a tool call event to all connected SSE clients. */\n notifyToolCall(name: string, args: string): void;\n /** Push a tool result event to all connected SSE clients. */\n notifyToolResult(name: string, output: string): void;\n}\n\nexport function createGatewayServer(opts: GatewayServerOptions): GatewayServer {\n const { agent, port, channels, heartbeat } = opts;\n const app = new Hono();\n\n // Track SSE clients for real-time push\n const sseClients = new Set<(data: string) => void>();\n\n function broadcast(payload: string): void {\n for (const send of sseClients) {\n try {\n send(payload);\n } catch {\n // client disconnected, will be cleaned up\n }\n }\n }\n\n function notify(role: string, content: string, chatId = \"default\"): void {\n broadcast(JSON.stringify({ type: \"message\", role, content, chatId }));\n }\n\n function notifyToolCall(name: string, args: string): void {\n broadcast(JSON.stringify({ type: \"tool_call\", name, arguments: args }));\n }\n\n function notifyToolResult(name: string, output: string): void {\n broadcast(JSON.stringify({ type: \"tool_result\", name, output }));\n }\n\n // Serve the control UI\n app.get(\"/\", (c) => {\n return c.html(getControlHtml());\n });\n\n // Chat endpoint — sends a message to the agent and returns the response\n app.post(\"/api/chat\", async (c) => {\n const body = await c.req.json<{ message?: string; session?: string }>();\n const message = body.message?.trim();\n\n if (!message) {\n return c.json({ error: \"message is required\" }, 400);\n }\n\n const sessionKey = body.session ?? \"web:default\";\n\n try {\n // Snapshot history so we can extract this turn's messages\n const session = agent.sessions.getOrCreate(sessionKey);\n const prevLen = session.getHistory().length;\n\n const response = await agent.processDirect(\n message,\n sessionKey,\n \"web\",\n \"default\",\n );\n\n // Build turn array: tool calls, tool results, and the final response\n const turn: Array<{\n type: \"tool_call\" | \"tool_result\" | \"text\";\n name?: string;\n arguments?: string;\n output?: string;\n content?: string;\n }> = [];\n\n const turnMessages = session.getHistory().slice(prevLen);\n for (const msg of turnMessages) {\n if (msg.role === \"assistant\" && msg.tool_calls && msg.tool_calls.length > 0) {\n for (const tc of msg.tool_calls) {\n turn.push({ type: \"tool_call\", name: tc.function.name, arguments: tc.function.arguments });\n }\n } else if (msg.role === \"tool\") {\n const content = typeof msg.content === \"string\" ? msg.content : \"\";\n turn.push({ type: \"tool_result\", name: msg.name ?? \"\", output: content });\n } else if (msg.role === \"assistant\" && msg.content) {\n turn.push({ type: \"text\", content: typeof msg.content === \"string\" ? msg.content : \"\" });\n }\n }\n\n return c.json({ response, turn });\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n console.error(\"Chat error:\", errMsg);\n return c.json({ error: errMsg }, 500);\n }\n });\n\n // History endpoint — returns conversation history for the UI\n app.get(\"/api/history\", (c) => {\n const sessionKey = c.req.query(\"session\") ?? \"web:default\";\n const session = agent.sessions.getOrCreate(sessionKey);\n const history = session.getHistory();\n\n // Return user, assistant, and tool messages for the UI\n const messages: Array<{\n role: string;\n content: string;\n tool_calls?: Array<{ name: string; arguments: string }>;\n tool_name?: string;\n }> = [];\n\n for (const msg of history) {\n if (msg.role === \"user\" || msg.role === \"assistant\") {\n const content =\n typeof msg.content === \"string\"\n ? msg.content\n : msg.content\n ? msg.content\n .filter((p): p is { type: \"text\"; text: string } => p.type === \"text\")\n .map((p) => p.text)\n .join(\"\")\n : \"\";\n\n const entry: (typeof messages)[number] = { role: msg.role, content };\n\n // Include tool calls if present on assistant messages\n if (msg.role === \"assistant\" && msg.tool_calls && msg.tool_calls.length > 0) {\n entry.tool_calls = msg.tool_calls.map((tc) => ({\n name: tc.function.name,\n arguments: tc.function.arguments,\n }));\n }\n\n // Skip empty assistant messages that only have tool_calls (no text)\n if (msg.role === \"assistant\" && !content && !entry.tool_calls) continue;\n\n messages.push(entry);\n } else if (msg.role === \"tool\") {\n const content =\n typeof msg.content === \"string\"\n ? msg.content\n : \"\";\n messages.push({\n role: \"tool\",\n content,\n tool_name: msg.name,\n });\n }\n }\n\n return c.json({ messages });\n });\n\n // SSE endpoint — real-time push for cron results and other async messages\n app.get(\"/api/events\", (c) => {\n return streamSSE(c, async (stream) => {\n const send = (data: string) => {\n stream.writeSSE({ data }).catch(() => {});\n };\n\n sseClients.add(send);\n\n // Send a ping so the client knows the connection is alive\n await stream.writeSSE({ data: JSON.stringify({ type: \"connected\" }) });\n\n // Keep alive with periodic pings\n const pingInterval = setInterval(() => {\n stream.writeSSE({ data: JSON.stringify({ type: \"ping\" }) }).catch(() => {\n clearInterval(pingInterval);\n });\n }, 30_000);\n\n // Wait until the client disconnects\n try {\n await new Promise<void>((resolve) => {\n stream.onAbort(() => resolve());\n });\n } finally {\n clearInterval(pingInterval);\n sseClients.delete(send);\n }\n });\n });\n\n // Heartbeat endpoint — triggered by DO scheduler via manager\n app.post(\"/api/heartbeat\", async (c) => {\n if (!heartbeat) {\n return c.json({ error: \"Heartbeat not configured\" }, 503);\n }\n\n try {\n const response = await heartbeat.triggerNow();\n return c.json({ status: \"ok\", response });\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n console.error(\"Heartbeat error:\", errMsg);\n return c.json({ error: errMsg }, 500);\n }\n });\n\n // LINE webhook endpoint\n app.post(\"/webhook/line\", async (c) => {\n if (!channels) {\n return c.json({ error: \"Channels not configured\" }, 503);\n }\n\n const { LineChannel } = await import(\"../channels/line.js\");\n const lineChannel = channels.getChannel(\"line\");\n if (!lineChannel || !(lineChannel instanceof LineChannel)) {\n return c.json({ error: \"LINE channel not enabled\" }, 404);\n }\n\n // Read raw body for signature verification\n const rawBody = await c.req.text();\n const signature = c.req.header(\"x-line-signature\") ?? \"\";\n\n if (!lineChannel.verifySignature(rawBody, signature)) {\n return c.json({ error: \"Invalid signature\" }, 401);\n }\n\n const body = JSON.parse(rawBody);\n\n // Process webhook events (non-blocking — return 200 immediately)\n lineChannel.handleWebhook(body).catch((err) => {\n console.error(\"LINE webhook processing error:\", err);\n });\n\n return c.json({ status: \"ok\" });\n });\n\n // Status / health check\n app.get(\"/api/status\", (c) => {\n return c.json({\n status: \"ok\",\n version: VERSION,\n uptime: process.uptime(),\n });\n });\n\n // Start the server\n serve({ fetch: app.fetch, port }, (info) => {\n console.log(`Gateway HTTP server listening on http://localhost:${info.port}`);\n });\n\n return { notify, notifyToolCall, notifyToolResult };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"server.mjs","names":[],"sources":["../../src/gateway/server.ts"],"sourcesContent":["import { Hono } from \"hono\";\nimport { serve } from \"@hono/node-server\";\nimport { streamSSE } from \"hono/streaming\";\nimport { getControlHtml } from \"./ui.js\";\nimport type { AgentLoop } from \"../agent/loop.js\";\nimport type { ChannelManager } from \"../channels/manager.js\";\nimport { createOutboundMessage } from \"../bus/events.js\";\nimport type { MessageBus } from \"../bus/queue.js\";\nimport type { HeartbeatService } from \"../heartbeat/service.js\";\nimport { VERSION } from \"../index.js\";\n\nexport interface GatewayServerOptions {\n agent: AgentLoop;\n bus: MessageBus;\n port: number;\n channels?: ChannelManager;\n heartbeat?: HeartbeatService;\n}\n\nexport interface GatewayServer {\n /** Push a message to all connected SSE clients. */\n notify(role: string, content: string, chatId?: string): void;\n /** Push a tool call event to all connected SSE clients. */\n notifyToolCall(name: string, args: string): void;\n /** Push a tool result event to all connected SSE clients. */\n notifyToolResult(name: string, output: string): void;\n}\n\nexport function createGatewayServer(opts: GatewayServerOptions): GatewayServer {\n const { agent, bus, port, channels, heartbeat } = opts;\n const app = new Hono();\n\n // Track SSE clients for real-time push\n const sseClients = new Set<(data: string) => void>();\n\n function broadcast(payload: string): void {\n for (const send of sseClients) {\n try {\n send(payload);\n } catch {\n // client disconnected, will be cleaned up\n }\n }\n }\n\n function notify(role: string, content: string, chatId = \"default\"): void {\n broadcast(JSON.stringify({ type: \"message\", role, content, chatId }));\n }\n\n function notifyToolCall(name: string, args: string): void {\n broadcast(JSON.stringify({ type: \"tool_call\", name, arguments: args }));\n }\n\n function notifyToolResult(name: string, output: string): void {\n broadcast(JSON.stringify({ type: \"tool_result\", name, output }));\n }\n\n // Serve the control UI\n app.get(\"/\", (c) => {\n return c.html(getControlHtml());\n });\n\n // Chat endpoint — sends a message to the agent and returns the response\n app.post(\"/api/chat\", async (c) => {\n const body = await c.req.json<{\n message?: string;\n session?: string;\n chatId?: string;\n deliver?: boolean;\n }>();\n const message = body.message?.trim();\n\n if (!message) {\n return c.json({ error: \"message is required\" }, 400);\n }\n\n const chatId = body.chatId ?? \"default\";\n const sessionKey = body.session ?? `line:${chatId}`;\n const deliver = body.deliver ?? false;\n\n try {\n const response = await agent.processDirect(\n message,\n sessionKey,\n \"line\",\n chatId,\n );\n\n // Deliver response to LINE if requested (e.g. cron-triggered)\n if (deliver && response) {\n await bus.publishOutbound(\n createOutboundMessage({\n channel: \"line\",\n chatId,\n content: response,\n }),\n );\n }\n\n return c.json({ response });\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n console.error(\"Chat error:\", errMsg);\n return c.json({ error: errMsg }, 500);\n }\n });\n\n // History endpoint — returns conversation history for the UI\n app.get(\"/api/history\", (c) => {\n const sessionKey = c.req.query(\"session\") ?? \"web:default\";\n const session = agent.sessions.getOrCreate(sessionKey);\n const history = session.getHistory();\n\n // Return user, assistant, and tool messages for the UI\n const messages: Array<{\n role: string;\n content: string;\n tool_calls?: Array<{ name: string; arguments: string }>;\n tool_name?: string;\n }> = [];\n\n for (const msg of history) {\n if (msg.role === \"user\" || msg.role === \"assistant\") {\n const content =\n typeof msg.content === \"string\"\n ? msg.content\n : msg.content\n ? msg.content\n .filter((p): p is { type: \"text\"; text: string } => p.type === \"text\")\n .map((p) => p.text)\n .join(\"\")\n : \"\";\n\n const entry: (typeof messages)[number] = { role: msg.role, content };\n\n // Include tool calls if present on assistant messages\n if (msg.role === \"assistant\" && msg.tool_calls && msg.tool_calls.length > 0) {\n entry.tool_calls = msg.tool_calls.map((tc) => ({\n name: tc.function.name,\n arguments: tc.function.arguments,\n }));\n }\n\n // Skip empty assistant messages that only have tool_calls (no text)\n if (msg.role === \"assistant\" && !content && !entry.tool_calls) continue;\n\n messages.push(entry);\n } else if (msg.role === \"tool\") {\n const content =\n typeof msg.content === \"string\"\n ? msg.content\n : \"\";\n messages.push({\n role: \"tool\",\n content,\n tool_name: msg.name,\n });\n }\n }\n\n return c.json({ messages });\n });\n\n // SSE endpoint — real-time push for cron results and other async messages\n app.get(\"/api/events\", (c) => {\n return streamSSE(c, async (stream) => {\n const send = (data: string) => {\n stream.writeSSE({ data }).catch(() => {});\n };\n\n sseClients.add(send);\n\n // Send a ping so the client knows the connection is alive\n await stream.writeSSE({ data: JSON.stringify({ type: \"connected\" }) });\n\n // Keep alive with periodic pings\n const pingInterval = setInterval(() => {\n stream.writeSSE({ data: JSON.stringify({ type: \"ping\" }) }).catch(() => {\n clearInterval(pingInterval);\n });\n }, 30_000);\n\n // Wait until the client disconnects\n try {\n await new Promise<void>((resolve) => {\n stream.onAbort(() => resolve());\n });\n } finally {\n clearInterval(pingInterval);\n sseClients.delete(send);\n }\n });\n });\n\n // Heartbeat endpoint — triggered by DO scheduler via manager\n app.post(\"/api/heartbeat\", async (c) => {\n if (!heartbeat) {\n return c.json({ error: \"Heartbeat not configured\" }, 503);\n }\n\n try {\n const response = await heartbeat.triggerNow();\n return c.json({ status: \"ok\", response });\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n console.error(\"Heartbeat error:\", errMsg);\n return c.json({ error: errMsg }, 500);\n }\n });\n\n // LINE webhook endpoint\n app.post(\"/webhook/line\", async (c) => {\n if (!channels) {\n return c.json({ error: \"Channels not configured\" }, 503);\n }\n\n const { LineChannel } = await import(\"../channels/line.js\");\n const lineChannel = channels.getChannel(\"line\");\n if (!lineChannel || !(lineChannel instanceof LineChannel)) {\n return c.json({ error: \"LINE channel not enabled\" }, 404);\n }\n\n // Read raw body for signature verification\n const rawBody = await c.req.text();\n const signature = c.req.header(\"x-line-signature\") ?? \"\";\n\n if (!lineChannel.verifySignature(rawBody, signature)) {\n return c.json({ error: \"Invalid signature\" }, 401);\n }\n\n const body = JSON.parse(rawBody);\n\n // Process webhook events (non-blocking — return 200 immediately)\n lineChannel.handleWebhook(body).catch((err) => {\n console.error(\"LINE webhook processing error:\", err);\n });\n\n return c.json({ status: \"ok\" });\n });\n\n // Status / health check\n app.get(\"/api/status\", (c) => {\n return c.json({\n status: \"ok\",\n version: VERSION,\n uptime: process.uptime(),\n });\n });\n\n // Start the server\n serve({ fetch: app.fetch, port }, (info) => {\n console.log(`Gateway HTTP server listening on http://localhost:${info.port}`);\n });\n\n return { notify, notifyToolCall, notifyToolResult };\n}\n"],"mappings":"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"}
|
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,
|
|
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
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":"
|
|
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,
|
|
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
|
package/dist/index.mjs.map
CHANGED
|
@@ -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 {
|
|
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
package/skills/cron/SKILL.md
CHANGED
|
@@ -7,21 +7,21 @@ description: Schedule reminders and recurring tasks.
|
|
|
7
7
|
|
|
8
8
|
Use the `cron` tool to schedule reminders or recurring tasks.
|
|
9
9
|
|
|
10
|
-
## Two Modes
|
|
10
|
+
## Two Modes (kind parameter)
|
|
11
11
|
|
|
12
|
-
1. **
|
|
13
|
-
2. **
|
|
12
|
+
1. **direct** (default) — message is sent verbatim to the user. No LLM call. Use for reminders and fixed messages.
|
|
13
|
+
2. **task** — message is run through the agent as a prompt. The agent processes it and delivers the result. Use for dynamic tasks that need tools/reasoning.
|
|
14
14
|
|
|
15
15
|
## Examples
|
|
16
16
|
|
|
17
|
-
Fixed reminder:
|
|
17
|
+
Fixed reminder (kind="direct", the default):
|
|
18
18
|
```
|
|
19
19
|
cron(action="add", message="Time to take a break!", every_seconds=1200)
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
Dynamic task (agent executes each time):
|
|
22
|
+
Dynamic task (kind="task", agent executes each time):
|
|
23
23
|
```
|
|
24
|
-
cron(action="add", message="Check HKUDS/nanobot GitHub stars and report", every_seconds=600)
|
|
24
|
+
cron(action="add", message="Check HKUDS/nanobot GitHub stars and report", kind="task", every_seconds=600)
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
List/remove:
|
|
@@ -38,3 +38,5 @@ cron(action="remove", job_id="abc123")
|
|
|
38
38
|
| every hour | every_seconds: 3600 |
|
|
39
39
|
| every day at 8am | cron_expr: "0 8 * * *" |
|
|
40
40
|
| weekdays at 5pm | cron_expr: "0 17 * * 1-5" |
|
|
41
|
+
| In 10 minutes | at: "2026-02-10T10:00:00Z" |
|
|
42
|
+
| at 10:00 AM on February 10, 2026 | at: "2026-02-10T10:00:00Z" |
|
package/dist/cron/service.d.mts
DELETED
|
@@ -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"}
|
package/dist/cron/service.mjs
DELETED
|
@@ -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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"}
|
package/dist/cron/types.d.mts
DELETED
|
@@ -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"}
|
package/dist/cron/types.mjs
DELETED
|
@@ -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
|
package/dist/cron/types.mjs.map
DELETED
|
@@ -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"}
|