UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

199 lines (198 loc) 7.1 kB
import { t as createSubsystemLogger } from "./subsystem-BzXSmsuh.js"; import { g as normalizeSkillIndexName } from "./workspace-_gXfieOh.js"; import { t as resolveSkillWorkshopConfig } from "./config-BzmFRVJh.js"; import { a as proposeUpdateSkill, f as readWorkspaceSkillFile, i as proposeCreateSkill, p as resolveSkillProposalTarget, r as listSkillProposals } from "./service-BlZrnDY7.js"; import { r as runAgentHarnessAgentEndHook, t as awaitAgentHarnessAgentEndHook } from "./lifecycle-hook-helpers-BiRlZKs7.js"; //#region src/skills/research/text.ts const TEXT_BLOCK_TYPES = new Set([ "text", "input_text", "output_text" ]); function readTextValue(value) { if (typeof value === "string") return value; if (value && typeof value === "object" && typeof value.value === "string") return value.value; return ""; } function extractTextBlock(block) { if (!block || typeof block !== "object") return ""; const type = block.type; if (typeof type !== "string" || !TEXT_BLOCK_TYPES.has(type)) return ""; return readTextValue(block.text); } function extractMessageText(content) { if (typeof content === "string") return content; if (Array.isArray(content)) return content.map(extractTextBlock).filter(Boolean).join("\n"); return extractTextBlock(content); } /** Extracts role/text pairs from mixed transcript message shapes. */ function extractTranscriptText(messages) { const result = []; for (const message of messages) { if (!message || typeof message !== "object") continue; const role = message.role; const content = message.content; if (typeof role !== "string") continue; const text = extractMessageText(content).trim(); if (text) result.push({ role, text }); } return result; } function compactWhitespace(value) { return value.replace(/\s+/g, " ").trim(); } //#endregion //#region src/skills/research/signals.ts const CORRECTION_PATTERNS = [ /\bnext time\b/i, /\bfrom now on\b/i, /\bremember to\b/i, /\bmake sure to\b/i, /\balways\b.{0,80}\b(use|check|verify|record|save|prefer)\b/i, /\bprefer\b.{0,120}\b(when|for|instead|use)\b/i, /\bwhen asked\b/i ]; function inferTopic(text) { const lower = text.toLowerCase(); if (/\banimated\b|\bgifs?\b/.test(lower)) return { skillName: "animated-gif-workflow", title: "Animated GIF Workflow", label: "animated GIF requests" }; if (/\bscreenshot|screen capture|imageoptim|asset\b/.test(lower)) return { skillName: "screenshot-asset-workflow", title: "Screenshot Asset Workflow", label: "screenshot asset updates" }; if (/\bqa\b|\bscenario\b|\btest plan\b/.test(lower)) return { skillName: "qa-scenario-workflow", title: "QA Scenario Workflow", label: "QA tasks" }; if (/\bpr\b|\bpull request\b|\bgithub\b/.test(lower)) return { skillName: "github-pr-workflow", title: "GitHub PR Workflow", label: "GitHub PR work" }; return { skillName: "learned-workflows", title: "Learned Workflows", label: "repeatable tasks" }; } function extractInstruction(text) { const trimmed = compactWhitespace(text); if (trimmed.length < 24 || trimmed.length > 1200) return; if (!CORRECTION_PATTERNS.some((pattern) => pattern.test(trimmed))) return; return trimmed.replace(/^ok[,. ]+/i, ""); } /** Extracts a candidate durable instruction from transcript text. */ function extractDurableInstructionProposal(params) { const instruction = extractTranscriptText(params.messages).filter((entry) => entry.role === "user").map((entry) => entry.text).map(extractInstruction).findLast(Boolean); if (!instruction) return; const topic = inferTopic(instruction); const skillName = normalizeSkillIndexName(topic.skillName); if (!skillName) return; return { skillName, description: `Reusable workflow notes for ${topic.label}.`, goal: `Capture durable user correction for ${topic.label}.`, evidence: instruction, content: [ `# ${topic.title}`, "", "## Workflow", "", `- ${instruction}`, "- Verify the result before final reply.", "- Record durable pitfalls as short bullets; avoid copying transcript noise." ].join("\n") }; } //#endregion //#region src/skills/research/autocapture.ts const log$1 = createSubsystemLogger("skills/research"); function buildAutoCaptureUpdateContent(existingSkill, capturedContent) { return [ existingSkill.trimEnd(), "", "## Captured Update", "", capturedContent.trim(), "" ].join("\n"); } /** Captures durable skill research signals from a session transcript when enabled. */ async function runSkillResearchAutoCapture(params) { if (!resolveSkillWorkshopConfig(params.config).autonomous.enabled) return; if (params.event.success === false) return; const workspaceDir = params.ctx.workspaceDir; if (!workspaceDir) return; const proposal = extractDurableInstructionProposal({ messages: params.event.messages }); if (!proposal) return; if ((await listSkillProposals({ workspaceDir })).proposals.some((entry) => (entry.status === "pending" || entry.status === "quarantined") && entry.skillKey === proposal.skillName)) return; try { const existingSkill = await readWorkspaceSkillFile(resolveSkillProposalTarget({ workspaceDir, skillName: proposal.skillName }).skillFile); const result = existingSkill === null ? await proposeCreateSkill({ workspaceDir, config: params.config, name: proposal.skillName, description: proposal.description, content: proposal.content, createdBy: "skill-workshop", goal: proposal.goal, evidence: proposal.evidence }) : await proposeUpdateSkill({ workspaceDir, config: params.config, agentId: params.ctx.agentId, skillName: proposal.skillName, description: proposal.description, content: buildAutoCaptureUpdateContent(existingSkill, proposal.content), createdBy: "skill-workshop", goal: proposal.goal, evidence: proposal.evidence }); log$1.info(`skill research auto-capture queued workshop proposal ${result.record.target.skillKey}`); } catch (error) { log$1.warn(`skill research auto-capture skipped: ${String(error)}`); } } //#endregion //#region src/agents/harness/agent-end-side-effects.ts /** * Agent-end side effect runner. * * Harnesses use this to trigger core research capture and plugin agent_end hooks * either fire-and-forget or awaited during tests/shutdown. */ const log = createSubsystemLogger("agents/harness"); async function runCoreAgentEndSideEffects(params) { try { await runSkillResearchAutoCapture({ event: params.event, ctx: params.ctx, ...params.ctx.config ? { config: params.ctx.config } : {} }); } catch (error) { log.warn(`skill research auto-capture failed: ${String(error)}`); } } /** Starts agent-end side effects without waiting for completion. */ function runAgentEndSideEffects(params) { runCoreAgentEndSideEffects(params); runAgentHarnessAgentEndHook(params); } /** Runs agent-end side effects and waits for plugin/core completion. */ async function awaitAgentEndSideEffects(params) { await runCoreAgentEndSideEffects(params); await awaitAgentHarnessAgentEndHook(params); } //#endregion export { runAgentEndSideEffects as n, awaitAgentEndSideEffects as t };