mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-12 22:02:55 +00:00
refactor: extract shared dedupe helpers for runtime paths
This commit is contained in:
@@ -42,3 +42,27 @@ export async function requestExecApprovalDecision(
|
|||||||
: undefined;
|
: undefined;
|
||||||
return typeof decisionValue === "string" ? decisionValue : null;
|
return typeof decisionValue === "string" ? decisionValue : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function requestExecApprovalDecisionForHost(params: {
|
||||||
|
approvalId: string;
|
||||||
|
command: string;
|
||||||
|
workdir: string;
|
||||||
|
host: "gateway" | "node";
|
||||||
|
security: ExecSecurity;
|
||||||
|
ask: ExecAsk;
|
||||||
|
agentId?: string;
|
||||||
|
resolvedPath?: string;
|
||||||
|
sessionKey?: string;
|
||||||
|
}): Promise<string | null> {
|
||||||
|
return await requestExecApprovalDecision({
|
||||||
|
id: params.approvalId,
|
||||||
|
command: params.command,
|
||||||
|
cwd: params.workdir,
|
||||||
|
host: params.host,
|
||||||
|
security: params.security,
|
||||||
|
ask: params.ask,
|
||||||
|
agentId: params.agentId,
|
||||||
|
resolvedPath: params.resolvedPath,
|
||||||
|
sessionKey: params.sessionKey,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
} from "../infra/exec-approvals.js";
|
} from "../infra/exec-approvals.js";
|
||||||
import type { SafeBinProfile } from "../infra/exec-safe-bin-policy.js";
|
import type { SafeBinProfile } from "../infra/exec-safe-bin-policy.js";
|
||||||
import { markBackgrounded, tail } from "./bash-process-registry.js";
|
import { markBackgrounded, tail } from "./bash-process-registry.js";
|
||||||
import { requestExecApprovalDecision } from "./bash-tools.exec-approval-request.js";
|
import { requestExecApprovalDecisionForHost } from "./bash-tools.exec-approval-request.js";
|
||||||
import {
|
import {
|
||||||
DEFAULT_APPROVAL_TIMEOUT_MS,
|
DEFAULT_APPROVAL_TIMEOUT_MS,
|
||||||
DEFAULT_NOTIFY_TAIL_CHARS,
|
DEFAULT_NOTIFY_TAIL_CHARS,
|
||||||
@@ -81,6 +81,19 @@ export async function processGatewayAllowlist(
|
|||||||
const analysisOk = allowlistEval.analysisOk;
|
const analysisOk = allowlistEval.analysisOk;
|
||||||
const allowlistSatisfied =
|
const allowlistSatisfied =
|
||||||
hostSecurity === "allowlist" && analysisOk ? allowlistEval.allowlistSatisfied : false;
|
hostSecurity === "allowlist" && analysisOk ? allowlistEval.allowlistSatisfied : false;
|
||||||
|
const recordMatchedAllowlistUse = (resolvedPath?: string) => {
|
||||||
|
if (allowlistMatches.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const seen = new Set<string>();
|
||||||
|
for (const match of allowlistMatches) {
|
||||||
|
if (seen.has(match.pattern)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
seen.add(match.pattern);
|
||||||
|
recordAllowlistUse(approvals.file, params.agentId, match, params.command, resolvedPath);
|
||||||
|
}
|
||||||
|
};
|
||||||
const hasHeredocSegment = allowlistEval.segments.some((segment) =>
|
const hasHeredocSegment = allowlistEval.segments.some((segment) =>
|
||||||
segment.argv.some((token) => token.startsWith("<<")),
|
segment.argv.some((token) => token.startsWith("<<")),
|
||||||
);
|
);
|
||||||
@@ -113,10 +126,10 @@ export async function processGatewayAllowlist(
|
|||||||
void (async () => {
|
void (async () => {
|
||||||
let decision: string | null = null;
|
let decision: string | null = null;
|
||||||
try {
|
try {
|
||||||
decision = await requestExecApprovalDecision({
|
decision = await requestExecApprovalDecisionForHost({
|
||||||
id: approvalId,
|
approvalId,
|
||||||
command: params.command,
|
command: params.command,
|
||||||
cwd: params.workdir,
|
workdir: params.workdir,
|
||||||
host: "gateway",
|
host: "gateway",
|
||||||
security: hostSecurity,
|
security: hostSecurity,
|
||||||
ask: hostAsk,
|
ask: hostAsk,
|
||||||
@@ -186,22 +199,7 @@ export async function processGatewayAllowlist(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allowlistMatches.length > 0) {
|
recordMatchedAllowlistUse(resolvedPath ?? undefined);
|
||||||
const seen = new Set<string>();
|
|
||||||
for (const match of allowlistMatches) {
|
|
||||||
if (seen.has(match.pattern)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
seen.add(match.pattern);
|
|
||||||
recordAllowlistUse(
|
|
||||||
approvals.file,
|
|
||||||
params.agentId,
|
|
||||||
match,
|
|
||||||
params.command,
|
|
||||||
resolvedPath ?? undefined,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let run: Awaited<ReturnType<typeof runExecProcess>> | null = null;
|
let run: Awaited<ReturnType<typeof runExecProcess>> | null = null;
|
||||||
try {
|
try {
|
||||||
@@ -321,22 +319,7 @@ export async function processGatewayAllowlist(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allowlistMatches.length > 0) {
|
recordMatchedAllowlistUse(allowlistEval.segments[0]?.resolution?.resolvedPath);
|
||||||
const seen = new Set<string>();
|
|
||||||
for (const match of allowlistMatches) {
|
|
||||||
if (seen.has(match.pattern)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
seen.add(match.pattern);
|
|
||||||
recordAllowlistUse(
|
|
||||||
approvals.file,
|
|
||||||
params.agentId,
|
|
||||||
match,
|
|
||||||
params.command,
|
|
||||||
allowlistEval.segments[0]?.resolution?.resolvedPath,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { execCommandOverride };
|
return { execCommandOverride };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
resolveExecApprovalsFromFile,
|
resolveExecApprovalsFromFile,
|
||||||
} from "../infra/exec-approvals.js";
|
} from "../infra/exec-approvals.js";
|
||||||
import { buildNodeShellCommand } from "../infra/node-shell.js";
|
import { buildNodeShellCommand } from "../infra/node-shell.js";
|
||||||
import { requestExecApprovalDecision } from "./bash-tools.exec-approval-request.js";
|
import { requestExecApprovalDecisionForHost } from "./bash-tools.exec-approval-request.js";
|
||||||
import {
|
import {
|
||||||
DEFAULT_APPROVAL_TIMEOUT_MS,
|
DEFAULT_APPROVAL_TIMEOUT_MS,
|
||||||
createApprovalSlug,
|
createApprovalSlug,
|
||||||
@@ -178,10 +178,10 @@ export async function executeNodeHostCommand(
|
|||||||
void (async () => {
|
void (async () => {
|
||||||
let decision: string | null = null;
|
let decision: string | null = null;
|
||||||
try {
|
try {
|
||||||
decision = await requestExecApprovalDecision({
|
decision = await requestExecApprovalDecisionForHost({
|
||||||
id: approvalId,
|
approvalId,
|
||||||
command: params.command,
|
command: params.command,
|
||||||
cwd: params.workdir,
|
workdir: params.workdir,
|
||||||
host: "node",
|
host: "node",
|
||||||
security: hostSecurity,
|
security: hostSecurity,
|
||||||
ask: hostAsk,
|
ask: hostAsk,
|
||||||
|
|||||||
@@ -317,35 +317,10 @@ export function subscribeEmbeddedPiSession(params: SubscribeEmbeddedPiSessionPar
|
|||||||
}
|
}
|
||||||
return `\`\`\`txt\n${trimmed}\n\`\`\``;
|
return `\`\`\`txt\n${trimmed}\n\`\`\``;
|
||||||
};
|
};
|
||||||
const emitToolSummary = (toolName?: string, meta?: string) => {
|
const emitToolResultMessage = (toolName: string | undefined, message: string) => {
|
||||||
if (!params.onToolResult) {
|
if (!params.onToolResult) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const agg = formatToolAggregate(toolName, meta ? [meta] : undefined, {
|
|
||||||
markdown: useMarkdown,
|
|
||||||
});
|
|
||||||
const { text: cleanedText, mediaUrls } = parseReplyDirectives(agg);
|
|
||||||
const filteredMediaUrls = filterToolResultMediaUrls(toolName, mediaUrls ?? []);
|
|
||||||
if (!cleanedText && filteredMediaUrls.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
void params.onToolResult({
|
|
||||||
text: cleanedText,
|
|
||||||
mediaUrls: filteredMediaUrls.length ? filteredMediaUrls : undefined,
|
|
||||||
});
|
|
||||||
} catch {
|
|
||||||
// ignore tool result delivery failures
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const emitToolOutput = (toolName?: string, meta?: string, output?: string) => {
|
|
||||||
if (!params.onToolResult || !output) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const agg = formatToolAggregate(toolName, meta ? [meta] : undefined, {
|
|
||||||
markdown: useMarkdown,
|
|
||||||
});
|
|
||||||
const message = `${agg}\n${formatToolOutputBlock(output)}`;
|
|
||||||
const { text: cleanedText, mediaUrls } = parseReplyDirectives(message);
|
const { text: cleanedText, mediaUrls } = parseReplyDirectives(message);
|
||||||
const filteredMediaUrls = filterToolResultMediaUrls(toolName, mediaUrls ?? []);
|
const filteredMediaUrls = filterToolResultMediaUrls(toolName, mediaUrls ?? []);
|
||||||
if (!cleanedText && filteredMediaUrls.length === 0) {
|
if (!cleanedText && filteredMediaUrls.length === 0) {
|
||||||
@@ -360,6 +335,22 @@ export function subscribeEmbeddedPiSession(params: SubscribeEmbeddedPiSessionPar
|
|||||||
// ignore tool result delivery failures
|
// ignore tool result delivery failures
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const emitToolSummary = (toolName?: string, meta?: string) => {
|
||||||
|
const agg = formatToolAggregate(toolName, meta ? [meta] : undefined, {
|
||||||
|
markdown: useMarkdown,
|
||||||
|
});
|
||||||
|
emitToolResultMessage(toolName, agg);
|
||||||
|
};
|
||||||
|
const emitToolOutput = (toolName?: string, meta?: string, output?: string) => {
|
||||||
|
if (!output) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const agg = formatToolAggregate(toolName, meta ? [meta] : undefined, {
|
||||||
|
markdown: useMarkdown,
|
||||||
|
});
|
||||||
|
const message = `${agg}\n${formatToolOutputBlock(output)}`;
|
||||||
|
emitToolResultMessage(toolName, message);
|
||||||
|
};
|
||||||
|
|
||||||
const stripBlockTags = (
|
const stripBlockTags = (
|
||||||
text: string,
|
text: string,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import type { OpenClawConfig } from "../config/config.js";
|
import type { OpenClawConfig } from "../config/config.js";
|
||||||
import { evaluateEntryMetadataRequirementsForCurrentPlatform } from "../shared/entry-status.js";
|
import { evaluateEntryRequirementsForCurrentPlatform } from "../shared/entry-status.js";
|
||||||
import type { RequirementConfigCheck, Requirements } from "../shared/requirements.js";
|
import type { RequirementConfigCheck, Requirements } from "../shared/requirements.js";
|
||||||
import { CONFIG_DIR } from "../utils.js";
|
import { CONFIG_DIR } from "../utils.js";
|
||||||
import {
|
import {
|
||||||
@@ -191,17 +191,15 @@ function buildSkillStatus(
|
|||||||
? bundledNames.has(entry.skill.name)
|
? bundledNames.has(entry.skill.name)
|
||||||
: entry.skill.source === "openclaw-bundled";
|
: entry.skill.source === "openclaw-bundled";
|
||||||
|
|
||||||
const requirementStatus = evaluateEntryMetadataRequirementsForCurrentPlatform({
|
|
||||||
always,
|
|
||||||
metadata: entry.metadata,
|
|
||||||
frontmatter: entry.frontmatter,
|
|
||||||
hasLocalBin: hasBinary,
|
|
||||||
remote: eligibility?.remote,
|
|
||||||
isEnvSatisfied,
|
|
||||||
isConfigSatisfied,
|
|
||||||
});
|
|
||||||
const { emoji, homepage, required, missing, requirementsSatisfied, configChecks } =
|
const { emoji, homepage, required, missing, requirementsSatisfied, configChecks } =
|
||||||
requirementStatus;
|
evaluateEntryRequirementsForCurrentPlatform({
|
||||||
|
always,
|
||||||
|
entry,
|
||||||
|
hasLocalBin: hasBinary,
|
||||||
|
remote: eligibility?.remote,
|
||||||
|
isEnvSatisfied,
|
||||||
|
isConfigSatisfied,
|
||||||
|
});
|
||||||
const eligible = !disabled && !blockedByAllowlist && requirementsSatisfied;
|
const eligible = !disabled && !blockedByAllowlist && requirementsSatisfied;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type { MemoryCitationsMode } from "../config/types.memory.js";
|
|||||||
import { listDeliverableMessageChannels } from "../utils/message-channel.js";
|
import { listDeliverableMessageChannels } from "../utils/message-channel.js";
|
||||||
import type { ResolvedTimeFormat } from "./date-time.js";
|
import type { ResolvedTimeFormat } from "./date-time.js";
|
||||||
import type { EmbeddedContextFile } from "./pi-embedded-helpers.js";
|
import type { EmbeddedContextFile } from "./pi-embedded-helpers.js";
|
||||||
|
import type { EmbeddedSandboxInfo } from "./pi-embedded-runner/types.js";
|
||||||
import { sanitizeForPromptLiteral } from "./sanitize-for-prompt.js";
|
import { sanitizeForPromptLiteral } from "./sanitize-for-prompt.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -229,20 +230,7 @@ export function buildAgentSystemPrompt(params: {
|
|||||||
repoRoot?: string;
|
repoRoot?: string;
|
||||||
};
|
};
|
||||||
messageToolHints?: string[];
|
messageToolHints?: string[];
|
||||||
sandboxInfo?: {
|
sandboxInfo?: EmbeddedSandboxInfo;
|
||||||
enabled: boolean;
|
|
||||||
workspaceDir?: string;
|
|
||||||
containerWorkspaceDir?: string;
|
|
||||||
workspaceAccess?: "none" | "ro" | "rw";
|
|
||||||
agentWorkspaceMount?: string;
|
|
||||||
browserBridgeUrl?: string;
|
|
||||||
browserNoVncUrl?: string;
|
|
||||||
hostBrowserAllowed?: boolean;
|
|
||||||
elevated?: {
|
|
||||||
allowed: boolean;
|
|
||||||
defaultLevel: "on" | "off" | "ask" | "full";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
/** Reaction guidance for the agent (for Telegram minimal/extensive modes). */
|
/** Reaction guidance for the agent (for Telegram minimal/extensive modes). */
|
||||||
reactionGuidance?: {
|
reactionGuidance?: {
|
||||||
level: "minimal" | "extensive";
|
level: "minimal" | "extensive";
|
||||||
|
|||||||
@@ -266,6 +266,46 @@ function processLine(
|
|||||||
return enhanced;
|
return enhanced;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type InteractiveSnapshotLine = NonNullable<ReturnType<typeof matchInteractiveSnapshotLine>>;
|
||||||
|
|
||||||
|
function buildInteractiveSnapshotLines(params: {
|
||||||
|
lines: string[];
|
||||||
|
options: RoleSnapshotOptions;
|
||||||
|
resolveRef: (parsed: InteractiveSnapshotLine) => { ref: string; nth?: number } | null;
|
||||||
|
recordRef: (parsed: InteractiveSnapshotLine, ref: string, nth?: number) => void;
|
||||||
|
includeSuffix: (suffix: string) => boolean;
|
||||||
|
}): string[] {
|
||||||
|
const out: string[] = [];
|
||||||
|
for (const line of params.lines) {
|
||||||
|
const parsed = matchInteractiveSnapshotLine(line, params.options);
|
||||||
|
if (!parsed) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!INTERACTIVE_ROLES.has(parsed.role)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const resolved = params.resolveRef(parsed);
|
||||||
|
if (!resolved?.ref) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
params.recordRef(parsed, resolved.ref, resolved.nth);
|
||||||
|
|
||||||
|
let enhanced = `- ${parsed.roleRaw}`;
|
||||||
|
if (parsed.name) {
|
||||||
|
enhanced += ` "${parsed.name}"`;
|
||||||
|
}
|
||||||
|
enhanced += ` [ref=${resolved.ref}]`;
|
||||||
|
if ((resolved.nth ?? 0) > 0) {
|
||||||
|
enhanced += ` [nth=${resolved.nth}]`;
|
||||||
|
}
|
||||||
|
if (params.includeSuffix(parsed.suffix)) {
|
||||||
|
enhanced += parsed.suffix;
|
||||||
|
}
|
||||||
|
out.push(enhanced);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
export function parseRoleRef(raw: string): string | null {
|
export function parseRoleRef(raw: string): string | null {
|
||||||
const trimmed = raw.trim();
|
const trimmed = raw.trim();
|
||||||
if (!trimmed) {
|
if (!trimmed) {
|
||||||
@@ -294,39 +334,24 @@ export function buildRoleSnapshotFromAriaSnapshot(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (options.interactive) {
|
if (options.interactive) {
|
||||||
const result: string[] = [];
|
const result = buildInteractiveSnapshotLines({
|
||||||
for (const line of lines) {
|
lines,
|
||||||
const parsed = matchInteractiveSnapshotLine(line, options);
|
options,
|
||||||
if (!parsed) {
|
resolveRef: ({ role, name }) => {
|
||||||
continue;
|
const ref = nextRef();
|
||||||
}
|
const nth = tracker.getNextIndex(role, name);
|
||||||
const { roleRaw, role, name, suffix } = parsed;
|
tracker.trackRef(role, name, ref);
|
||||||
if (!INTERACTIVE_ROLES.has(role)) {
|
return { ref, nth };
|
||||||
continue;
|
},
|
||||||
}
|
recordRef: ({ role, name }, ref, nth) => {
|
||||||
|
refs[ref] = {
|
||||||
const ref = nextRef();
|
role,
|
||||||
const nth = tracker.getNextIndex(role, name);
|
name,
|
||||||
tracker.trackRef(role, name, ref);
|
nth,
|
||||||
refs[ref] = {
|
};
|
||||||
role,
|
},
|
||||||
name,
|
includeSuffix: (suffix) => suffix.includes("["),
|
||||||
nth,
|
});
|
||||||
};
|
|
||||||
|
|
||||||
let enhanced = `- ${roleRaw}`;
|
|
||||||
if (name) {
|
|
||||||
enhanced += ` "${name}"`;
|
|
||||||
}
|
|
||||||
enhanced += ` [ref=${ref}]`;
|
|
||||||
if (nth > 0) {
|
|
||||||
enhanced += ` [nth=${nth}]`;
|
|
||||||
}
|
|
||||||
if (suffix.includes("[")) {
|
|
||||||
enhanced += suffix;
|
|
||||||
}
|
|
||||||
result.push(enhanced);
|
|
||||||
}
|
|
||||||
|
|
||||||
removeNthFromNonDuplicates(refs, tracker);
|
removeNthFromNonDuplicates(refs, tracker);
|
||||||
|
|
||||||
@@ -370,23 +395,18 @@ export function buildRoleSnapshotFromAiSnapshot(
|
|||||||
const refs: RoleRefMap = {};
|
const refs: RoleRefMap = {};
|
||||||
|
|
||||||
if (options.interactive) {
|
if (options.interactive) {
|
||||||
const out: string[] = [];
|
const out = buildInteractiveSnapshotLines({
|
||||||
for (const line of lines) {
|
lines,
|
||||||
const parsed = matchInteractiveSnapshotLine(line, options);
|
options,
|
||||||
if (!parsed) {
|
resolveRef: ({ suffix }) => {
|
||||||
continue;
|
const ref = parseAiSnapshotRef(suffix);
|
||||||
}
|
return ref ? { ref } : null;
|
||||||
const { roleRaw, role, name, suffix } = parsed;
|
},
|
||||||
if (!INTERACTIVE_ROLES.has(role)) {
|
recordRef: ({ role, name }, ref) => {
|
||||||
continue;
|
refs[ref] = { role, ...(name ? { name } : {}) };
|
||||||
}
|
},
|
||||||
const ref = parseAiSnapshotRef(suffix);
|
includeSuffix: () => true,
|
||||||
if (!ref) {
|
});
|
||||||
continue;
|
|
||||||
}
|
|
||||||
refs[ref] = { role, ...(name ? { name } : {}) };
|
|
||||||
out.push(`- ${roleRaw}${name ? ` "${name}"` : ""}${suffix}`);
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
snapshot: out.join("\n") || "(no interactive elements)",
|
snapshot: out.join("\n") || "(no interactive elements)",
|
||||||
refs,
|
refs,
|
||||||
|
|||||||
@@ -200,6 +200,21 @@ function parseSessionKeyFromPayloadJSON(payloadJSON: string): string | null {
|
|||||||
return sessionKey.length > 0 ? sessionKey : null;
|
return sessionKey.length > 0 ? sessionKey : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parsePayloadObject(payloadJSON?: string | null): Record<string, unknown> | null {
|
||||||
|
if (!payloadJSON) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
let payload: unknown;
|
||||||
|
try {
|
||||||
|
payload = JSON.parse(payloadJSON) as unknown;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return typeof payload === "object" && payload !== null
|
||||||
|
? (payload as Record<string, unknown>)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
async function sendReceiptAck(params: {
|
async function sendReceiptAck(params: {
|
||||||
cfg: ReturnType<typeof loadConfig>;
|
cfg: ReturnType<typeof loadConfig>;
|
||||||
deps: NodeEventContext["deps"];
|
deps: NodeEventContext["deps"];
|
||||||
@@ -232,17 +247,10 @@ async function sendReceiptAck(params: {
|
|||||||
export const handleNodeEvent = async (ctx: NodeEventContext, nodeId: string, evt: NodeEvent) => {
|
export const handleNodeEvent = async (ctx: NodeEventContext, nodeId: string, evt: NodeEvent) => {
|
||||||
switch (evt.event) {
|
switch (evt.event) {
|
||||||
case "voice.transcript": {
|
case "voice.transcript": {
|
||||||
if (!evt.payloadJSON) {
|
const obj = parsePayloadObject(evt.payloadJSON);
|
||||||
|
if (!obj) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let payload: unknown;
|
|
||||||
try {
|
|
||||||
payload = JSON.parse(evt.payloadJSON) as unknown;
|
|
||||||
} catch {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const obj =
|
|
||||||
typeof payload === "object" && payload !== null ? (payload as Record<string, unknown>) : {};
|
|
||||||
const text = typeof obj.text === "string" ? obj.text.trim() : "";
|
const text = typeof obj.text === "string" ? obj.text.trim() : "";
|
||||||
if (!text) {
|
if (!text) {
|
||||||
return;
|
return;
|
||||||
@@ -455,17 +463,10 @@ export const handleNodeEvent = async (ctx: NodeEventContext, nodeId: string, evt
|
|||||||
case "exec.started":
|
case "exec.started":
|
||||||
case "exec.finished":
|
case "exec.finished":
|
||||||
case "exec.denied": {
|
case "exec.denied": {
|
||||||
if (!evt.payloadJSON) {
|
const obj = parsePayloadObject(evt.payloadJSON);
|
||||||
|
if (!obj) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let payload: unknown;
|
|
||||||
try {
|
|
||||||
payload = JSON.parse(evt.payloadJSON) as unknown;
|
|
||||||
} catch {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const obj =
|
|
||||||
typeof payload === "object" && payload !== null ? (payload as Record<string, unknown>) : {};
|
|
||||||
const sessionKey =
|
const sessionKey =
|
||||||
typeof obj.sessionKey === "string" ? obj.sessionKey.trim() : `node-${nodeId}`;
|
typeof obj.sessionKey === "string" ? obj.sessionKey.trim() : `node-${nodeId}`;
|
||||||
if (!sessionKey) {
|
if (!sessionKey) {
|
||||||
@@ -519,17 +520,10 @@ export const handleNodeEvent = async (ctx: NodeEventContext, nodeId: string, evt
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case "push.apns.register": {
|
case "push.apns.register": {
|
||||||
if (!evt.payloadJSON) {
|
const obj = parsePayloadObject(evt.payloadJSON);
|
||||||
|
if (!obj) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let payload: unknown;
|
|
||||||
try {
|
|
||||||
payload = JSON.parse(evt.payloadJSON) as unknown;
|
|
||||||
} catch {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const obj =
|
|
||||||
typeof payload === "object" && payload !== null ? (payload as Record<string, unknown>) : {};
|
|
||||||
const token = typeof obj.token === "string" ? obj.token : "";
|
const token = typeof obj.token === "string" ? obj.token : "";
|
||||||
const topic = typeof obj.topic === "string" ? obj.topic : "";
|
const topic = typeof obj.topic === "string" ? obj.topic : "";
|
||||||
const environment = obj.environment;
|
const environment = obj.environment;
|
||||||
|
|||||||
@@ -1,23 +1,9 @@
|
|||||||
import { vi } from "vitest";
|
import { vi } from "vitest";
|
||||||
import type { PluginRegistry } from "../plugins/registry.js";
|
import { createEmptyPluginRegistry, type PluginRegistry } from "../plugins/registry.js";
|
||||||
import { setActivePluginRegistry } from "../plugins/runtime.js";
|
import { setActivePluginRegistry } from "../plugins/runtime.js";
|
||||||
|
|
||||||
export const registryState: { registry: PluginRegistry } = {
|
export const registryState: { registry: PluginRegistry } = {
|
||||||
registry: {
|
registry: createEmptyPluginRegistry(),
|
||||||
plugins: [],
|
|
||||||
tools: [],
|
|
||||||
hooks: [],
|
|
||||||
typedHooks: [],
|
|
||||||
channels: [],
|
|
||||||
providers: [],
|
|
||||||
gatewayHandlers: {},
|
|
||||||
httpHandlers: [],
|
|
||||||
httpRoutes: [],
|
|
||||||
cliRegistrars: [],
|
|
||||||
services: [],
|
|
||||||
commands: [],
|
|
||||||
diagnostics: [],
|
|
||||||
} as PluginRegistry,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function setRegistry(registry: PluginRegistry) {
|
export function setRegistry(registry: PluginRegistry) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import type { OpenClawConfig } from "../config/config.js";
|
import type { OpenClawConfig } from "../config/config.js";
|
||||||
import { evaluateEntryMetadataRequirementsForCurrentPlatform } from "../shared/entry-status.js";
|
import { evaluateEntryRequirementsForCurrentPlatform } from "../shared/entry-status.js";
|
||||||
import type { RequirementConfigCheck, Requirements } from "../shared/requirements.js";
|
import type { RequirementConfigCheck, Requirements } from "../shared/requirements.js";
|
||||||
import { CONFIG_DIR } from "../utils.js";
|
import { CONFIG_DIR } from "../utils.js";
|
||||||
import { hasBinary, isConfigPathTruthy, resolveHookConfig } from "./config.js";
|
import { hasBinary, isConfigPathTruthy, resolveHookConfig } from "./config.js";
|
||||||
@@ -91,17 +91,15 @@ function buildHookStatus(
|
|||||||
Boolean(process.env[envName] || hookConfig?.env?.[envName]);
|
Boolean(process.env[envName] || hookConfig?.env?.[envName]);
|
||||||
const isConfigSatisfied = (pathStr: string) => isConfigPathTruthy(config, pathStr);
|
const isConfigSatisfied = (pathStr: string) => isConfigPathTruthy(config, pathStr);
|
||||||
|
|
||||||
const requirementStatus = evaluateEntryMetadataRequirementsForCurrentPlatform({
|
|
||||||
always,
|
|
||||||
metadata: entry.metadata,
|
|
||||||
frontmatter: entry.frontmatter,
|
|
||||||
hasLocalBin: hasBinary,
|
|
||||||
remote: eligibility?.remote,
|
|
||||||
isEnvSatisfied,
|
|
||||||
isConfigSatisfied,
|
|
||||||
});
|
|
||||||
const { emoji, homepage, required, missing, requirementsSatisfied, configChecks } =
|
const { emoji, homepage, required, missing, requirementsSatisfied, configChecks } =
|
||||||
requirementStatus;
|
evaluateEntryRequirementsForCurrentPlatform({
|
||||||
|
always,
|
||||||
|
entry,
|
||||||
|
hasLocalBin: hasBinary,
|
||||||
|
remote: eligibility?.remote,
|
||||||
|
isEnvSatisfied,
|
||||||
|
isConfigSatisfied,
|
||||||
|
});
|
||||||
|
|
||||||
const eligible = !disabled && requirementsSatisfied;
|
const eligible = !disabled && requirementsSatisfied;
|
||||||
|
|
||||||
|
|||||||
@@ -346,30 +346,7 @@ export function unwrapDispatchWrappersForResolution(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function extractPosixShellInlineCommand(argv: string[]): string | null {
|
function extractPosixShellInlineCommand(argv: string[]): string | null {
|
||||||
for (let i = 1; i < argv.length; i += 1) {
|
return extractInlineCommandByFlags(argv, POSIX_INLINE_COMMAND_FLAGS, { allowCombinedC: true });
|
||||||
const token = argv[i]?.trim();
|
|
||||||
if (!token) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const lower = token.toLowerCase();
|
|
||||||
if (lower === "--") {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (POSIX_INLINE_COMMAND_FLAGS.has(lower)) {
|
|
||||||
const cmd = argv[i + 1]?.trim();
|
|
||||||
return cmd ? cmd : null;
|
|
||||||
}
|
|
||||||
if (/^-[^-]*c[^-]*$/i.test(token)) {
|
|
||||||
const commandIndex = lower.indexOf("c");
|
|
||||||
const inline = token.slice(commandIndex + 1).trim();
|
|
||||||
if (inline) {
|
|
||||||
return inline;
|
|
||||||
}
|
|
||||||
const cmd = argv[i + 1]?.trim();
|
|
||||||
return cmd ? cmd : null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractCmdInlineCommand(argv: string[]): string | null {
|
function extractCmdInlineCommand(argv: string[]): string | null {
|
||||||
@@ -389,6 +366,14 @@ function extractCmdInlineCommand(argv: string[]): string | null {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function extractPowerShellInlineCommand(argv: string[]): string | null {
|
function extractPowerShellInlineCommand(argv: string[]): string | null {
|
||||||
|
return extractInlineCommandByFlags(argv, POWERSHELL_INLINE_COMMAND_FLAGS);
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractInlineCommandByFlags(
|
||||||
|
argv: string[],
|
||||||
|
flags: ReadonlySet<string>,
|
||||||
|
options: { allowCombinedC?: boolean } = {},
|
||||||
|
): string | null {
|
||||||
for (let i = 1; i < argv.length; i += 1) {
|
for (let i = 1; i < argv.length; i += 1) {
|
||||||
const token = argv[i]?.trim();
|
const token = argv[i]?.trim();
|
||||||
if (!token) {
|
if (!token) {
|
||||||
@@ -398,7 +383,16 @@ function extractPowerShellInlineCommand(argv: string[]): string | null {
|
|||||||
if (lower === "--") {
|
if (lower === "--") {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (POWERSHELL_INLINE_COMMAND_FLAGS.has(lower)) {
|
if (flags.has(lower)) {
|
||||||
|
const cmd = argv[i + 1]?.trim();
|
||||||
|
return cmd ? cmd : null;
|
||||||
|
}
|
||||||
|
if (options.allowCombinedC && /^-[^-]*c[^-]*$/i.test(token)) {
|
||||||
|
const commandIndex = lower.indexOf("c");
|
||||||
|
const inline = token.slice(commandIndex + 1).trim();
|
||||||
|
if (inline) {
|
||||||
|
return inline;
|
||||||
|
}
|
||||||
const cmd = argv[i + 1]?.trim();
|
const cmd = argv[i + 1]?.trim();
|
||||||
return cmd ? cmd : null;
|
return cmd ? cmd : null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import type { SsrFPolicy } from "../infra/net/ssrf.js";
|
import type { SsrFPolicy } from "../infra/net/ssrf.js";
|
||||||
import { resolveRemoteEmbeddingBearerClient } from "./embeddings-remote-client.js";
|
import {
|
||||||
import { fetchRemoteEmbeddingVectors } from "./embeddings-remote-fetch.js";
|
createRemoteEmbeddingProvider,
|
||||||
|
resolveRemoteEmbeddingClient,
|
||||||
|
} from "./embeddings-remote-provider.js";
|
||||||
import type { EmbeddingProvider, EmbeddingProviderOptions } from "./embeddings.js";
|
import type { EmbeddingProvider, EmbeddingProviderOptions } from "./embeddings.js";
|
||||||
|
|
||||||
export type MistralEmbeddingClient = {
|
export type MistralEmbeddingClient = {
|
||||||
@@ -28,31 +30,13 @@ export async function createMistralEmbeddingProvider(
|
|||||||
options: EmbeddingProviderOptions,
|
options: EmbeddingProviderOptions,
|
||||||
): Promise<{ provider: EmbeddingProvider; client: MistralEmbeddingClient }> {
|
): Promise<{ provider: EmbeddingProvider; client: MistralEmbeddingClient }> {
|
||||||
const client = await resolveMistralEmbeddingClient(options);
|
const client = await resolveMistralEmbeddingClient(options);
|
||||||
const url = `${client.baseUrl.replace(/\/$/, "")}/embeddings`;
|
|
||||||
|
|
||||||
const embed = async (input: string[]): Promise<number[][]> => {
|
|
||||||
if (input.length === 0) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return await fetchRemoteEmbeddingVectors({
|
|
||||||
url,
|
|
||||||
headers: client.headers,
|
|
||||||
ssrfPolicy: client.ssrfPolicy,
|
|
||||||
body: { model: client.model, input },
|
|
||||||
errorPrefix: "mistral embeddings failed",
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
provider: {
|
provider: createRemoteEmbeddingProvider({
|
||||||
id: "mistral",
|
id: "mistral",
|
||||||
model: client.model,
|
client,
|
||||||
embedQuery: async (text) => {
|
errorPrefix: "mistral embeddings failed",
|
||||||
const [vec] = await embed([text]);
|
}),
|
||||||
return vec ?? [];
|
|
||||||
},
|
|
||||||
embedBatch: embed,
|
|
||||||
},
|
|
||||||
client,
|
client,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -60,11 +44,10 @@ export async function createMistralEmbeddingProvider(
|
|||||||
export async function resolveMistralEmbeddingClient(
|
export async function resolveMistralEmbeddingClient(
|
||||||
options: EmbeddingProviderOptions,
|
options: EmbeddingProviderOptions,
|
||||||
): Promise<MistralEmbeddingClient> {
|
): Promise<MistralEmbeddingClient> {
|
||||||
const { baseUrl, headers, ssrfPolicy } = await resolveRemoteEmbeddingBearerClient({
|
return await resolveRemoteEmbeddingClient({
|
||||||
provider: "mistral",
|
provider: "mistral",
|
||||||
options,
|
options,
|
||||||
defaultBaseUrl: DEFAULT_MISTRAL_BASE_URL,
|
defaultBaseUrl: DEFAULT_MISTRAL_BASE_URL,
|
||||||
|
normalizeModel: normalizeMistralModel,
|
||||||
});
|
});
|
||||||
const model = normalizeMistralModel(options.model);
|
|
||||||
return { baseUrl, headers, ssrfPolicy, model };
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import type { SsrFPolicy } from "../infra/net/ssrf.js";
|
import type { SsrFPolicy } from "../infra/net/ssrf.js";
|
||||||
import { resolveRemoteEmbeddingBearerClient } from "./embeddings-remote-client.js";
|
import {
|
||||||
import { fetchRemoteEmbeddingVectors } from "./embeddings-remote-fetch.js";
|
createRemoteEmbeddingProvider,
|
||||||
|
resolveRemoteEmbeddingClient,
|
||||||
|
} from "./embeddings-remote-provider.js";
|
||||||
import type { EmbeddingProvider, EmbeddingProviderOptions } from "./embeddings.js";
|
import type { EmbeddingProvider, EmbeddingProviderOptions } from "./embeddings.js";
|
||||||
|
|
||||||
export type OpenAiEmbeddingClient = {
|
export type OpenAiEmbeddingClient = {
|
||||||
@@ -33,32 +35,14 @@ export async function createOpenAiEmbeddingProvider(
|
|||||||
options: EmbeddingProviderOptions,
|
options: EmbeddingProviderOptions,
|
||||||
): Promise<{ provider: EmbeddingProvider; client: OpenAiEmbeddingClient }> {
|
): Promise<{ provider: EmbeddingProvider; client: OpenAiEmbeddingClient }> {
|
||||||
const client = await resolveOpenAiEmbeddingClient(options);
|
const client = await resolveOpenAiEmbeddingClient(options);
|
||||||
const url = `${client.baseUrl.replace(/\/$/, "")}/embeddings`;
|
|
||||||
|
|
||||||
const embed = async (input: string[]): Promise<number[][]> => {
|
|
||||||
if (input.length === 0) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return await fetchRemoteEmbeddingVectors({
|
|
||||||
url,
|
|
||||||
headers: client.headers,
|
|
||||||
ssrfPolicy: client.ssrfPolicy,
|
|
||||||
body: { model: client.model, input },
|
|
||||||
errorPrefix: "openai embeddings failed",
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
provider: {
|
provider: createRemoteEmbeddingProvider({
|
||||||
id: "openai",
|
id: "openai",
|
||||||
model: client.model,
|
client,
|
||||||
|
errorPrefix: "openai embeddings failed",
|
||||||
maxInputTokens: OPENAI_MAX_INPUT_TOKENS[client.model],
|
maxInputTokens: OPENAI_MAX_INPUT_TOKENS[client.model],
|
||||||
embedQuery: async (text) => {
|
}),
|
||||||
const [vec] = await embed([text]);
|
|
||||||
return vec ?? [];
|
|
||||||
},
|
|
||||||
embedBatch: embed,
|
|
||||||
},
|
|
||||||
client,
|
client,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -66,11 +50,10 @@ export async function createOpenAiEmbeddingProvider(
|
|||||||
export async function resolveOpenAiEmbeddingClient(
|
export async function resolveOpenAiEmbeddingClient(
|
||||||
options: EmbeddingProviderOptions,
|
options: EmbeddingProviderOptions,
|
||||||
): Promise<OpenAiEmbeddingClient> {
|
): Promise<OpenAiEmbeddingClient> {
|
||||||
const { baseUrl, headers, ssrfPolicy } = await resolveRemoteEmbeddingBearerClient({
|
return await resolveRemoteEmbeddingClient({
|
||||||
provider: "openai",
|
provider: "openai",
|
||||||
options,
|
options,
|
||||||
defaultBaseUrl: DEFAULT_OPENAI_BASE_URL,
|
defaultBaseUrl: DEFAULT_OPENAI_BASE_URL,
|
||||||
|
normalizeModel: normalizeOpenAiModel,
|
||||||
});
|
});
|
||||||
const model = normalizeOpenAiModel(options.model);
|
|
||||||
return { baseUrl, headers, ssrfPolicy, model };
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { SsrFPolicy } from "../infra/net/ssrf.js";
|
|||||||
import type { EmbeddingProviderOptions } from "./embeddings.js";
|
import type { EmbeddingProviderOptions } from "./embeddings.js";
|
||||||
import { buildRemoteBaseUrlPolicy } from "./remote-http.js";
|
import { buildRemoteBaseUrlPolicy } from "./remote-http.js";
|
||||||
|
|
||||||
type RemoteEmbeddingProviderId = "openai" | "voyage" | "mistral";
|
export type RemoteEmbeddingProviderId = "openai" | "voyage" | "mistral";
|
||||||
|
|
||||||
export async function resolveRemoteEmbeddingBearerClient(params: {
|
export async function resolveRemoteEmbeddingBearerClient(params: {
|
||||||
provider: RemoteEmbeddingProviderId;
|
provider: RemoteEmbeddingProviderId;
|
||||||
|
|||||||
63
src/memory/embeddings-remote-provider.ts
Normal file
63
src/memory/embeddings-remote-provider.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import type { SsrFPolicy } from "../infra/net/ssrf.js";
|
||||||
|
import {
|
||||||
|
resolveRemoteEmbeddingBearerClient,
|
||||||
|
type RemoteEmbeddingProviderId,
|
||||||
|
} from "./embeddings-remote-client.js";
|
||||||
|
import { fetchRemoteEmbeddingVectors } from "./embeddings-remote-fetch.js";
|
||||||
|
import type { EmbeddingProvider, EmbeddingProviderOptions } from "./embeddings.js";
|
||||||
|
|
||||||
|
export type RemoteEmbeddingClient = {
|
||||||
|
baseUrl: string;
|
||||||
|
headers: Record<string, string>;
|
||||||
|
ssrfPolicy?: SsrFPolicy;
|
||||||
|
model: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function createRemoteEmbeddingProvider(params: {
|
||||||
|
id: string;
|
||||||
|
client: RemoteEmbeddingClient;
|
||||||
|
errorPrefix: string;
|
||||||
|
maxInputTokens?: number;
|
||||||
|
}): EmbeddingProvider {
|
||||||
|
const { client } = params;
|
||||||
|
const url = `${client.baseUrl.replace(/\/$/, "")}/embeddings`;
|
||||||
|
|
||||||
|
const embed = async (input: string[]): Promise<number[][]> => {
|
||||||
|
if (input.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return await fetchRemoteEmbeddingVectors({
|
||||||
|
url,
|
||||||
|
headers: client.headers,
|
||||||
|
ssrfPolicy: client.ssrfPolicy,
|
||||||
|
body: { model: client.model, input },
|
||||||
|
errorPrefix: params.errorPrefix,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: params.id,
|
||||||
|
model: client.model,
|
||||||
|
...(typeof params.maxInputTokens === "number" ? { maxInputTokens: params.maxInputTokens } : {}),
|
||||||
|
embedQuery: async (text) => {
|
||||||
|
const [vec] = await embed([text]);
|
||||||
|
return vec ?? [];
|
||||||
|
},
|
||||||
|
embedBatch: embed,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function resolveRemoteEmbeddingClient(params: {
|
||||||
|
provider: RemoteEmbeddingProviderId;
|
||||||
|
options: EmbeddingProviderOptions;
|
||||||
|
defaultBaseUrl: string;
|
||||||
|
normalizeModel: (model: string) => string;
|
||||||
|
}): Promise<RemoteEmbeddingClient> {
|
||||||
|
const { baseUrl, headers, ssrfPolicy } = await resolveRemoteEmbeddingBearerClient({
|
||||||
|
provider: params.provider,
|
||||||
|
options: params.options,
|
||||||
|
defaultBaseUrl: params.defaultBaseUrl,
|
||||||
|
});
|
||||||
|
const model = params.normalizeModel(params.options.model);
|
||||||
|
return { baseUrl, headers, ssrfPolicy, model };
|
||||||
|
}
|
||||||
@@ -64,3 +64,30 @@ export function evaluateEntryMetadataRequirementsForCurrentPlatform(
|
|||||||
localPlatform: process.platform,
|
localPlatform: process.platform,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function evaluateEntryRequirementsForCurrentPlatform(params: {
|
||||||
|
always: boolean;
|
||||||
|
entry: {
|
||||||
|
metadata?: (RequirementsMetadata & { emoji?: string; homepage?: string }) | null;
|
||||||
|
frontmatter?: {
|
||||||
|
emoji?: string;
|
||||||
|
homepage?: string;
|
||||||
|
website?: string;
|
||||||
|
url?: string;
|
||||||
|
} | null;
|
||||||
|
};
|
||||||
|
hasLocalBin: (bin: string) => boolean;
|
||||||
|
remote?: RequirementRemote;
|
||||||
|
isEnvSatisfied: (envName: string) => boolean;
|
||||||
|
isConfigSatisfied: (pathStr: string) => boolean;
|
||||||
|
}): ReturnType<typeof evaluateEntryMetadataRequirements> {
|
||||||
|
return evaluateEntryMetadataRequirementsForCurrentPlatform({
|
||||||
|
always: params.always,
|
||||||
|
metadata: params.entry.metadata,
|
||||||
|
frontmatter: params.entry.frontmatter,
|
||||||
|
hasLocalBin: params.hasLocalBin,
|
||||||
|
remote: params.remote,
|
||||||
|
isEnvSatisfied: params.isEnvSatisfied,
|
||||||
|
isConfigSatisfied: params.isConfigSatisfied,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user