clawmini 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +59 -0
- package/README.md +4 -2
- package/dist/adapter-discord/index.d.mts.map +1 -1
- package/dist/adapter-discord/index.mjs +13 -4
- package/dist/adapter-discord/index.mjs.map +1 -1
- package/dist/cli/index.mjs +7 -6
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/lite.mjs +16 -10
- package/dist/cli/lite.mjs.map +1 -1
- package/dist/daemon/index.mjs +590 -401
- package/dist/daemon/index.mjs.map +1 -1
- package/dist/{fetch-BjZVyU3Z.mjs → fetch-Cn1XNyiO.mjs} +1 -1
- package/dist/{fetch-BjZVyU3Z.mjs.map → fetch-Cn1XNyiO.mjs.map} +1 -1
- package/dist/lite-oSYSvaOr.mjs +164 -0
- package/dist/lite-oSYSvaOr.mjs.map +1 -0
- package/dist/web/_app/immutable/chunks/{CAZeqksE.js → 8YNcRyEk.js} +1 -1
- package/dist/web/_app/immutable/chunks/{B3YcEpQV.js → DQoygso7.js} +1 -1
- package/dist/web/_app/immutable/entry/{app.ZuicLpkH.js → app.DO5eYwVz.js} +2 -2
- package/dist/web/_app/immutable/entry/start.D48mVn1m.js +1 -0
- package/dist/web/_app/immutable/nodes/{0.BB1CjKco.js → 0.B-0CcADM.js} +1 -1
- package/dist/web/_app/immutable/nodes/{1.CdSgEHu9.js → 1.FixKgvRO.js} +1 -1
- package/{web/.svelte-kit/output/client/_app/immutable/nodes/3.CKp7Wkn8.js → dist/web/_app/immutable/nodes/3.ncP0xLO6.js} +1 -1
- package/dist/web/_app/immutable/nodes/{4.FyeoMY-Y.js → 4.CQYJEgv8.js} +1 -1
- package/dist/web/_app/immutable/nodes/{5.D6mVN7l7.js → 5.BpJUN6QH.js} +1 -1
- package/dist/web/_app/version.json +1 -1
- package/dist/web/index.html +6 -6
- package/dist/{workspace-BC1ahx4R.mjs → workspace-DjoNjhW0.mjs} +12 -42
- package/dist/workspace-DjoNjhW0.mjs.map +1 -0
- package/docs/15_lite_fetch_pending/development_log.md +31 -0
- package/docs/15_lite_fetch_pending/notes.md +48 -0
- package/docs/15_lite_fetch_pending/prd.md +39 -0
- package/docs/15_lite_fetch_pending/questions.md +3 -0
- package/docs/15_lite_fetch_pending/tickets.md +42 -0
- package/docs/CHECKS.md +2 -2
- package/eslint.config.js +12 -0
- package/package.json +3 -2
- package/src/adapter-discord/client.ts +1 -1
- package/src/adapter-discord/index.ts +22 -5
- package/src/cli/client.ts +8 -3
- package/src/cli/e2e/adapter-discord.test.ts +2 -2
- package/src/cli/e2e/daemon.test.ts +2 -1
- package/src/cli/e2e/export-lite-func.test.ts +41 -13
- package/src/cli/e2e/fallbacks.test.ts +4 -0
- package/src/cli/lite.ts +24 -6
- package/src/daemon/api/agent-router.ts +191 -0
- package/src/daemon/{router.test.ts → api/index.test.ts} +101 -34
- package/src/daemon/api/index.ts +4 -0
- package/src/daemon/{router-policy-request.test.ts → api/policy-request.test.ts} +27 -13
- package/src/daemon/api/router-utils.ts +159 -0
- package/src/daemon/api/trpc.ts +30 -0
- package/src/daemon/api/user-router.ts +221 -0
- package/src/daemon/index.ts +3 -3
- package/src/daemon/message-interruption.test.ts +17 -10
- package/src/daemon/message-typing.test.ts +1 -1
- package/src/daemon/message.ts +260 -239
- package/src/daemon/observation.test.ts +1 -1
- package/src/daemon/queue.test.ts +28 -0
- package/src/daemon/queue.ts +30 -15
- package/src/daemon/request-store.test.ts +4 -4
- package/src/daemon/request-store.ts +3 -1
- package/src/shared/workspace.ts +4 -5
- package/templates/debug/settings.json +5 -0
- package/templates/environments/macos/env.json +1 -1
- package/templates/environments/macos-proxy/env.json +1 -1
- package/templates/gemini-claw/.gemini/hooks/insert-pending.sh +9 -0
- package/templates/gemini-claw/.gemini/settings.json +14 -1
- package/templates/gemini-claw/.gemini/system.md +2 -0
- package/web/.svelte-kit/generated/server/internal.js +1 -1
- package/web/.svelte-kit/output/client/.vite/manifest.json +26 -26
- package/web/.svelte-kit/output/client/_app/immutable/chunks/{CAZeqksE.js → 8YNcRyEk.js} +1 -1
- package/web/.svelte-kit/output/client/_app/immutable/chunks/{B3YcEpQV.js → DQoygso7.js} +1 -1
- package/web/.svelte-kit/output/client/_app/immutable/entry/{app.ZuicLpkH.js → app.DO5eYwVz.js} +2 -2
- package/web/.svelte-kit/output/client/_app/immutable/entry/start.D48mVn1m.js +1 -0
- package/web/.svelte-kit/output/client/_app/immutable/nodes/{0.BB1CjKco.js → 0.B-0CcADM.js} +1 -1
- package/web/.svelte-kit/output/client/_app/immutable/nodes/{1.CdSgEHu9.js → 1.FixKgvRO.js} +1 -1
- package/{dist/web/_app/immutable/nodes/3.CKp7Wkn8.js → web/.svelte-kit/output/client/_app/immutable/nodes/3.ncP0xLO6.js} +1 -1
- package/web/.svelte-kit/output/client/_app/immutable/nodes/{4.FyeoMY-Y.js → 4.CQYJEgv8.js} +1 -1
- package/web/.svelte-kit/output/client/_app/immutable/nodes/{5.D6mVN7l7.js → 5.BpJUN6QH.js} +1 -1
- package/web/.svelte-kit/output/client/_app/version.json +1 -1
- package/web/.svelte-kit/output/server/chunks/internal.js +1 -1
- package/web/.svelte-kit/output/server/manifest-full.js +1 -1
- package/web/.svelte-kit/output/server/manifest.js +1 -1
- package/web/.svelte-kit/output/server/nodes/0.js +1 -1
- package/web/.svelte-kit/output/server/nodes/1.js +1 -1
- package/web/.svelte-kit/output/server/nodes/3.js +1 -1
- package/web/.svelte-kit/output/server/nodes/4.js +1 -1
- package/web/.svelte-kit/output/server/nodes/5.js +1 -1
- package/dist/chats-BcbxvPlj.mjs +0 -29
- package/dist/chats-BcbxvPlj.mjs.map +0 -1
- package/dist/chats-CpRQrNHj.mjs +0 -91
- package/dist/chats-CpRQrNHj.mjs.map +0 -1
- package/dist/fs-B5wW0oaH.mjs +0 -14
- package/dist/fs-B5wW0oaH.mjs.map +0 -1
- package/dist/lite-DBUuHsX0.mjs +0 -80
- package/dist/lite-DBUuHsX0.mjs.map +0 -1
- package/dist/policy-utils-BvfOK6Ih.mjs +0 -114
- package/dist/policy-utils-BvfOK6Ih.mjs.map +0 -1
- package/dist/rolldown-runtime-95iHPtFO.mjs +0 -18
- package/dist/web/_app/immutable/entry/start.DuQwh4Nz.js +0 -1
- package/dist/workspace-BC1ahx4R.mjs.map +0 -1
- package/src/daemon/router.ts +0 -510
- package/web/.svelte-kit/output/client/_app/immutable/entry/start.DuQwh4Nz.js +0 -1
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
//#region \0rolldown/runtime.js
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __exportAll = (all, no_symbols) => {
|
|
4
|
-
let target = {};
|
|
5
|
-
for (var name in all) {
|
|
6
|
-
__defProp(target, name, {
|
|
7
|
-
get: all[name],
|
|
8
|
-
enumerable: true
|
|
9
|
-
});
|
|
10
|
-
}
|
|
11
|
-
if (!no_symbols) {
|
|
12
|
-
__defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
13
|
-
}
|
|
14
|
-
return target;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
//#endregion
|
|
18
|
-
export { __exportAll as t };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{l as o,a as r}from"../chunks/B3YcEpQV.js";export{o as load_css,r as start};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"workspace-BC1ahx4R.mjs","names":["fsPromises"],"sources":["../src/shared/config.ts","../src/shared/workspace.ts"],"sourcesContent":["import { z } from 'zod';\n\nexport const FallbackSchema = z.looseObject({\n commands: z\n .looseObject({\n new: z.string().optional(),\n append: z.string().optional(),\n getSessionId: z.string().optional(),\n getMessageContent: z.string().optional(),\n })\n .optional(),\n env: z.record(z.string(), z.union([z.string(), z.boolean()])).optional(),\n retries: z.number().int().min(0).default(1),\n delayMs: z.number().int().min(0).default(1000),\n});\n\nexport const AgentSchema = z.looseObject({\n commands: z\n .looseObject({\n new: z.string().optional(),\n append: z.string().optional(),\n getSessionId: z.string().optional(),\n getMessageContent: z.string().optional(),\n })\n .optional(),\n env: z.record(z.string(), z.union([z.string(), z.boolean()])).optional(),\n directory: z.string().optional(),\n fallbacks: z.array(FallbackSchema).optional(),\n files: z.string().default('./attachments').optional(),\n});\n\nexport type Agent = z.infer<typeof AgentSchema>;\n\nexport const CronJobSchema = z.looseObject({\n id: z.string().min(1),\n createdAt: z.string().optional(),\n message: z.string().default(''),\n reply: z.string().optional(),\n agentId: z.string().optional(),\n env: z.record(z.string(), z.union([z.string(), z.boolean()])).optional(),\n session: z.looseObject({ type: z.string() }).optional(),\n schedule: z.union([\n z.looseObject({ cron: z.string() }),\n z.looseObject({ every: z.string() }),\n z.looseObject({ at: z.string() }),\n ]),\n});\n\nexport type CronJob = z.infer<typeof CronJobSchema>;\n\nexport const ChatSettingsSchema = z.looseObject({\n defaultAgent: z.string().optional(),\n sessions: z.record(z.string(), z.string()).optional(),\n routers: z.array(z.string()).optional(),\n jobs: z.array(CronJobSchema).optional(),\n});\n\nexport type ChatSettings = z.infer<typeof ChatSettingsSchema>;\n\nexport const AgentSessionSettingsSchema = z.looseObject({\n env: z.record(z.string(), z.union([z.string(), z.boolean()])).optional(),\n});\n\nexport type AgentSessionSettings = z.infer<typeof AgentSessionSettingsSchema>;\n\nexport const EnvironmentSchema = z.looseObject({\n init: z.string().optional(),\n up: z.string().optional(),\n down: z.string().optional(),\n prefix: z.string().optional(),\n envFormat: z.string().optional(),\n exportLiteTo: z.string().optional(),\n env: z.record(z.string(), z.union([z.string(), z.boolean()])).optional(),\n});\n\nexport type Environment = z.infer<typeof EnvironmentSchema>;\n\nexport const SettingsSchema = z.looseObject({\n chats: z\n .looseObject({\n defaultId: z.string().optional(),\n })\n .optional(),\n defaultAgent: AgentSchema.optional(),\n environments: z.record(z.string(), z.string()).optional(),\n routers: z.array(z.string()).optional(),\n files: z.string().default('./attachments').optional(),\n api: z\n .union([\n z.boolean(),\n z.looseObject({\n host: z.string().optional(),\n port: z.number().optional(),\n proxy_host: z.string().optional(),\n }),\n ])\n .optional(),\n});\n\nexport type Settings = z.infer<typeof SettingsSchema>;\n","/* eslint-disable max-lines */\nimport { execSync } from 'node:child_process';\nimport fs from 'node:fs';\nimport fsPromises from 'node:fs/promises';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport {\n type Agent,\n AgentSchema,\n type ChatSettings,\n ChatSettingsSchema,\n type AgentSessionSettings,\n AgentSessionSettingsSchema,\n type Environment,\n EnvironmentSchema,\n type Settings,\n SettingsSchema,\n} from './config.js';\nimport { pathIsInsideDir } from './utils/fs.js';\n\nexport function getWorkspaceRoot(startDir = process.cwd()): string {\n let curr = startDir;\n while (curr !== path.parse(curr).root) {\n if (fs.existsSync(path.join(curr, '.clawmini'))) {\n return curr;\n }\n if (fs.existsSync(path.join(curr, 'package.json')) || fs.existsSync(path.join(curr, '.git'))) {\n return curr;\n }\n curr = path.dirname(curr);\n }\n return startDir;\n}\n\nexport function resolveAgentWorkDir(\n agentId: string,\n customDir?: string,\n startDir = process.cwd()\n): string {\n const workspaceRoot = getWorkspaceRoot(startDir);\n const dirPath = customDir\n ? path.resolve(workspaceRoot, customDir)\n : path.resolve(workspaceRoot, agentId);\n\n if (!pathIsInsideDir(dirPath, workspaceRoot, { allowSameDir: true })) {\n throw new Error('Invalid agent directory: resolves outside the workspace.');\n }\n\n return dirPath;\n}\n\nexport async function ensureAgentWorkDir(\n agentId: string,\n customDir?: string,\n startDir = process.cwd()\n): Promise<string> {\n const dirPath = resolveAgentWorkDir(agentId, customDir, startDir);\n\n if (!fs.existsSync(dirPath)) {\n await fsPromises.mkdir(dirPath, { recursive: true });\n console.log(`Created agent working directory at ${dirPath}`);\n }\n return dirPath;\n}\n\nexport function getClawminiDir(startDir = process.cwd()): string {\n return path.join(getWorkspaceRoot(startDir), '.clawmini');\n}\n\nexport function getSocketPath(startDir = process.cwd()): string {\n return path.join(getClawminiDir(startDir), 'server.sock');\n}\n\nexport function getSettingsPath(startDir = process.cwd()): string {\n return path.join(getClawminiDir(startDir), 'settings.json');\n}\n\nexport function getPoliciesPath(startDir = process.cwd()): string {\n return path.join(getClawminiDir(startDir), 'policies.json');\n}\n\nexport function getChatSettingsPath(chatId: string, startDir = process.cwd()): string {\n return path.join(getClawminiDir(startDir), 'chats', chatId, 'settings.json');\n}\n\nexport function isValidAgentId(agentId: string): boolean {\n if (!agentId || agentId.length === 0) return false;\n return /^[a-zA-Z0-9_]+(?:-[a-zA-Z0-9_]+)*$/.test(agentId);\n}\n\nexport function getAgentDir(agentId: string, startDir = process.cwd()): string {\n if (!isValidAgentId(agentId)) {\n throw new Error(`Invalid agent ID: ${agentId}`);\n }\n return path.join(getClawminiDir(startDir), 'agents', agentId);\n}\n\nexport function getAgentSettingsPath(agentId: string, startDir = process.cwd()): string {\n return path.join(getAgentDir(agentId, startDir), 'settings.json');\n}\n\nexport function getAgentSessionSettingsPath(\n agentId: string,\n sessionId: string,\n startDir = process.cwd()\n): string {\n if (!isValidAgentId(agentId)) {\n throw new Error(`Invalid agent ID: ${agentId}`);\n }\n return path.join(\n getClawminiDir(startDir),\n 'agents',\n agentId,\n 'sessions',\n sessionId,\n 'settings.json'\n );\n}\n\nasync function readJsonFile(filePath: string): Promise<Record<string, unknown> | null> {\n try {\n const data = await fsPromises.readFile(filePath, 'utf-8');\n return JSON.parse(data) as Record<string, unknown>;\n } catch {\n return null;\n }\n}\n\nasync function writeJsonFile(filePath: string, data: Record<string, unknown>): Promise<void> {\n const dir = path.dirname(filePath);\n await fsPromises.mkdir(dir, { recursive: true });\n await fsPromises.writeFile(filePath, JSON.stringify(data, null, 2), 'utf-8');\n}\n\nexport async function readChatSettings(\n chatId: string,\n startDir = process.cwd()\n): Promise<ChatSettings | null> {\n const data = await readJsonFile(getChatSettingsPath(chatId, startDir));\n if (!data) return null;\n const parsed = ChatSettingsSchema.safeParse(data);\n return parsed.success ? parsed.data : null;\n}\n\nexport async function writeChatSettings(\n chatId: string,\n data: ChatSettings,\n startDir = process.cwd()\n): Promise<void> {\n await writeJsonFile(getChatSettingsPath(chatId, startDir), data as Record<string, unknown>);\n}\n\nexport async function readAgentSessionSettings(\n agentId: string,\n sessionId: string,\n startDir = process.cwd()\n): Promise<AgentSessionSettings | null> {\n const data = await readJsonFile(getAgentSessionSettingsPath(agentId, sessionId, startDir));\n if (!data) return null;\n const parsed = AgentSessionSettingsSchema.safeParse(data);\n return parsed.success ? parsed.data : null;\n}\n\nexport async function writeAgentSessionSettings(\n agentId: string,\n sessionId: string,\n data: AgentSessionSettings,\n startDir = process.cwd()\n): Promise<void> {\n await writeJsonFile(\n getAgentSessionSettingsPath(agentId, sessionId, startDir),\n data as Record<string, unknown>\n );\n}\n\nexport async function getAgent(agentId: string, startDir = process.cwd()): Promise<Agent | null> {\n const filePath = getAgentSettingsPath(agentId, startDir);\n let dataStr: string;\n try {\n dataStr = await fsPromises.readFile(filePath, 'utf-8');\n } catch (err: unknown) {\n if (err && typeof err === 'object' && 'code' in err && err.code === 'ENOENT') return null;\n throw err;\n }\n\n let data: unknown;\n try {\n data = JSON.parse(dataStr);\n } catch (parseErr: unknown) {\n const message = parseErr instanceof Error ? parseErr.message : String(parseErr);\n throw new Error(`Invalid JSON in ${filePath}: ${message}`, { cause: parseErr });\n }\n\n const parsed = AgentSchema.safeParse(data);\n if (!parsed.success) {\n throw new Error(`Invalid schema in ${filePath}: ${parsed.error.message}`);\n }\n return parsed.data;\n}\n\nexport async function writeAgentSettings(\n agentId: string,\n data: Agent,\n startDir = process.cwd()\n): Promise<void> {\n await ensureAgentWorkDir(agentId, data.directory, startDir);\n await writeJsonFile(getAgentSettingsPath(agentId, startDir), data as Record<string, unknown>);\n}\n\nexport async function listAgents(startDir = process.cwd()): Promise<string[]> {\n const agentsDir = path.join(getClawminiDir(startDir), 'agents');\n try {\n const entries = await fsPromises.readdir(agentsDir, { withFileTypes: true });\n const agentIds = [];\n for (const entry of entries) {\n if (entry.isDirectory()) {\n const settingsPath = path.join(agentsDir, entry.name, 'settings.json');\n try {\n await fsPromises.access(settingsPath);\n agentIds.push(entry.name);\n } catch {\n // No settings.json, probably just a sessions dir for a non-existent agent or default agent\n }\n }\n }\n return agentIds;\n } catch {\n return [];\n }\n}\n\nexport async function deleteAgent(agentId: string, startDir = process.cwd()): Promise<void> {\n const dir = getAgentDir(agentId, startDir);\n const agentsDir = path.join(getClawminiDir(startDir), 'agents');\n\n if (!pathIsInsideDir(dir, agentsDir)) {\n throw new Error(`Security Error: Cannot delete agent directory outside of ${agentsDir}`);\n }\n\n try {\n await fsPromises.rm(dir, { recursive: true, force: true });\n } catch {\n // Ignore if not found\n }\n}\n\nasync function isDirectory(dirPath: string): Promise<boolean> {\n try {\n const stat = await fsPromises.stat(dirPath);\n return stat.isDirectory();\n } catch {\n return false;\n }\n}\n\nexport async function resolveTemplatePathBase(\n templateName: string,\n startDir = process.cwd()\n): Promise<string> {\n const workspaceRoot = getWorkspaceRoot(startDir);\n const localTemplatePath = path.join(workspaceRoot, '.clawmini', 'templates', templateName);\n\n if (await isDirectory(localTemplatePath)) {\n return localTemplatePath;\n }\n\n // Fallback to built-in templates\n // Find the clawmini package root by looking for package.json\n let currentDir = path.dirname(fileURLToPath(import.meta.url));\n while (\n currentDir !== path.parse(currentDir).root &&\n !fs.existsSync(path.join(currentDir, 'package.json'))\n ) {\n currentDir = path.dirname(currentDir);\n }\n\n const searchPath = path.join(currentDir, 'templates', templateName);\n\n if (await isDirectory(searchPath)) {\n return searchPath;\n }\n\n throw new Error(\n `Template not found: ${templateName} (searched local: ${localTemplatePath}, built-in: ${searchPath})`\n );\n}\n\nexport async function resolveTemplatePath(\n templateName: string,\n startDir = process.cwd()\n): Promise<string> {\n if (templateName === 'environments' || templateName.startsWith('environments/')) {\n throw new Error(`Template not found: ${templateName}`);\n }\n return resolveTemplatePathBase(templateName, startDir);\n}\n\nexport async function resolveEnvironmentTemplatePath(\n templateName: string,\n startDir = process.cwd()\n): Promise<string> {\n return resolveTemplatePathBase(path.join('environments', templateName), startDir);\n}\n\nexport async function copyTemplateBase(\n templatePath: string,\n targetDir: string,\n allowMissingDir: boolean = false\n): Promise<void> {\n // Check if target directory exists and is not empty\n try {\n const entries = await fsPromises.readdir(targetDir);\n if (entries.length > 0) {\n throw new Error(`Target directory is not empty: ${targetDir}`);\n }\n } catch (err: unknown) {\n if (err && typeof err === 'object' && 'code' in err && err.code === 'ENOENT') {\n if (allowMissingDir) {\n await fsPromises.mkdir(targetDir, { recursive: true });\n } else {\n throw new Error(`Target directory does not exist: ${targetDir}`, { cause: err });\n }\n } else {\n throw err;\n }\n }\n\n // Recursively copy\n await fsPromises.cp(templatePath, targetDir, { recursive: true });\n}\n\nexport async function copyTemplate(\n templateName: string,\n targetDir: string,\n startDir = process.cwd()\n): Promise<void> {\n const templatePath = await resolveTemplatePath(templateName, startDir);\n await copyTemplateBase(templatePath, targetDir, false);\n}\n\nexport async function copyEnvironmentTemplate(\n templateName: string,\n targetDir: string,\n startDir = process.cwd()\n): Promise<void> {\n const templatePath = await resolveEnvironmentTemplatePath(templateName, startDir);\n await copyTemplateBase(templatePath, targetDir, true);\n}\n\nexport async function applyTemplateToAgent(\n agentId: string,\n templateName: string,\n overrides: Agent,\n startDir = process.cwd()\n): Promise<void> {\n const agentWorkDir = resolveAgentWorkDir(agentId, overrides.directory, startDir);\n await copyTemplate(templateName, agentWorkDir, startDir);\n\n const settingsPath = path.join(agentWorkDir, 'settings.json');\n try {\n const rawSettings = await fsPromises.readFile(settingsPath, 'utf-8');\n const parsedSettings = JSON.parse(rawSettings);\n const validation = AgentSchema.safeParse(parsedSettings);\n\n if (validation.success) {\n const templateData = validation.data;\n if (templateData.directory) {\n console.warn(\n `Warning: Ignoring 'directory' field from template settings.json. Using default or provided directory.`\n );\n delete templateData.directory;\n }\n\n // Merge: overrides take precedence over templateData\n const mergedEnv = { ...(templateData.env || {}), ...(overrides.env || {}) };\n const mergedData: Agent = { ...templateData, ...overrides };\n if (Object.keys(mergedEnv).length > 0) {\n mergedData.env = mergedEnv;\n }\n\n await writeAgentSettings(agentId, mergedData, startDir);\n }\n } catch {\n // Ignore parsing or file not found errors\n } finally {\n try {\n await fsPromises.rm(settingsPath);\n } catch {\n // Ignore if it doesn't exist\n }\n }\n}\n\nexport async function readSettings(startDir = process.cwd()): Promise<Settings | null> {\n const data = await readJsonFile(getSettingsPath(startDir));\n if (!data) return null;\n const parsed = SettingsSchema.safeParse(data);\n return parsed.success ? parsed.data : null;\n}\n\nexport async function writeSettings(data: Settings, startDir = process.cwd()): Promise<void> {\n await writeJsonFile(getSettingsPath(startDir), data as Record<string, unknown>);\n}\n\nexport async function readPolicies(\n startDir = process.cwd()\n): Promise<import('./policies.js').PolicyConfig | null> {\n const data = await readJsonFile(getPoliciesPath(startDir));\n if (!data) return null;\n // Basic validation, assuming PolicyConfig structure\n if (data.policies && typeof data.policies === 'object') {\n return data as unknown as import('./policies.js').PolicyConfig;\n }\n return null;\n}\n\nexport function getEnvironmentPath(name: string, startDir = process.cwd()): string {\n return path.join(getClawminiDir(startDir), 'environments', name);\n}\n\nexport async function readEnvironment(\n name: string,\n startDir = process.cwd()\n): Promise<Environment | null> {\n const data = await readJsonFile(path.join(getEnvironmentPath(name, startDir), 'env.json'));\n if (!data) return null;\n const parsed = EnvironmentSchema.safeParse(data);\n return parsed.success ? parsed.data : null;\n}\n\nexport async function getActiveEnvironmentInfo(\n targetPath: string,\n startDir = process.cwd()\n): Promise<{ name: string; targetPath: string } | null> {\n const settings = await readSettings(startDir);\n if (!settings?.environments) return null;\n\n const workspaceRoot = getWorkspaceRoot(startDir);\n const resolvedTarget = path.resolve(workspaceRoot, targetPath);\n\n let bestMatch: { name: string; targetPath: string } | null = null;\n let maxDepth = -1;\n\n for (const [envPath, envName] of Object.entries(settings.environments)) {\n const resolvedEnvPath = path.resolve(workspaceRoot, envPath);\n\n if (pathIsInsideDir(resolvedTarget, resolvedEnvPath, { allowSameDir: true })) {\n const depth = resolvedEnvPath.split(path.sep).length;\n if (depth > maxDepth) {\n maxDepth = depth;\n bestMatch = { name: envName, targetPath: resolvedEnvPath };\n }\n }\n }\n\n return bestMatch;\n}\n\nexport async function getActiveEnvironmentName(\n targetPath: string,\n startDir = process.cwd()\n): Promise<string | null> {\n const info = await getActiveEnvironmentInfo(targetPath, startDir);\n return info ? info.name : null;\n}\n\nexport async function enableEnvironment(\n name: string,\n targetPath: string = './',\n startDir = process.cwd()\n): Promise<void> {\n const targetDir = getEnvironmentPath(name, startDir);\n\n // Copy template to targetDir if it does not already exist\n if (!fs.existsSync(targetDir)) {\n await copyEnvironmentTemplate(name, targetDir, startDir);\n console.log(`Copied environment template '${name}'.`);\n } else {\n console.log(`Environment template '${name}' already exists in workspace.`);\n }\n\n const settings = (await readSettings(startDir)) || { chats: { defaultId: '' } };\n const environments = settings.environments || {};\n\n environments[targetPath] = name;\n settings.environments = environments;\n\n await writeSettings(settings, startDir);\n console.log(`Enabled environment '${name}' for path '${targetPath}'.`);\n\n // Execute init command if present\n const envConfig = await readEnvironment(name, startDir);\n if (envConfig?.init) {\n // Get the target directory for the environment\n const workspaceRoot = getWorkspaceRoot(startDir);\n const affectedDir = path.resolve(workspaceRoot, targetPath);\n console.log(`Executing init command for environment '${name}': ${envConfig.init}`);\n execSync(envConfig.init, { cwd: affectedDir, stdio: 'inherit' });\n }\n}\n"],"mappings":"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"}
|
package/src/daemon/router.ts
DELETED
|
@@ -1,510 +0,0 @@
|
|
|
1
|
-
/* eslint-disable max-lines */
|
|
2
|
-
import { initTRPC, TRPCError } from '@trpc/server';
|
|
3
|
-
import { z } from 'zod';
|
|
4
|
-
import fs from 'node:fs/promises';
|
|
5
|
-
import path from 'node:path';
|
|
6
|
-
import { daemonEvents, DAEMON_EVENT_MESSAGE_APPENDED, DAEMON_EVENT_TYPING } from './events.js';
|
|
7
|
-
import {
|
|
8
|
-
getSettingsPath,
|
|
9
|
-
readChatSettings,
|
|
10
|
-
writeChatSettings,
|
|
11
|
-
getAgent,
|
|
12
|
-
getWorkspaceRoot,
|
|
13
|
-
readPolicies,
|
|
14
|
-
getClawminiDir,
|
|
15
|
-
} from '../shared/workspace.js';
|
|
16
|
-
import { PolicyRequestService } from './policy-request-service.js';
|
|
17
|
-
import { RequestStore } from './request-store.js';
|
|
18
|
-
import { CronJobSchema } from '../shared/config.js';
|
|
19
|
-
import { handleUserMessage } from './message.js';
|
|
20
|
-
import { getDefaultChatId, getMessages } from './chats.js';
|
|
21
|
-
import { runCommand } from './utils/spawn.js';
|
|
22
|
-
import { cronManager } from './cron.js';
|
|
23
|
-
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
24
|
-
import type { TokenPayload } from './auth.js';
|
|
25
|
-
|
|
26
|
-
export interface Context {
|
|
27
|
-
req?: IncomingMessage | undefined;
|
|
28
|
-
res?: ServerResponse | undefined;
|
|
29
|
-
isApiServer?: boolean | undefined;
|
|
30
|
-
tokenPayload?: TokenPayload | null | undefined;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const t = initTRPC.context<Context>().create();
|
|
34
|
-
export const router = t.router;
|
|
35
|
-
export const publicProcedure = t.procedure;
|
|
36
|
-
|
|
37
|
-
const apiAuthMiddleware = t.middleware(({ ctx, next }) => {
|
|
38
|
-
if (ctx.isApiServer) {
|
|
39
|
-
if (!ctx.tokenPayload) {
|
|
40
|
-
throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Missing or invalid token' });
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return next({
|
|
44
|
-
ctx: {
|
|
45
|
-
...ctx,
|
|
46
|
-
tokenPayload: ctx.tokenPayload,
|
|
47
|
-
},
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
export const apiProcedure = t.procedure.use(apiAuthMiddleware);
|
|
52
|
-
|
|
53
|
-
export async function getUniquePath(p: string): Promise<string> {
|
|
54
|
-
let currentPath = p;
|
|
55
|
-
let counter = 1;
|
|
56
|
-
while (true) {
|
|
57
|
-
try {
|
|
58
|
-
await fs.stat(currentPath);
|
|
59
|
-
const ext = path.extname(p);
|
|
60
|
-
const base = path.basename(p, ext);
|
|
61
|
-
currentPath = path.join(path.dirname(p), `${base}-${counter}${ext}`);
|
|
62
|
-
counter++;
|
|
63
|
-
} catch {
|
|
64
|
-
return currentPath;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async function resolveAgentDir(
|
|
70
|
-
agentId: string | undefined | null,
|
|
71
|
-
workspaceRoot: string
|
|
72
|
-
): Promise<string> {
|
|
73
|
-
if (agentId && agentId !== 'default') {
|
|
74
|
-
try {
|
|
75
|
-
const agent = await getAgent(agentId, workspaceRoot);
|
|
76
|
-
if (agent && agent.directory) {
|
|
77
|
-
return path.resolve(workspaceRoot, agent.directory);
|
|
78
|
-
}
|
|
79
|
-
} catch (err: unknown) {
|
|
80
|
-
console.warn(`Could not load custom agent '${agentId}' for resolving directory:`, err);
|
|
81
|
-
}
|
|
82
|
-
return path.resolve(workspaceRoot, agentId);
|
|
83
|
-
}
|
|
84
|
-
return workspaceRoot;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
async function resolveAndCheckChatId(ctx: Context, inputChatId?: string): Promise<string> {
|
|
88
|
-
const chatId =
|
|
89
|
-
inputChatId ??
|
|
90
|
-
(ctx.isApiServer && ctx.tokenPayload ? ctx.tokenPayload.chatId : await getDefaultChatId());
|
|
91
|
-
|
|
92
|
-
if (ctx.isApiServer && ctx.tokenPayload) {
|
|
93
|
-
if (ctx.tokenPayload.chatId !== chatId) {
|
|
94
|
-
throw new TRPCError({ code: 'FORBIDDEN', message: 'Token not authorized for this chat' });
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return chatId;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
async function getAgentFilesDir(
|
|
102
|
-
agentId: string | undefined,
|
|
103
|
-
chatId: string,
|
|
104
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
105
|
-
settings: any,
|
|
106
|
-
workspaceRoot: string
|
|
107
|
-
): Promise<string> {
|
|
108
|
-
const chatSettings = (await readChatSettings(chatId)) ?? {};
|
|
109
|
-
const targetAgentId = agentId ?? chatSettings.defaultAgent ?? 'default';
|
|
110
|
-
let agentFilesDir = settings?.defaultAgent?.files || './attachments';
|
|
111
|
-
const agentDir = await resolveAgentDir(targetAgentId, workspaceRoot);
|
|
112
|
-
|
|
113
|
-
if (targetAgentId !== 'default') {
|
|
114
|
-
try {
|
|
115
|
-
const customAgent = await getAgent(targetAgentId, workspaceRoot);
|
|
116
|
-
if (customAgent?.files) {
|
|
117
|
-
agentFilesDir = customAgent.files;
|
|
118
|
-
}
|
|
119
|
-
} catch (err: unknown) {
|
|
120
|
-
console.warn(
|
|
121
|
-
`Could not load custom agent '${targetAgentId}' for resolving files directory:`,
|
|
122
|
-
err
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return path.resolve(agentDir, agentFilesDir);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
async function validateAttachments(files: string[]): Promise<void> {
|
|
131
|
-
const { pathIsInsideDir } = await import('../shared/utils/fs.js');
|
|
132
|
-
const { getClawminiDir } = await import('../shared/workspace.js');
|
|
133
|
-
const tmpDir = path.join(getClawminiDir(process.cwd()), 'tmp');
|
|
134
|
-
|
|
135
|
-
for (const file of files) {
|
|
136
|
-
const absoluteFile = path.resolve(process.cwd(), file);
|
|
137
|
-
if (!pathIsInsideDir(absoluteFile, tmpDir)) {
|
|
138
|
-
throw new TRPCError({
|
|
139
|
-
code: 'BAD_REQUEST',
|
|
140
|
-
message: 'File must be inside the temporary directory.',
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
try {
|
|
144
|
-
await fs.access(absoluteFile);
|
|
145
|
-
} catch {
|
|
146
|
-
throw new TRPCError({
|
|
147
|
-
code: 'BAD_REQUEST',
|
|
148
|
-
message: `File does not exist: ${file}`,
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
async function validateLogFile(
|
|
155
|
-
file: string,
|
|
156
|
-
agentDir: string,
|
|
157
|
-
workspaceRoot: string
|
|
158
|
-
): Promise<string> {
|
|
159
|
-
const { pathIsInsideDir } = await import('../shared/utils/fs.js');
|
|
160
|
-
// The agent sends paths relative to its working directory.
|
|
161
|
-
// We resolve to an absolute path to verify it is within the agent's directory.
|
|
162
|
-
const resolvedPath = path.resolve(agentDir, file);
|
|
163
|
-
|
|
164
|
-
if (!pathIsInsideDir(resolvedPath, agentDir, { allowSameDir: true })) {
|
|
165
|
-
throw new TRPCError({
|
|
166
|
-
code: 'BAD_REQUEST',
|
|
167
|
-
message: 'File must be within the agent workspace.',
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
try {
|
|
172
|
-
await fs.access(resolvedPath);
|
|
173
|
-
} catch {
|
|
174
|
-
throw new TRPCError({
|
|
175
|
-
code: 'BAD_REQUEST',
|
|
176
|
-
message: `File does not exist: ${file}`,
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Convert the absolute path to a path relative to the WORKSPACE directory.
|
|
181
|
-
// This allows adapters (like discord-adapter) to easily resolve it against their own view of the workspace.
|
|
182
|
-
return path.relative(workspaceRoot, resolvedPath);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const AppRouter = router({
|
|
186
|
-
sendMessage: apiProcedure
|
|
187
|
-
.input(
|
|
188
|
-
z.object({
|
|
189
|
-
type: z.literal('send-message'),
|
|
190
|
-
client: z.literal('cli'),
|
|
191
|
-
data: z.object({
|
|
192
|
-
message: z.string(),
|
|
193
|
-
chatId: z.string().optional(),
|
|
194
|
-
sessionId: z.string().optional(),
|
|
195
|
-
agentId: z.string().optional(),
|
|
196
|
-
noWait: z.boolean().optional(),
|
|
197
|
-
files: z.array(z.string()).optional(),
|
|
198
|
-
adapter: z.string().optional(),
|
|
199
|
-
}),
|
|
200
|
-
})
|
|
201
|
-
)
|
|
202
|
-
.mutation(async ({ input, ctx }) => {
|
|
203
|
-
let message = input.data.message;
|
|
204
|
-
const chatId = await resolveAndCheckChatId(ctx, input.data.chatId);
|
|
205
|
-
const noWait = input.data.noWait ?? false;
|
|
206
|
-
const sessionId = input.data.sessionId;
|
|
207
|
-
const agentId = input.data.agentId;
|
|
208
|
-
const settingsPath = getSettingsPath();
|
|
209
|
-
|
|
210
|
-
let settings;
|
|
211
|
-
try {
|
|
212
|
-
const settingsStr = await fs.readFile(settingsPath, 'utf8');
|
|
213
|
-
settings = JSON.parse(settingsStr);
|
|
214
|
-
} catch (err) {
|
|
215
|
-
throw new Error(`Failed to read settings from ${settingsPath}: ${err}`, { cause: err });
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const files = input.data.files;
|
|
219
|
-
if (files && files.length > 0) {
|
|
220
|
-
const workspaceRoot = getWorkspaceRoot(process.cwd());
|
|
221
|
-
const chatSettings = (await readChatSettings(chatId)) ?? {};
|
|
222
|
-
const targetAgentId = agentId ?? chatSettings.defaultAgent ?? 'default';
|
|
223
|
-
const agentDir = await resolveAgentDir(targetAgentId, workspaceRoot);
|
|
224
|
-
const absoluteFilesDir = await getAgentFilesDir(agentId, chatId, settings, workspaceRoot);
|
|
225
|
-
|
|
226
|
-
const adapterNamespace = input.data.adapter || 'cli';
|
|
227
|
-
const targetDir = path.join(absoluteFilesDir, adapterNamespace);
|
|
228
|
-
|
|
229
|
-
const { pathIsInsideDir } = await import('../shared/utils/fs.js');
|
|
230
|
-
|
|
231
|
-
if (!pathIsInsideDir(targetDir, workspaceRoot, { allowSameDir: true })) {
|
|
232
|
-
throw new TRPCError({
|
|
233
|
-
code: 'BAD_REQUEST',
|
|
234
|
-
message: 'Target directory must be within the workspace.',
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
await validateAttachments(files);
|
|
239
|
-
|
|
240
|
-
await fs.mkdir(targetDir, { recursive: true });
|
|
241
|
-
|
|
242
|
-
const finalPaths: string[] = [];
|
|
243
|
-
for (const file of files) {
|
|
244
|
-
const fileName = path.basename(file);
|
|
245
|
-
const targetPath = await getUniquePath(path.join(targetDir, fileName));
|
|
246
|
-
|
|
247
|
-
try {
|
|
248
|
-
await fs.rename(file, targetPath);
|
|
249
|
-
} catch {
|
|
250
|
-
await fs.copyFile(file, targetPath);
|
|
251
|
-
await fs.unlink(file);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
finalPaths.push(path.relative(agentDir, targetPath));
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
const fileList = `Attached files:\n${finalPaths.map((p) => `- ${p}`).join('\n')}`;
|
|
258
|
-
message = message ? `${message}\n\n${fileList}` : fileList;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
await handleUserMessage(
|
|
262
|
-
chatId,
|
|
263
|
-
message,
|
|
264
|
-
settings,
|
|
265
|
-
undefined,
|
|
266
|
-
noWait,
|
|
267
|
-
(args) => runCommand({ ...args, logToTerminal: true }),
|
|
268
|
-
sessionId,
|
|
269
|
-
agentId
|
|
270
|
-
);
|
|
271
|
-
|
|
272
|
-
return { success: true };
|
|
273
|
-
}),
|
|
274
|
-
getMessages: apiProcedure
|
|
275
|
-
.input(z.object({ chatId: z.string().optional(), limit: z.number().optional() }))
|
|
276
|
-
.query(async ({ input, ctx }) => {
|
|
277
|
-
const chatId = await resolveAndCheckChatId(ctx, input.chatId);
|
|
278
|
-
return getMessages(chatId, input.limit);
|
|
279
|
-
}),
|
|
280
|
-
waitForMessages: apiProcedure
|
|
281
|
-
.input(
|
|
282
|
-
z.object({
|
|
283
|
-
chatId: z.string().optional(),
|
|
284
|
-
lastMessageId: z.string().optional(),
|
|
285
|
-
})
|
|
286
|
-
)
|
|
287
|
-
.subscription(async function* ({ input, ctx, signal }) {
|
|
288
|
-
const chatId = await resolveAndCheckChatId(ctx, input.chatId);
|
|
289
|
-
|
|
290
|
-
// 1. Check if there are already new messages
|
|
291
|
-
if (input.lastMessageId) {
|
|
292
|
-
const messages = await getMessages(chatId);
|
|
293
|
-
const lastIndex = messages.findIndex((m) => m.id === input.lastMessageId);
|
|
294
|
-
if (lastIndex !== -1 && lastIndex < messages.length - 1) {
|
|
295
|
-
yield messages.slice(lastIndex + 1);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// 2. Listen for new messages
|
|
300
|
-
const { on } = await import('node:events');
|
|
301
|
-
try {
|
|
302
|
-
for await (const [event] of on(daemonEvents, DAEMON_EVENT_MESSAGE_APPENDED, { signal })) {
|
|
303
|
-
if (event.chatId === chatId) {
|
|
304
|
-
yield [event.message];
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
} catch (err) {
|
|
308
|
-
if (err instanceof Error && err.name === 'AbortError') {
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
throw err;
|
|
312
|
-
}
|
|
313
|
-
}),
|
|
314
|
-
waitForTyping: apiProcedure
|
|
315
|
-
.input(
|
|
316
|
-
z.object({
|
|
317
|
-
chatId: z.string().optional(),
|
|
318
|
-
})
|
|
319
|
-
)
|
|
320
|
-
.subscription(async function* ({ input, ctx, signal }) {
|
|
321
|
-
const chatId = await resolveAndCheckChatId(ctx, input.chatId);
|
|
322
|
-
|
|
323
|
-
const { on } = await import('node:events');
|
|
324
|
-
try {
|
|
325
|
-
for await (const [event] of on(daemonEvents, DAEMON_EVENT_TYPING, { signal })) {
|
|
326
|
-
if (event.chatId === chatId) {
|
|
327
|
-
yield event;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
} catch (err) {
|
|
331
|
-
if (err instanceof Error && err.name === 'AbortError') {
|
|
332
|
-
return;
|
|
333
|
-
}
|
|
334
|
-
throw err;
|
|
335
|
-
}
|
|
336
|
-
}),
|
|
337
|
-
ping: publicProcedure.query(() => {
|
|
338
|
-
return { status: 'ok' };
|
|
339
|
-
}),
|
|
340
|
-
shutdown: publicProcedure.mutation(() => {
|
|
341
|
-
// Schedule a shutdown shortly after the response is sent
|
|
342
|
-
setTimeout(() => {
|
|
343
|
-
console.log('Shutting down daemon...');
|
|
344
|
-
process.kill(process.pid, 'SIGTERM');
|
|
345
|
-
}, 100);
|
|
346
|
-
return { success: true };
|
|
347
|
-
}),
|
|
348
|
-
logMessage: apiProcedure
|
|
349
|
-
.input(
|
|
350
|
-
z.object({
|
|
351
|
-
chatId: z.string().optional(),
|
|
352
|
-
message: z.string().optional(),
|
|
353
|
-
files: z.array(z.string()).optional(),
|
|
354
|
-
})
|
|
355
|
-
)
|
|
356
|
-
.mutation(async ({ input, ctx }) => {
|
|
357
|
-
const chatId = await resolveAndCheckChatId(ctx, input.chatId);
|
|
358
|
-
const timestamp = new Date().toISOString();
|
|
359
|
-
const id = Date.now().toString() + Math.random().toString(36).substring(2, 7);
|
|
360
|
-
|
|
361
|
-
const filePaths: string[] = [];
|
|
362
|
-
if (input.files && input.files.length > 0) {
|
|
363
|
-
const workspaceRoot = getWorkspaceRoot(process.cwd());
|
|
364
|
-
const agentDir = await resolveAgentDir(ctx.tokenPayload?.agentId, workspaceRoot);
|
|
365
|
-
|
|
366
|
-
for (const file of input.files) {
|
|
367
|
-
const validPath = await validateLogFile(file, agentDir, workspaceRoot);
|
|
368
|
-
filePaths.push(validPath);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
const filesArgStr = filePaths.map((p) => ` --file ${p}`).join('');
|
|
373
|
-
const messageStr = input.message || '';
|
|
374
|
-
const logMsg: import('./chats.js').CommandLogMessage = {
|
|
375
|
-
id,
|
|
376
|
-
messageId: id,
|
|
377
|
-
role: 'log',
|
|
378
|
-
source: 'router',
|
|
379
|
-
content: messageStr,
|
|
380
|
-
stderr: '',
|
|
381
|
-
timestamp,
|
|
382
|
-
command: `clawmini-lite log${filesArgStr}`,
|
|
383
|
-
cwd: process.cwd(),
|
|
384
|
-
exitCode: 0,
|
|
385
|
-
...(filePaths.length > 0 ? { files: filePaths } : {}),
|
|
386
|
-
};
|
|
387
|
-
|
|
388
|
-
await import('./chats.js').then((m) => m.appendMessage(chatId, logMsg));
|
|
389
|
-
return { success: true };
|
|
390
|
-
}),
|
|
391
|
-
listCronJobs: apiProcedure
|
|
392
|
-
.input(z.object({ chatId: z.string().optional() }))
|
|
393
|
-
.query(async ({ input, ctx }) => {
|
|
394
|
-
const chatId = await resolveAndCheckChatId(ctx, input.chatId);
|
|
395
|
-
const settings = await readChatSettings(chatId);
|
|
396
|
-
return settings?.jobs ?? [];
|
|
397
|
-
}),
|
|
398
|
-
addCronJob: apiProcedure
|
|
399
|
-
.input(z.object({ chatId: z.string().optional(), job: CronJobSchema }))
|
|
400
|
-
.mutation(async ({ input, ctx }) => {
|
|
401
|
-
const chatId = await resolveAndCheckChatId(ctx, input.chatId);
|
|
402
|
-
const settings = (await readChatSettings(chatId)) || {};
|
|
403
|
-
const cronJobs = settings.jobs ?? [];
|
|
404
|
-
const existingIndex = cronJobs.findIndex((j) => j.id === input.job.id);
|
|
405
|
-
if (existingIndex >= 0) {
|
|
406
|
-
cronJobs[existingIndex] = input.job;
|
|
407
|
-
} else {
|
|
408
|
-
cronJobs.push(input.job);
|
|
409
|
-
}
|
|
410
|
-
settings.jobs = cronJobs;
|
|
411
|
-
await writeChatSettings(chatId, settings);
|
|
412
|
-
cronManager.scheduleJob(chatId, input.job);
|
|
413
|
-
return { success: true };
|
|
414
|
-
}),
|
|
415
|
-
deleteCronJob: apiProcedure
|
|
416
|
-
.input(z.object({ chatId: z.string().optional(), id: z.string() }))
|
|
417
|
-
.mutation(async ({ input, ctx }) => {
|
|
418
|
-
const chatId = await resolveAndCheckChatId(ctx, input.chatId);
|
|
419
|
-
const settings = await readChatSettings(chatId);
|
|
420
|
-
if (!settings || !settings.jobs) {
|
|
421
|
-
return { success: true, deleted: false };
|
|
422
|
-
}
|
|
423
|
-
const initialLength = settings.jobs.length;
|
|
424
|
-
settings.jobs = settings.jobs.filter((j) => j.id !== input.id);
|
|
425
|
-
if (settings.jobs.length !== initialLength) {
|
|
426
|
-
await writeChatSettings(chatId, settings);
|
|
427
|
-
cronManager.unscheduleJob(chatId, input.id);
|
|
428
|
-
return { success: true, deleted: true };
|
|
429
|
-
}
|
|
430
|
-
return { success: true, deleted: false };
|
|
431
|
-
}),
|
|
432
|
-
listPolicies: apiProcedure.query(async () => {
|
|
433
|
-
return await readPolicies();
|
|
434
|
-
}),
|
|
435
|
-
executePolicyHelp: apiProcedure
|
|
436
|
-
.input(z.object({ commandName: z.string() }))
|
|
437
|
-
.query(async ({ input }) => {
|
|
438
|
-
const config = await readPolicies();
|
|
439
|
-
const policy = config?.policies?.[input.commandName];
|
|
440
|
-
|
|
441
|
-
if (!policy) {
|
|
442
|
-
throw new TRPCError({
|
|
443
|
-
code: 'NOT_FOUND',
|
|
444
|
-
message: `Policy not found: ${input.commandName}`,
|
|
445
|
-
});
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
if (!policy.allowHelp) {
|
|
449
|
-
return { stdout: '', stderr: 'This command does not support --help\n', exitCode: 1 };
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
const { executeSafe } = await import('./policy-utils.js');
|
|
453
|
-
const fullArgs = [...(policy.args || []), '--help'];
|
|
454
|
-
const { stdout, stderr, exitCode } = await executeSafe(policy.command, fullArgs, {
|
|
455
|
-
cwd: getWorkspaceRoot(),
|
|
456
|
-
});
|
|
457
|
-
|
|
458
|
-
return { stdout, stderr, exitCode };
|
|
459
|
-
}),
|
|
460
|
-
createPolicyRequest: apiProcedure
|
|
461
|
-
.input(
|
|
462
|
-
z.object({
|
|
463
|
-
commandName: z.string(),
|
|
464
|
-
args: z.array(z.string()),
|
|
465
|
-
fileMappings: z.record(z.string(), z.string()),
|
|
466
|
-
chatId: z.string().optional(),
|
|
467
|
-
})
|
|
468
|
-
)
|
|
469
|
-
.mutation(async ({ input, ctx }) => {
|
|
470
|
-
const workspaceRoot = getWorkspaceRoot(process.cwd());
|
|
471
|
-
const snapshotDir = path.join(getClawminiDir(process.cwd()), 'tmp', 'snapshots');
|
|
472
|
-
const store = new RequestStore(process.cwd());
|
|
473
|
-
const agentDir = await resolveAgentDir(ctx.tokenPayload?.agentId, workspaceRoot);
|
|
474
|
-
const service = new PolicyRequestService(store, agentDir, snapshotDir);
|
|
475
|
-
|
|
476
|
-
const chatId = await resolveAndCheckChatId(ctx, input.chatId);
|
|
477
|
-
const agentId = ctx.tokenPayload?.agentId ?? 'unknown';
|
|
478
|
-
|
|
479
|
-
const request = await service.createRequest(
|
|
480
|
-
input.commandName,
|
|
481
|
-
input.args,
|
|
482
|
-
input.fileMappings,
|
|
483
|
-
chatId,
|
|
484
|
-
agentId
|
|
485
|
-
);
|
|
486
|
-
|
|
487
|
-
const { generateRequestPreview } = await import('./policy-utils.js');
|
|
488
|
-
const previewContent = await generateRequestPreview(request);
|
|
489
|
-
|
|
490
|
-
const logMsg = {
|
|
491
|
-
id: (await import('node:crypto')).randomUUID(),
|
|
492
|
-
// TODO: we should store the message ID in the CLAW_API_TOKEN, and extract it here
|
|
493
|
-
messageId: (await import('node:crypto')).randomUUID(),
|
|
494
|
-
role: 'log' as const,
|
|
495
|
-
source: 'router' as const,
|
|
496
|
-
content: previewContent,
|
|
497
|
-
stderr: '',
|
|
498
|
-
timestamp: new Date().toISOString(),
|
|
499
|
-
command: 'policy-request',
|
|
500
|
-
cwd: process.cwd(),
|
|
501
|
-
exitCode: 0,
|
|
502
|
-
};
|
|
503
|
-
|
|
504
|
-
await import('./chats.js').then((m) => m.appendMessage(chatId, logMsg));
|
|
505
|
-
return request;
|
|
506
|
-
}),
|
|
507
|
-
});
|
|
508
|
-
|
|
509
|
-
export type AppRouter = typeof AppRouter;
|
|
510
|
-
export const appRouter = AppRouter;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{l as o,a as r}from"../chunks/B3YcEpQV.js";export{o as load_css,r as start};
|