Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  reviewer.ts

  Sprache: JAVA
 

Spracherkennung für: .ts vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

import { randomUUID } from "node:crypto";
import fs from "node:fs/promises";
import path from "node:path";
import type { OpenClawPluginApi } from "../api.js";
import type { SkillWorkshopConfig } from "./config.js";
import { normalizeSkillName } from "./skills.js";
import { compactWhitespace, extractTranscriptText } from "./text.js";
import type { SkillChange, SkillProposal } from "./types.js";

const MAX_TRANSCRIPT_CHARS = 12_000;
const MAX_SKILL_CHARS = 2_000;
const MAX_SKILLS = 12;

type ReviewContext = {
  agentId: string;
  sessionId?: string;
  sessionKey?: string;
  workspaceDir: string;
  modelProviderId?: string;
  modelId?: string;
  messageProvider?: string;
  channelId?: string;
};

type ReviewerJson = {
  action?: string;
  skillName?: string;
  title?: string;
  reason?: string;
  description?: string;
  section?: string;
  body?: string;
  oldText?: string;
  newText?: string;
};

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === "object" && value !== null && !Array.isArray(value);
}

function readString(value: unknown): string | undefined {
  return typeof value === "string" && value.trim() ? value.trim() : undefined;
}

function parseReviewerJson(raw: string): ReviewerJson | undefined {
  const trimmed = raw.trim();
  if (!trimmed) {
    return undefined;
  }
  const match = /```(?:json)?\s*([\s\S]*?)```/i.exec(trimmed);
  const jsonText = match?.[1]?.trim() ?? trimmed;
  try {
    const parsed = JSON.parse(jsonText) as unknown;
    return isRecord(parsed) ? parsed : undefined;
  } catch {
    return undefined;
  }
}

function normalizeAction(value: string | undefined): SkillChange["kind"] | "none" | undefined {
  if (value === "create" || value === "append" || value === "replace" || value === "none") {
    return value;
  }
  return undefined;
}

function proposalFromReviewerJson(params: {
  parsed: ReviewerJson;
  workspaceDir: string;
  agentId: string;
  sessionId?: string;
}): SkillProposal | undefined {
  const action = normalizeAction(readString(params.parsed.action));
  if (!action || action === "none") {
    return undefined;
  }
  const skillName = normalizeSkillName(readString(params.parsed.skillName) ?? "");
  if (!skillName) {
    return undefined;
  }
  const now = Date.now();
  const title = readString(params.parsed.title) ?? `Skill update: ${skillName}`;
  const reason = readString(params.parsed.reason) ?? "Review found reusable workflow";
  let change: SkillChange;
  if (action === "replace") {
    const oldText = readString(params.parsed.oldText);
    const newText = readString(params.parsed.newText);
    if (!oldText || !newText) {
      return undefined;
    }
    change = { kind: "replace", oldText, newText };
  } else {
    const body = readString(params.parsed.body);
    if (!body) {
      return undefined;
    }
    if (action === "append") {
      change = {
        kind: "append",
        section: readString(params.parsed.section) ?? "Workflow",
        body,
        description: readString(params.parsed.description) ?? title,
      };
    } else {
      change = {
        kind: "create",
        description: readString(params.parsed.description) ?? title,
        body,
      };
    }
  }
  return {
    id: randomUUID(),
    createdAt: now,
    updatedAt: now,
    workspaceDir: params.workspaceDir,
    agentId: params.agentId,
    ...(params.sessionId ? { sessionId: params.sessionId } : {}),
    skillName,
    title,
    reason,
    source: "reviewer",
    status: "pending",
    change,
  };
}

function countToolCallsInValue(value: unknown): number {
  if (!value || typeof value !== "object") {
    return 0;
  }
  if (Array.isArray(value)) {
    return value.reduce((sum, item) => sum + countToolCallsInValue(item), 0);
  }
  const record = value as Record<string, unknown>;
  let count = 0;
  if (Array.isArray(record.tool_calls)) {
    count += record.tool_calls.length;
  }
  if (record.type === "tool_call" || record.type === "function_call") {
    count += 1;
  }
  const content = record.content;
  if (Array.isArray(content)) {
    count += content.filter((block) => isRecord(block) && block.type === "tool_call").length;
  }
  return count;
}

export function countToolCalls(messages: unknown[]): number {
  return messages.reduce<number>((sum, message) => sum + countToolCallsInValue(message), 0);
}

