UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

374 lines (373 loc) 16.2 kB
import { n as getRuntimeConfig } from "./io.runtime-B9iJRs3w.js"; import { t as createSubsystemLogger } from "./subsystem-Dy2tqXOS.js"; import "./config-Cs0XXL3x.js"; import { a as clearAgentRunContext, h as registerAgentRunContext } from "./agent-run-registry-CKYKdfNd.js"; import { b as runWithGatewayIndependentRootWorkAdmission, s as getGatewayRestartDrainSignal } from "./gateway-work-admission-R1IpuDim.js"; import "./sessions-BdNAJTEP.js"; import { j as resolveInternalSessionEffectsIdentity } from "./session-accessor.sqlite-entry-CWk3jL7s.js"; import { t as SessionManager } from "./session-manager-DO0bDxav.js"; import { r as runOutsidePreparedModelRuntimePluginGenerationScope } from "./prepared-model-runtime-generation-scope-o4umnoSw.js"; import { t as resolveSkillWorkshopConfig } from "./config-Cjp42tXL.js"; import { a as runWithCronCreatorAuthorityCapability, i as createCronCreatorAuthorityCapability } from "./cron-creator-authority-context-CkDgGats.js"; import { n as getCanonicalSkillWorkspace, t as applyAutonomousSkillProposal } from "./autonomous-apply-Cq93vacq.js"; import { a as recordSkillExperienceReviewOutcome } from "./collection-review-state-D6da8JQS.js"; import { n as resolveAgentRunSessionTarget } from "./run-session-target-Ckn1JYI3.js"; import { t as runSkillWorkshopReview } from "./review-run-DbDM03C2.js"; import { i as assertSkillReviewRunSucceeded, n as countSkillModelIterations, r as selectCurrentSkillTurnMessages, t as buildSkillExperienceReviewPrompt } from "./experience-review-prompt-BuZFjGt6.js"; import { randomUUID } from "node:crypto"; //#region src/skills/workshop/experience-review.ts const EXPERIENCE_REVIEW_MIN_MODEL_ITERATIONS = 10; const EXPERIENCE_REVIEW_IDLE_MS = 3e4; const EXPERIENCE_REVIEW_RETRY_IDLE_MS = 3e4; const EXPERIENCE_REVIEW_TIMEOUT_MS = 12e4; const EXPERIENCE_REVIEW_MAX_PENDING = 32; const EXPERIENCE_REVIEW_BLOCKED_TRIGGERS = /* @__PURE__ */ new Set([ "cron", "heartbeat", "memory", "overflow" ]); const EXPERIENCE_REVIEW_BLOCKED_SESSION_SEGMENTS = /* @__PURE__ */ new Set([ "cron", "hook", "subagent", "skill-workshop-review" ]); const log = createSubsystemLogger("skills/workshop"); function isEligibleContext(ctx) { if (ctx.compacted === true || ctx.skillWorkshopAvailable !== true || !ctx.modelProviderId?.trim() || !ctx.modelId?.trim()) return false; const trigger = ctx.foregroundPromptContext.trigger?.trim().toLowerCase(); if (trigger && EXPERIENCE_REVIEW_BLOCKED_TRIGGERS.has(trigger)) return false; const sessionKey = ctx.sessionKey?.trim().toLowerCase(); if (!sessionKey || sessionKey.includes("active-memory")) return false; return !sessionKey.split(":").some((segment) => EXPERIENCE_REVIEW_BLOCKED_SESSION_SEGMENTS.has(segment)); } async function prepareSkillExperienceReviewCandidate(candidate, config) { if (resolveSkillWorkshopConfig(config).autonomous.mode === "off") return; const { resolveConversationCapabilityProfile } = await import("./agents/conversation-capability-profile.js"); const { resolveSandboxRuntimeStatus } = await import("./sandbox-ChGSR5HU.js"); const { isToolAllowedByPolicies } = await import("./tool-policy-match-4tEYkdtf.js"); const { mergeAlsoAllowPolicy } = await import("./tool-policy-XITAo8Vo.js"); const foreground = candidate.ctx.foregroundPromptContext; const sessionKey = candidate.ctx.sessionKey; if (!sessionKey || resolveSandboxRuntimeStatus({ cfg: config, sessionKey, agentId: foreground.agentId }).sandboxed) return; const capabilityProfile = resolveConversationCapabilityProfile({ config, sessionKey, sandboxSessionKey: sessionKey, agentId: foreground.agentId, agentAccountId: foreground.agentAccountId, messageProvider: foreground.messageProvider, messageChannel: foreground.messageChannel, chatType: foreground.chatType, groupId: foreground.groupId, groupChannel: foreground.groupChannel, groupSpace: foreground.groupSpace, memberRoleIds: foreground.memberRoleIds, spawnedBy: foreground.spawnedBy, senderId: foreground.senderId, senderName: foreground.senderName, senderUsername: foreground.senderUsername, senderE164: foreground.senderE164, senderIsOwner: foreground.senderIsOwner, modelProvider: candidate.ctx.modelProviderId, modelId: candidate.ctx.modelId, workspaceDir: candidate.ctx.workspaceDir }); if (!isToolAllowedByPolicies("skill_workshop", [ mergeAlsoAllowPolicy(capabilityProfile.policy.profilePolicy, capabilityProfile.policy.profileAlsoAllow), mergeAlsoAllowPolicy(capabilityProfile.policy.providerProfilePolicy, capabilityProfile.policy.providerProfileAlsoAllow), capabilityProfile.policy.globalPolicy, capabilityProfile.policy.globalProviderPolicy, capabilityProfile.policy.agentPolicy, capabilityProfile.policy.agentProviderPolicy, capabilityProfile.policy.groupPolicy, capabilityProfile.policy.senderPolicy, capabilityProfile.policy.subagentPolicy, capabilityProfile.policy.inheritedToolPolicy ])) return; return { ...candidate, config }; } function createSkillExperienceReviewScheduler(deps) { const pendingBySession = /* @__PURE__ */ new Map(); let reviewInFlight = false; const setTimer = deps.setTimer ?? ((callback, delayMs) => setTimeout(callback, delayMs)); const clearTimer = deps.clearTimer ?? clearTimeout; const arm = (key, pending, delayMs) => { if (pending.timer) clearTimer(pending.timer); const generation = ++pending.generation; const timerCallback = () => { if (pendingBySession.get(key) !== pending || pending.generation !== generation) return; pending.timer = void 0; Promise.resolve(deps.isSystemActive()).then(async (active) => { if (pendingBySession.get(key) !== pending || pending.generation !== generation) return; if (active || reviewInFlight) { arm(key, pending, EXPERIENCE_REVIEW_RETRY_IDLE_MS); return; } reviewInFlight = true; try { pendingBySession.delete(key); const candidate = deps.prepareReview ? await deps.prepareReview(pending.candidate) : pending.candidate; if (!candidate) return; await deps.runReview(candidate); } finally { reviewInFlight = false; } }).catch((error) => { log.warn(`skill experience review failed: ${String(error)}`); if (pendingBySession.get(key) === pending && pending.generation === generation) pendingBySession.delete(key); }); }; const timer = runOutsidePreparedModelRuntimePluginGenerationScope(() => setTimer(timerCallback, delayMs)); pending.timer = timer; timer.unref?.(); }; return { schedule(params) { const sessionKey = params.ctx.sessionKey?.trim(); if (!sessionKey) return; const key = JSON.stringify([params.ctx.foregroundPromptContext.agentId, sessionKey]); const existing = pendingBySession.get(key); const errored = typeof params.event.error === "string" && params.event.error.trim() !== ""; if (existing && errored && params.ctx.runId?.trim() && params.ctx.runId === existing.candidate.ctx.runId) { if (existing.timer) clearTimer(existing.timer); pendingBySession.delete(key); return; } if (existing) arm(key, existing, EXPERIENCE_REVIEW_IDLE_MS); if (errored) { log.debug(`experience review skipped: reason=errored-completion session=${sessionKey}`); return; } if (resolveSkillWorkshopConfig(params.config).autonomous.mode === "off") return; if (!isEligibleContext(params.ctx)) { log.debug(`experience review skipped: reason=ineligible-context session=${sessionKey}`); return; } const workspaceDir = getCanonicalSkillWorkspace() ?? params.ctx.workspaceDir?.trim(); if (!workspaceDir) { log.debug(`experience review skipped: reason=missing-workspace session=${sessionKey}`); return; } const turnMessages = selectCurrentSkillTurnMessages(params.event.messages); const reportedModelIterations = params.ctx.modelIterations; const modelIterations = reportedModelIterations === void 0 ? countSkillModelIterations(turnMessages) : Number.isSafeInteger(reportedModelIterations) && reportedModelIterations >= 0 ? reportedModelIterations : 0; if (modelIterations < EXPERIENCE_REVIEW_MIN_MODEL_ITERATIONS) { log.debug(`experience review skipped: reason=below-depth-bar iterations=${modelIterations} session=${sessionKey}`); return; } { if (!existing && pendingBySession.size >= EXPERIENCE_REVIEW_MAX_PENDING) { const oldest = pendingBySession.entries().next().value; if (oldest) { if (oldest[1].timer) clearTimer(oldest[1].timer); pendingBySession.delete(oldest[0]); } } const candidate = { ctx: { agentId: params.ctx.agentId, runId: params.ctx.runId, sessionKey, sessionId: params.ctx.sessionId, workspaceDir, modelProviderId: params.ctx.modelProviderId, modelId: params.ctx.modelId, modelContextWindowTokens: params.ctx.modelContextWindowTokens, authProfileId: params.ctx.authProfileId, skillWorkshopAvailable: params.ctx.skillWorkshopAvailable, compacted: params.ctx.compacted, foregroundPromptContext: params.ctx.foregroundPromptContext }, ...params.config ? { config: params.config } : {}, usedSkills: params.usedSkills ? [...params.usedSkills] : void 0, turnAborted: !params.event.success }; const pending = existing ?? { candidate, generation: 0 }; pending.candidate = candidate; pendingBySession.set(key, pending); arm(key, pending, EXPERIENCE_REVIEW_IDLE_MS); log.debug(`experience review scheduled: session=${sessionKey} iterations=${modelIterations} aborted=${!params.event.success}`); } }, clear() { for (const pending of pendingBySession.values()) if (pending.timer) clearTimer(pending.timer); pendingBySession.clear(); } }; } async function runSkillExperienceReview(candidate, deps = {}) { await runWithGatewayIndependentRootWorkAdmission(() => runSkillExperienceReviewInner(candidate, deps), "skills:experience-review"); } async function runSkillExperienceReviewInner(candidate, deps) { const abortSignal = getGatewayRestartDrainSignal(); const foregroundPromptContext = candidate.ctx.foregroundPromptContext; const workspaceDir = getCanonicalSkillWorkspace() ?? candidate.ctx.workspaceDir; const foregroundSessionKey = candidate.ctx.sessionKey; const foregroundSessionId = candidate.ctx.sessionId; const modelProviderId = candidate.ctx.modelProviderId?.trim(); const modelId = candidate.ctx.modelId?.trim(); if (!workspaceDir || !foregroundSessionKey || !foregroundSessionId || !modelProviderId || !modelId) return; const runId = `skill-workshop-review:${randomUUID()}`; const reviewSession = resolveInternalSessionEffectsIdentity({ agentId: foregroundPromptContext.agentId, runId }); const origin = foregroundPromptContext.cronCreatorCallerOrigin; const capability = origin ? createCronCreatorAuthorityCapability(runId, origin) : void 0; const config = candidate.config ?? getRuntimeConfig(); const proposalMutationBudget = { remaining: 1, readSkillHashes: /* @__PURE__ */ new Map() }; const attemptedAtMs = Date.now(); let outcome; let proposalId; let usage; registerAgentRunContext(runId, { agentId: foregroundPromptContext.agentId, sessionId: reviewSession.sessionId, sessionKey: reviewSession.sessionKey, isControlUiVisible: false, projectSessionActive: false, projectSessionLifecycle: false, projectSessionMessages: false }); try { const foregroundSessionTarget = await resolveAgentRunSessionTarget({ agentId: foregroundPromptContext.agentId, config, sessionId: foregroundSessionId, sessionKey: foregroundSessionKey, missingSessionKey: "resolve-existing" }); abortSignal.throwIfAborted(); const detachedSession = await SessionManager.openModelContextAsync(foregroundSessionTarget, { cwd: workspaceDir, signal: abortSignal }); abortSignal.throwIfAborted(); const { listWritableWorkspaceSkillSummaries } = await import("./workspace-skill-read-DCvaze8_.js"); abortSignal.throwIfAborted(); const existingSkills = listWritableWorkspaceSkillSummaries(workspaceDir, { config, agentId: foregroundPromptContext.agentId }); const run = () => runSkillWorkshopReview({ reviewKind: "experience", ...foregroundPromptContext, sessionId: reviewSession.sessionId, sessionKey: reviewSession.sessionKey, messageActionTurnCapability: void 0, sessionManager: detachedSession, sessionPersistence: "detached", workspaceDir, config, abortSignal, prompt: buildSkillExperienceReviewPrompt({ ...candidate, existingSkills }), provider: modelProviderId, model: modelId, ...candidate.ctx.authProfileId ? { authProfileId: candidate.ctx.authProfileId, authProfileIdSource: "user" } : {}, timeoutMs: EXPERIENCE_REVIEW_TIMEOUT_MS, runId, silentExpected: true, allowEmptyAssistantReplyAsSilent: true, terminalReplyExpectation: "optional", toolExecutionAllow: ["skill_workshop"], skillWorkshopUpdateProposals: true, skillWorkshopAutonomousCapture: true, skillWorkshopProposalMutationBudget: proposalMutationBudget, skillWorkshopOrigin: { agentId: foregroundPromptContext.agentId, sessionKey: foregroundSessionKey, ...candidate.ctx.runId ? { runId: candidate.ctx.runId } : {} }, ...capability ? { cronCreatorAuthorityCapability: capability } : {} }); const embeddedResult = capability ? await runWithCronCreatorAuthorityCapability(capability, run) : await run(); abortSignal.throwIfAborted(); assertSkillReviewRunSucceeded(embeddedResult); const proposalIds = [...proposalMutationBudget.mutatedProposalIds ?? []]; proposalId = proposalIds[0]; outcome = proposalIds.length === 0 ? "nothing" : "proposed"; const currentConfig = deps.getCurrentConfig ? await deps.getCurrentConfig() : (await import("./config/config.js")).getRuntimeConfig(); abortSignal.throwIfAborted(); if (resolveSkillWorkshopConfig(currentConfig).autonomous.mode === "auto") { const { inspectSkillProposal } = await import("./service-KztO4CV6.js"); abortSignal.throwIfAborted(); for (const mutatedProposalId of proposalIds) { abortSignal.throwIfAborted(); const proposal = await inspectSkillProposal(mutatedProposalId, { workspaceDir, agentId: foregroundPromptContext.agentId }); abortSignal.throwIfAborted(); if (!proposal || proposal.record.status !== "pending" || proposal.record.autonomousCapture !== true) continue; if ((await applyAutonomousSkillProposal({ workspaceDir, agentId: foregroundPromptContext.agentId, config: currentConfig, proposal, reason: "Autonomous self-learning capture" })).status === "applied") outcome = "applied"; } } const agentUsage = embeddedResult.meta?.agentMeta?.usage; usage = agentUsage ? { inputTokens: (agentUsage.input ?? 0) + (agentUsage.cacheRead ?? 0) + (agentUsage.cacheWrite ?? 0), cachedInputTokens: agentUsage.cacheRead ?? 0, outputTokens: agentUsage.output ?? 0 } : void 0; } catch (error) { recordSkillExperienceReviewOutcome(workspaceDir, { attemptedAtMs, outcome: "failed", error: String(error).slice(0, 300) }); throw error; } finally { clearAgentRunContext(runId); } recordSkillExperienceReviewOutcome(workspaceDir, { attemptedAtMs, outcome, ...proposalId ? { proposalId } : {}, ...usage ? { usage } : {} }); } //#endregion //#region src/skills/workshop/experience-review-default.ts const defaultScheduler = createSkillExperienceReviewScheduler({ isSystemActive: async () => { const { getActiveEmbeddedRunCount } = await import("./active-run-projections-gG4OCrrt.js"); return getActiveEmbeddedRunCount() > 0; }, prepareReview: async (candidate) => { const { getRuntimeConfig } = await import("./config/config.js"); return prepareSkillExperienceReviewCandidate(candidate, getRuntimeConfig()); }, runReview: runSkillExperienceReview }); /** Queues a conservative, post-run learning review after the agent system becomes idle. */ function scheduleSkillExperienceReview(params) { defaultScheduler.schedule(params); } //#endregion export { scheduleSkillExperienceReview };