openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
374 lines (373 loc) • 16.2 kB
JavaScript
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 };