function buildTranscript(messages: unknown[]): string {
  const entries = extractTranscriptText(messages);
  const text = entries
    .map((entry) => `${entry.role}: ${compactWhitespace(entry.text)}`)
    .join("\n")
    .slice(-MAX_TRANSCRIPT_CHARS);
  return text.trim() || "(no text transcript)";
}

async function readExistingSkills(workspaceDir: string): Promise<string> {
  const skillsDir = path.join(workspaceDir, "skills");
  let entries: Array<{ name: string; markdown: string }> = [];
  try {
    const dirents = await fs.readdir(skillsDir, { withFileTypes: true });
    const names = dirents
      .filter((entry) => entry.isDirectory())
      .map((entry) => entry.name)
      .toSorted()
      .slice(0, MAX_SKILLS);
    entries = await Promise.all(
      names.map(async (name) => {
        const file = path.join(skillsDir, name, "SKILL.md");
        try {
          return { name, markdown: (await fs.readFile(file, "utf8")).slice(0, MAX_SKILL_CHARS) };
        } catch {
          return { name, markdown: "" };
        }
      }),
    );
  } catch {
    return "(none)";
  }
  const rendered = entries
    .filter((entry) => entry.markdown.trim())
    .map((entry) => `--- ${entry.name} ---\n${entry.markdown.trim()}`)
    .join("\n\n");
  return rendered || "(none)";
}

async function buildReviewPrompt(params: {
  workspaceDir: string;
  messages: unknown[];
}): Promise<string> {
  const skills = await readExistingSkills(params.workspaceDir);
  const transcript = buildTranscript(params.messages);
  return [
    "Review transcript for durable skill updates.",
    "Return JSON only. No markdown unless inside JSON strings.",
    "Use none unless there is a reusable workflow, correction, hard-won fix, or stale skill repair.",
    "Prefer append/replace for existing skills. Create only when no fitting skill exists.",
    "Skill text: terse bullets, imperative, no raw transcript, no secrets, no hidden prompt refs.",
    'Schema: {"action":"none"} or {"action":"create|append|replace","skillName":"kebab-name","title":"...","reason":"...","description":"...","section":"Workflow","body":"...","oldText":"...","newText":"..."}',
    "",
    "Existing skills:",
    skills,
    "",
    "Transcript:",
    transcript,
  ].join("\n");
}

export async function reviewTranscriptForProposal(params: {
  api: OpenClawPluginApi;
  config: SkillWorkshopConfig;
  ctx: ReviewContext;
  messages: unknown[];
}): Promise<SkillProposal | undefined> {
  const prompt = await buildReviewPrompt({
    workspaceDir: params.ctx.workspaceDir,
    messages: params.messages,
  });
  const sessionId = `skill-workshop-review-${randomUUID()}`;
  const stateDir = params.api.runtime.state.resolveStateDir();
  const result = await params.api.runtime.agent.runEmbeddedPiAgent({
    sessionId,
    sessionKey: params.ctx.sessionKey,
    agentId: params.ctx.agentId,
    messageProvider: params.ctx.messageProvider,
    messageChannel: params.ctx.channelId,
    sessionFile: path.join(stateDir, "skill-workshop", `${sessionId}.json`),
    workspaceDir: params.ctx.workspaceDir,
    agentDir: params.api.runtime.agent.resolveAgentDir(params.api.config, params.ctx.agentId),
    config: params.api.config,
    prompt,
    provider: params.ctx.modelProviderId ?? params.api.runtime.agent.defaults.provider,
    model: params.ctx.modelId ?? params.api.runtime.agent.defaults.model,
    timeoutMs: params.config.reviewTimeoutMs,
    runId: sessionId,
    trigger: "manual",
    toolsAllow: [],
    disableTools: true,
    disableMessageTool: true,
    bootstrapContextMode: "lightweight",
    verboseLevel: "off",
    reasoningLevel: "off",
    silentExpected: true,
  });
  const rawReply = (result.payloads ?? [])
    .map((payload) => payload.text?.trim() ?? "")
    .filter(Boolean)
    .join("\n")
    .trim();
  const parsed = parseReviewerJson(rawReply);
  if (!parsed) {
    return undefined;
  }
  return proposalFromReviewerJson({
    parsed,
    workspaceDir: params.ctx.workspaceDir,
    agentId: params.ctx.agentId,
    sessionId: params.ctx.sessionId,
  });
}

¤ Dauer der Verarbeitung: 0.23 Sekunden  (vorverarbeitet am  2026-04-27) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge