Skip to content

Commit 48e034d

Browse files
Ace AutonomousAce Autonomous
authored andcommitted
polish: rewrite all 7 ambiguous notifications for humans
1. Watchdog: "no_improvement" → "hasn't improved after 350 uses" 2. New proposal: "Tool: exec, 347x" → "Your agent used exec 347 times" 3. Sub-pattern: "Native Tool Sub-Pattern" → "Your agent ran exec in docker domain" 4. Expired: "No response in 7 days" → "No action taken — automatically removed" 5. Revalidation: "targets native tool" → "exec is built-in, doesn't need a skill" 6. Blocked: "BLOCKED: credential leak" → "Failed security validation" 7. Upgrade blocked: same as #6 Every notification answers: what happened, why, what to do.
1 parent d5320a9 commit 48e034d

4 files changed

Lines changed: 32 additions & 29 deletions

File tree

index.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,10 +128,11 @@ async function validateAndDeploy(skillName: string): Promise<{ ok: boolean; mess
128128
if (result.errors.some((e: string) => e.startsWith("BLOCKED:"))) {
129129
fs.rmSync(path.join(SKILLS_DIR, skillName), { recursive: true, force: true });
130130
const blockReasons = result.errors.filter((e: string) => e.startsWith("BLOCKED:")).join("; ");
131-
notify(`Blocked: ${skillName}${blockReasons}`);
131+
notify(`🚫 ${bold("Skill blocked")} ${mono(skillName)}\n\nFailed security validation — not deployed.\n${blockReasons.replace(/BLOCKED:\s*/g, "")}`);
132132
return { ok: false, message: `Skill '${skillName}' blocked by validator: ${blockReasons}` };
133133
}
134-
notify(`Deploying ${skillName} with warnings: ${result.errors.join("; ")}`);
134+
notify(`⚠️ ${bold("Deployed with warnings")} ${mono(skillName)}\n\n${result.errors.join("\n")}`);
135+
135136
}
136137
} catch (err) { fs.rmSync(path.join(SKILLS_DIR, skillName), { recursive: true, force: true }); return { ok: false, message: `Skill '${skillName}' rejected: validator failed to load — ${(err as Error).message}` }; }
137138

@@ -695,7 +696,8 @@ function buildPlugin() {
695696
if (valResult.errors.some((e: string) => e.startsWith("BLOCKED:"))) {
696697
const blockReasons = valResult.errors.filter((e: string) => e.startsWith("BLOCKED:")).join("; ");
697698
fs.rmSync(upgradeDir, { recursive: true, force: true });
698-
notify(`Upgrade blocked: ${upgradeName}${blockReasons}`);
699+
notify(`🚫 ${bold("Upgrade blocked")} ${mono(upgradeName)}\n\nFailed security validation — old skill preserved.\n${blockReasons.replace(/BLOCKED:\s*/g, "")}`);
700+
699701
return { text: `Upgrade '${upgradeName}' blocked by validator: ${blockReasons}` };
700702
}
701703
}

src/pattern/analyze-native.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import * as fsSync from "fs";
1818
import { appendJsonl } from "./store.js";
1919
import { notify } from "../notify.js";
20+
import { bold, mono } from "../notify-format.js";
2021
import { writeProposal } from "../skill/generator.js";
2122
import { validateSkillMd } from "../skill/validator.js";
2223
import { generateSkillWithLLm } from "../skill/llm-generator.js";
@@ -234,12 +235,11 @@ export async function handleNativeToolCandidate(
234235
const summary = descMatch ? descMatch[1].slice(0, 120) : canonicalName;
235236

236237
notify(
237-
`Native Tool Sub-Pattern Proposal\n` +
238-
`${canonicalName}\n` +
239-
`Tool: ${key} (${sp.domain} domain)\n` +
240-
`${sp.entries.length}x, ${Math.round(spSuccessRate * 100)}% success, ${spSessions.size} sessions\n` +
241-
`Summary: ${summary}\n` +
242-
`Use: /forge approve ${canonicalName} or /forge reject ${canonicalName}`
238+
`📋 ${bold("New Skill Proposal")}\n\n` +
239+
`${bold(canonicalName)}\n` +
240+
`Your agent ran ${mono(key)} in the ${sp.domain} domain ${sp.entries.length} times across ${spSessions.size} session${spSessions.size !== 1 ? "s" : ""} (${Math.round(spSuccessRate * 100)}% success)\n` +
241+
(summary ? `\n${summary}` : "") + `\n\n` +
242+
`${mono("/forge preview " + canonicalName)}\n${mono("/forge approve " + canonicalName)}\n${mono("/forge reject " + canonicalName)}`
243243
).catch(err => console.error("[aceforge] notify error:", err));
244244

245245
console.log(`[aceforge] sub-pattern proposal written: ${canonicalName}`);

src/pattern/analyze.ts

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -371,13 +371,12 @@ export async function analyzePatterns(): Promise<void> {
371371
: "";
372372

373373
notify(
374-
`New Skill Proposal\n` +
375-
`${skillName}\n` +
376-
`Tool: ${key}\n` +
377-
`${entries.length}x, ${Math.round(successRate * 100)}% success, ${sessions.size} sessions\n` +
378-
`Summary: ${summary}` +
379-
notesSuffix + `\n` +
380-
`Use: /forge approve ${skillName} or /forge reject ${skillName}`
374+
`📋 ${bold("New Skill Proposal")}\n\n` +
375+
`${bold(skillName)}\n` +
376+
`Your agent used ${mono(key)} ${entries.length} times across ${sessions.size} session${sessions.size !== 1 ? "s" : ""} (${Math.round(successRate * 100)}% success)\n` +
377+
(summary ? `\n${summary}` : "") +
378+
(notesSuffix ? `\n${notesSuffix}` : "") + `\n\n` +
379+
`${mono("/forge preview " + skillName)}\n${mono("/forge approve " + skillName)}\n${mono("/forge reject " + skillName)}`
381380
).catch(err => console.error("[aceforge] notify error:", err));
382381
}
383382

@@ -401,17 +400,19 @@ export async function analyzePatterns(): Promise<void> {
401400
const { runEffectivenessWatchdog } = await import("../skill/lifecycle.js");
402401
const watchdogAlerts = runEffectivenessWatchdog();
403402
if (watchdogAlerts.length > 0) {
404-
const alertText = watchdogAlerts.map((a: any) =>
405-
`${a.skill}: ${a.reason === "no_improvement"
406-
? `no improvement after ${a.activations} activations (${Math.round(a.successRate * 100)}% vs ${Math.round(a.baselineRate * 100)}% baseline)`
407-
: `degraded to ${Math.round(a.successRate * 100)}% success over ${a.activations} activations`}`
408-
).join("\n");
409-
console.warn(`[aceforge] watchdog alerts:\n${alertText}`);
403+
const alertLines = watchdogAlerts.map((a: any) => {
404+
if (a.reason === "no_improvement") {
405+
return `${bold(a.skill)} hasn't improved after ${a.activations} uses — still at ${Math.round(a.successRate * 100)}% (was ${Math.round(a.baselineRate * 100)}% at deploy)`;
406+
} else {
407+
return `${bold(a.skill)} is declining — down to ${Math.round(a.successRate * 100)}% success over ${a.activations} uses`;
408+
}
409+
}).join("\n\n");
410+
console.warn(`[aceforge] watchdog alerts:\n${alertLines}`);
410411
notify(
411-
`Skill Effectiveness Alert\n` +
412-
`${watchdogAlerts.length} skill(s) flagged for review:\n` +
413-
alertText + `\n` +
414-
`Consider: /forge retire <name> or wait for evolution cycle`
412+
`📉 ${bold("Underperforming Skills")}\n\n` +
413+
alertLines + `\n\n` +
414+
`You can retire them or let the next evolution cycle attempt a fix:\n` +
415+
mono("/forge retire <skill-name>")
415416
).catch(err => console.error("[aceforge] notify error:", err));
416417
}
417418
} catch (err) {

src/skill/lifecycle.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ export function expireOldProposals(notifyFn?: (msg: string) => Promise<void>): v
338338
fsSync.rmSync(propDir, { recursive: true, force: true });
339339
console.log(`[aceforge] proposal expired: ${name}`);
340340
if (notifyFn) {
341-
notifyFn(`Proposal expired: ${name}\nNo response in 7 days.`);
341+
notifyFn(`🗑️ Proposal expired · ${name}\n\nNo action taken in 7 days — automatically removed.`);
342342
}
343343
}
344344
}
@@ -590,7 +590,7 @@ export function revalidateProposals(
590590
removed.push(name);
591591
console.log(`[aceforge] revalidation: removed '${name}' — targets native tool '${prefix}'`);
592592
if (notifyFn) {
593-
notifyFn(`Proposal removed: ${name}\nReason: targets native OpenClaw tool '${prefix}'`);
593+
notifyFn(`🧹 Stale proposal cleaned up · ${name}\n\n${prefix} is a built-in OpenClaw tool that doesn't need a skill.`);
594594
}
595595
continue;
596596
}
@@ -610,7 +610,7 @@ export function revalidateProposals(
610610
removed.push(name);
611611
console.log(`[aceforge] revalidation: removed '${name}' — duplicates deployed skill '${duplicateSkill}'`);
612612
if (notifyFn) {
613-
notifyFn(`Proposal removed: ${name}\nReason: duplicates deployed skill '${duplicateSkill}'`);
613+
notifyFn(`🧹 Stale proposal cleaned up · ${name}\n\nAlready covered by deployed skill ${duplicateSkill}.`);
614614
}
615615
}
616616
}

0 commit comments

Comments
 (0)