@aiassesstech/nole 0.6.2 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +55 -0
- package/dist/freedom-run/freedom-run-manager.d.ts +57 -0
- package/dist/freedom-run/freedom-run-manager.d.ts.map +1 -0
- package/dist/freedom-run/freedom-run-manager.js +186 -0
- package/dist/freedom-run/freedom-run-manager.js.map +1 -0
- package/dist/freedom-run/index.d.ts +2 -0
- package/dist/freedom-run/index.d.ts.map +1 -0
- package/dist/freedom-run/index.js +2 -0
- package/dist/freedom-run/index.js.map +1 -0
- package/dist/governance/types.d.ts +1 -1
- package/dist/governance/types.d.ts.map +1 -1
- package/dist/governance/types.js.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +582 -30
- package/dist/plugin.js.map +1 -1
- package/package.json +5 -5
package/dist/plugin.js
CHANGED
|
@@ -25,6 +25,7 @@ import { extractMoltBookCandidates } from './pipeline/discovery.js';
|
|
|
25
25
|
import { extractDirectoryCandidates, prioritizeDirectoryOutreach } from './pipeline/directory-outreach.js';
|
|
26
26
|
import { generateDailyContentPlan } from './pipeline/content-calendar.js';
|
|
27
27
|
import { getAllDiscoveryKeywords } from './pipeline/types.js';
|
|
28
|
+
import { FreedomRunManager } from './freedom-run/index.js';
|
|
28
29
|
/**
|
|
29
30
|
* OpenClaw plugin entry point — export default function register(api)
|
|
30
31
|
*
|
|
@@ -103,6 +104,17 @@ export default function register(api) {
|
|
|
103
104
|
serviceRegistry.register(moltbookResilient);
|
|
104
105
|
serviceRegistry.register(xResilient);
|
|
105
106
|
const postQueue = new PostQueue({ dataDir, maxQueueSize: 100, maxRetries: 5, maxAgeMs: 24 * 60 * 60 * 1000 });
|
|
107
|
+
// ── Freedom Run Manager (BB Amendment A1) ──
|
|
108
|
+
const freedomRunDeps = {
|
|
109
|
+
getPipelineSnapshot: () => pipeline.getSummary(),
|
|
110
|
+
getAuditTail: (limit) => store.getAuditChain(limit),
|
|
111
|
+
getWalletBalance: () => wallet.getBalance(),
|
|
112
|
+
getServiceHealth: () => serviceRegistry.getSnapshot(),
|
|
113
|
+
};
|
|
114
|
+
const freedomRun = new FreedomRunManager(dataDir, freedomRunDeps);
|
|
115
|
+
freedomRun.initialize().catch((err) => {
|
|
116
|
+
console.warn(`[nole] Freedom run init warning: ${err.message}`);
|
|
117
|
+
});
|
|
106
118
|
// Wallet adapter (auto-detects Coinbase credentials from ~/.nole/credentials)
|
|
107
119
|
let wallet = new MockWalletAdapter(config.seedCapitalUsd);
|
|
108
120
|
// Platform API client — initialized after credentials load (see below)
|
|
@@ -2631,11 +2643,17 @@ export default function register(api) {
|
|
|
2631
2643
|
});
|
|
2632
2644
|
api.registerTool({
|
|
2633
2645
|
name: "nole_daily_brief",
|
|
2634
|
-
description: "
|
|
2635
|
-
"
|
|
2646
|
+
description: "Nole's daily operations brief with windowed scheduling for Freedom Run. " +
|
|
2647
|
+
"Windows: morning (scan/qualify), midday (deliver/post), afternoon (nurture/commission), " +
|
|
2648
|
+
"evening (report/metrics). Use 'auto' for full one-shot brief.",
|
|
2636
2649
|
parameters: {
|
|
2637
2650
|
type: "object",
|
|
2638
2651
|
properties: {
|
|
2652
|
+
window: {
|
|
2653
|
+
type: "string",
|
|
2654
|
+
enum: ["morning", "midday", "afternoon", "evening", "auto"],
|
|
2655
|
+
description: "Which daily cycle window to execute. Defaults to 'auto' (full brief).",
|
|
2656
|
+
},
|
|
2639
2657
|
includeProspects: {
|
|
2640
2658
|
type: "boolean",
|
|
2641
2659
|
description: "Include detailed prospect list in brief (default: false)",
|
|
@@ -2646,8 +2664,257 @@ export default function register(api) {
|
|
|
2646
2664
|
const dbv = await validateToolParams("nole_daily_brief", _toolCallId, params);
|
|
2647
2665
|
if (!dbv.ok)
|
|
2648
2666
|
return validationErrorResponse(dbv.message);
|
|
2667
|
+
const window = params.window || 'auto';
|
|
2668
|
+
// Freedom Run gate — if not 'auto' mode, require running state (BB Amendment A1)
|
|
2669
|
+
if (window !== 'auto') {
|
|
2670
|
+
const frStatus = freedomRun.getStatus();
|
|
2671
|
+
if (!freedomRun.canOperate()) {
|
|
2672
|
+
return {
|
|
2673
|
+
content: [{
|
|
2674
|
+
type: "text",
|
|
2675
|
+
text: JSON.stringify({
|
|
2676
|
+
freedomRunGate: 'BLOCKED',
|
|
2677
|
+
status: frStatus.status,
|
|
2678
|
+
reason: frStatus.reason,
|
|
2679
|
+
triggeredBy: frStatus.triggeredBy,
|
|
2680
|
+
message: frStatus.status === 'terminated'
|
|
2681
|
+
? 'Freedom Run has been terminated. A new spec is required to restart.'
|
|
2682
|
+
: 'Freedom Run is paused. Jessie or Greg must resume before daily operations continue.',
|
|
2683
|
+
}, null, 2),
|
|
2684
|
+
}],
|
|
2685
|
+
};
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2649
2688
|
try {
|
|
2689
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
2650
2690
|
const pipelineSummary = await pipeline.getSummary();
|
|
2691
|
+
// ── Morning window: scan, qualify, conversion detection, veto rate check ──
|
|
2692
|
+
if (window === 'morning' || window === 'auto') {
|
|
2693
|
+
const walletBalance = await wallet.getBalance().catch(() => null);
|
|
2694
|
+
// Conversion detection — poll for pitched prospects that subscribed (§11.7)
|
|
2695
|
+
const pitched = await pipeline.listByStage('pitched');
|
|
2696
|
+
const conversions = [];
|
|
2697
|
+
for (const prospect of pitched) {
|
|
2698
|
+
if (!prospect.walletAddress)
|
|
2699
|
+
continue;
|
|
2700
|
+
try {
|
|
2701
|
+
const sub = await platform.getSubscription(prospect.walletAddress);
|
|
2702
|
+
if (sub.ok && sub.data) {
|
|
2703
|
+
await pipeline.updateStage(prospect.id, 'converted', {
|
|
2704
|
+
convertedAt: new Date().toISOString(),
|
|
2705
|
+
subscriptionTier: sub.data.tier,
|
|
2706
|
+
});
|
|
2707
|
+
conversions.push({ agentName: prospect.agentName, tier: sub.data.tier ?? 'unknown' });
|
|
2708
|
+
// Send onboarding message
|
|
2709
|
+
const convertedProspect = await pipeline.get(prospect.id) ?? prospect;
|
|
2710
|
+
const onboardMsg = generateOnboardingMessage(convertedProspect, 'https://www.aiassesstech.com/assess', 'https://www.aiassesstech.com/directory');
|
|
2711
|
+
if (prospect.discoveryChannel === 'moltbook' && moltbook) {
|
|
2712
|
+
await moltbook.createPost('aiassesstech', onboardMsg.subject, onboardMsg.body).catch(() => { });
|
|
2713
|
+
}
|
|
2714
|
+
// Submit first assessment trigger through governance (BB Amendment A9)
|
|
2715
|
+
await governance.propose({
|
|
2716
|
+
actionType: 'self_assessment',
|
|
2717
|
+
description: `Trigger first Grillo assessment for new subscriber ${prospect.agentName} (${prospect.walletAddress})`,
|
|
2718
|
+
riskLevel: 'low',
|
|
2719
|
+
metadata: { targetAgent: prospect.agentName, walletAddress: prospect.walletAddress },
|
|
2720
|
+
}).catch((err) => {
|
|
2721
|
+
console.warn(`[nole] First assessment governance proposal failed: ${err.message}`);
|
|
2722
|
+
});
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
catch { /* polling failure is non-fatal */ }
|
|
2726
|
+
}
|
|
2727
|
+
// Veto rate check for Freedom Run kill switch (BB Amendment A2, §11.8)
|
|
2728
|
+
let vetoRateWarning = null;
|
|
2729
|
+
try {
|
|
2730
|
+
const govStats = await governance.getStats();
|
|
2731
|
+
const recentResults = await store.listGovernanceResults(10);
|
|
2732
|
+
const recentVetoes = recentResults.filter(r => r.finalOutcome === 'vetoed').length;
|
|
2733
|
+
if (recentResults.length >= 5) {
|
|
2734
|
+
const last5 = recentResults.slice(0, 5);
|
|
2735
|
+
const last5Vetoes = last5.filter(r => r.finalOutcome === 'vetoed').length;
|
|
2736
|
+
if (last5Vetoes / 5 >= 0.6) {
|
|
2737
|
+
vetoRateWarning = `CRITICAL: 60% veto rate over last 5 proposals — auto-terminating Freedom Run`;
|
|
2738
|
+
await freedomRun.terminate(`Grillo veto rate ${(last5Vetoes / 5 * 100).toFixed(0)}% over 5-pitch window exceeds 60% threshold`, 'mighty-mark');
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2741
|
+
if (!vetoRateWarning && recentResults.length >= 10) {
|
|
2742
|
+
const last10 = recentResults.slice(0, 10);
|
|
2743
|
+
const last10Vetoes = last10.filter(r => r.finalOutcome === 'vetoed').length;
|
|
2744
|
+
if (last10Vetoes / 10 >= 0.4) {
|
|
2745
|
+
vetoRateWarning = `WARNING: 40% veto rate over last 10 proposals — auto-pausing Freedom Run`;
|
|
2746
|
+
await freedomRun.pause(`Grillo veto rate ${(last10Vetoes / 10 * 100).toFixed(0)}% over 10-pitch window exceeds 40% threshold`, 'mighty-mark');
|
|
2747
|
+
}
|
|
2748
|
+
}
|
|
2749
|
+
}
|
|
2750
|
+
catch { /* veto rate check is best-effort */ }
|
|
2751
|
+
const suggestedActions = [];
|
|
2752
|
+
if (pipelineSummary.byStage.discovered > 0) {
|
|
2753
|
+
suggestedActions.push(`Qualify ${pipelineSummary.byStage.discovered} discovered prospect(s) — run nole_pipeline action=qualify`);
|
|
2754
|
+
}
|
|
2755
|
+
if (pipelineSummary.byStage.qualified > 0) {
|
|
2756
|
+
suggestedActions.push(`Pitch ${pipelineSummary.byStage.qualified} qualified prospect(s) — run nole_pipeline action=pitch`);
|
|
2757
|
+
}
|
|
2758
|
+
if (pipelineSummary.todayPitched < config.maxDailyPitches) {
|
|
2759
|
+
suggestedActions.push(`${config.maxDailyPitches - pipelineSummary.todayPitched} pitch slots remaining today`);
|
|
2760
|
+
}
|
|
2761
|
+
if (moltbook) {
|
|
2762
|
+
suggestedActions.push('Scan MoltBook for new prospects — run nole_pipeline action=scan');
|
|
2763
|
+
}
|
|
2764
|
+
if (window === 'morning') {
|
|
2765
|
+
return {
|
|
2766
|
+
content: [{
|
|
2767
|
+
type: "text",
|
|
2768
|
+
text: JSON.stringify({
|
|
2769
|
+
window: 'morning',
|
|
2770
|
+
date: today,
|
|
2771
|
+
freedomRun: freedomRun.getStatus().status,
|
|
2772
|
+
pipeline: pipelineSummary,
|
|
2773
|
+
wallet: walletBalance ? { balance: walletBalance, network: wallet.network } : { status: 'unavailable' },
|
|
2774
|
+
conversions: conversions.length > 0 ? conversions : 'none',
|
|
2775
|
+
vetoRateWarning,
|
|
2776
|
+
suggestedActions,
|
|
2777
|
+
}, null, 2),
|
|
2778
|
+
}],
|
|
2779
|
+
};
|
|
2780
|
+
}
|
|
2781
|
+
}
|
|
2782
|
+
// ── Midday window: deliver approved pitches, post content ──
|
|
2783
|
+
if (window === 'midday') {
|
|
2784
|
+
let socialActivityToday = {};
|
|
2785
|
+
try {
|
|
2786
|
+
const interactions = await store.listSocialInteractions(undefined, 200);
|
|
2787
|
+
for (const i of interactions) {
|
|
2788
|
+
if (i.timestamp.startsWith(today)) {
|
|
2789
|
+
const key = `${i.platform}:${i.action}`;
|
|
2790
|
+
socialActivityToday[key] = (socialActivityToday[key] ?? 0) + 1;
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
catch {
|
|
2795
|
+
socialActivityToday = {};
|
|
2796
|
+
}
|
|
2797
|
+
const todayPlan = generateDailyContentPlan(new Date(), { maxDailyXPosts: config.maxDailyXPosts, moltbookScanFrequency: config.moltbookScanFrequency }, { activeSubscribers: pipelineSummary.byStage.onboarded + pipelineSummary.byStage.converted, weeklyConversions: pipelineSummary.todayConverted });
|
|
2798
|
+
return {
|
|
2799
|
+
content: [{
|
|
2800
|
+
type: "text",
|
|
2801
|
+
text: JSON.stringify({
|
|
2802
|
+
window: 'midday',
|
|
2803
|
+
date: today,
|
|
2804
|
+
freedomRun: freedomRun.getStatus().status,
|
|
2805
|
+
socialActivity: socialActivityToday,
|
|
2806
|
+
contentPlan: todayPlan,
|
|
2807
|
+
pitchSlots: Math.max(0, config.maxDailyPitches - pipelineSummary.todayPitched),
|
|
2808
|
+
suggestedActions: [
|
|
2809
|
+
'Deliver any approved pitches awaiting send',
|
|
2810
|
+
'Post thought leadership content per content plan',
|
|
2811
|
+
'Respond to inbound messages',
|
|
2812
|
+
],
|
|
2813
|
+
}, null, 2),
|
|
2814
|
+
}],
|
|
2815
|
+
};
|
|
2816
|
+
}
|
|
2817
|
+
// ── Afternoon window: nurture, commission sweep, content review ──
|
|
2818
|
+
if (window === 'afternoon') {
|
|
2819
|
+
const allProspects = await pipeline.listAll(200);
|
|
2820
|
+
const atRiskSubscribers = findAtRiskSubscribers(allProspects);
|
|
2821
|
+
let commissionStatus = null;
|
|
2822
|
+
try {
|
|
2823
|
+
const cs = await platform.getCommissionStatus();
|
|
2824
|
+
if (cs.ok)
|
|
2825
|
+
commissionStatus = cs.data;
|
|
2826
|
+
}
|
|
2827
|
+
catch { /* best-effort */ }
|
|
2828
|
+
// Commission sweep via governance — NOT direct claim (BB Amendment A4/A8)
|
|
2829
|
+
let commissionSweepResult = 'No eligible claims';
|
|
2830
|
+
let eligibleEscrows = 0;
|
|
2831
|
+
if (commissionStatus && typeof commissionStatus === 'object' && 'pendingClaims' in commissionStatus) {
|
|
2832
|
+
eligibleEscrows = commissionStatus.pendingClaims ?? 0;
|
|
2833
|
+
}
|
|
2834
|
+
if (eligibleEscrows > 0) {
|
|
2835
|
+
try {
|
|
2836
|
+
const escrows = commissionStatus?.eligibleEscrows ?? [{ escrowId: 'batch', amount: 0, subscriber: 'unknown' }];
|
|
2837
|
+
let proposalsSubmitted = 0;
|
|
2838
|
+
for (const escrow of escrows) {
|
|
2839
|
+
await governance.propose({
|
|
2840
|
+
actionType: 'financial',
|
|
2841
|
+
description: `Claim commission escrow #${escrow.escrowId} — $${escrow.amount} USDC from subscriber ${escrow.subscriber}`,
|
|
2842
|
+
riskLevel: 'critical',
|
|
2843
|
+
metadata: { escrowId: escrow.escrowId, amount: escrow.amount, subscriber: escrow.subscriber },
|
|
2844
|
+
});
|
|
2845
|
+
proposalsSubmitted++;
|
|
2846
|
+
}
|
|
2847
|
+
commissionSweepResult = `${proposalsSubmitted} commission claim proposal(s) submitted for Jessie's approval (risk: critical, NO auto-approval)`;
|
|
2848
|
+
}
|
|
2849
|
+
catch (err) {
|
|
2850
|
+
commissionSweepResult = `Commission sweep error: ${err instanceof Error ? err.message : String(err)}`;
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
return {
|
|
2854
|
+
content: [{
|
|
2855
|
+
type: "text",
|
|
2856
|
+
text: JSON.stringify({
|
|
2857
|
+
window: 'afternoon',
|
|
2858
|
+
date: today,
|
|
2859
|
+
freedomRun: freedomRun.getStatus().status,
|
|
2860
|
+
nurture: {
|
|
2861
|
+
atRiskCount: atRiskSubscribers.length,
|
|
2862
|
+
subscribers: atRiskSubscribers.length > 0 ? atRiskSubscribers : undefined,
|
|
2863
|
+
},
|
|
2864
|
+
commission: {
|
|
2865
|
+
status: commissionStatus,
|
|
2866
|
+
sweepResult: commissionSweepResult,
|
|
2867
|
+
note: 'All claims go through governance. Jessie must explicitly approve. No auto-approval timeout.',
|
|
2868
|
+
},
|
|
2869
|
+
suggestedActions: [
|
|
2870
|
+
...atRiskSubscribers.map(r => `[${r.status.toUpperCase()}] ${r.suggestedAction}`),
|
|
2871
|
+
'Review content performance metrics',
|
|
2872
|
+
],
|
|
2873
|
+
}, null, 2),
|
|
2874
|
+
}],
|
|
2875
|
+
};
|
|
2876
|
+
}
|
|
2877
|
+
// ── Evening window: daily report, metrics, report to Jessie ──
|
|
2878
|
+
if (window === 'evening') {
|
|
2879
|
+
const walletBalance = await wallet.getBalance().catch(() => null);
|
|
2880
|
+
const allProspects = await pipeline.listAll(200);
|
|
2881
|
+
const govStats = await governance.getStats();
|
|
2882
|
+
let socialActivityToday = {};
|
|
2883
|
+
try {
|
|
2884
|
+
const interactions = await store.listSocialInteractions(undefined, 200);
|
|
2885
|
+
for (const i of interactions) {
|
|
2886
|
+
if (i.timestamp.startsWith(today)) {
|
|
2887
|
+
const key = `${i.platform}:${i.action}`;
|
|
2888
|
+
socialActivityToday[key] = (socialActivityToday[key] ?? 0) + 1;
|
|
2889
|
+
}
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2892
|
+
catch {
|
|
2893
|
+
socialActivityToday = {};
|
|
2894
|
+
}
|
|
2895
|
+
return {
|
|
2896
|
+
content: [{
|
|
2897
|
+
type: "text",
|
|
2898
|
+
text: JSON.stringify({
|
|
2899
|
+
window: 'evening',
|
|
2900
|
+
date: today,
|
|
2901
|
+
freedomRun: freedomRun.getStatus(),
|
|
2902
|
+
dailyReport: {
|
|
2903
|
+
pipeline: pipelineSummary,
|
|
2904
|
+
wallet: walletBalance ? { balance: walletBalance, network: wallet.network } : { status: 'unavailable' },
|
|
2905
|
+
governance: govStats,
|
|
2906
|
+
socialActivity: socialActivityToday,
|
|
2907
|
+
activeProspects: allProspects.filter(p => !['disqualified', 'declined'].includes(p.stage)).length,
|
|
2908
|
+
},
|
|
2909
|
+
suggestedActions: [
|
|
2910
|
+
'Submit daily report to Jessie (lands in her 17:30 CT afternoon review)',
|
|
2911
|
+
'Update pipeline metrics for weekly tracking',
|
|
2912
|
+
],
|
|
2913
|
+
}, null, 2),
|
|
2914
|
+
}],
|
|
2915
|
+
};
|
|
2916
|
+
}
|
|
2917
|
+
// ── Auto mode: full one-shot brief (backward compatible) ──
|
|
2651
2918
|
const walletBalance = await wallet.getBalance().catch(() => null);
|
|
2652
2919
|
let commissionStatus = null;
|
|
2653
2920
|
try {
|
|
@@ -2656,28 +2923,12 @@ export default function register(api) {
|
|
|
2656
2923
|
commissionStatus = cs.data;
|
|
2657
2924
|
}
|
|
2658
2925
|
catch { /* best-effort */ }
|
|
2659
|
-
|
|
2660
|
-
if (commissionStatus && typeof commissionStatus === 'object' && 'pendingClaims' in commissionStatus) {
|
|
2661
|
-
pendingClaims = commissionStatus.pendingClaims ?? 0;
|
|
2662
|
-
}
|
|
2663
|
-
let commissionClaimResult = null;
|
|
2664
|
-
if (pendingClaims > 0) {
|
|
2665
|
-
try {
|
|
2666
|
-
const claimed = await platform.claimCommission();
|
|
2667
|
-
commissionClaimResult = claimed.ok
|
|
2668
|
-
? `Claimed ${pendingClaims} pending commission(s)`
|
|
2669
|
-
: `Claim failed: ${claimed.error}`;
|
|
2670
|
-
}
|
|
2671
|
-
catch {
|
|
2672
|
-
commissionClaimResult = 'Claim attempt failed';
|
|
2673
|
-
}
|
|
2674
|
-
}
|
|
2675
|
-
const today = new Date().toISOString().slice(0, 10);
|
|
2926
|
+
const today2 = today;
|
|
2676
2927
|
let socialActivityToday = {};
|
|
2677
2928
|
try {
|
|
2678
2929
|
const interactions = await store.listSocialInteractions(undefined, 200);
|
|
2679
2930
|
for (const i of interactions) {
|
|
2680
|
-
if (i.timestamp.startsWith(
|
|
2931
|
+
if (i.timestamp.startsWith(today2)) {
|
|
2681
2932
|
const key = `${i.platform}:${i.action}`;
|
|
2682
2933
|
socialActivityToday[key] = (socialActivityToday[key] ?? 0) + 1;
|
|
2683
2934
|
}
|
|
@@ -2707,15 +2958,14 @@ export default function register(api) {
|
|
|
2707
2958
|
}
|
|
2708
2959
|
}
|
|
2709
2960
|
const brief = {
|
|
2961
|
+
window: 'auto',
|
|
2710
2962
|
date: today,
|
|
2963
|
+
freedomRun: freedomRun.getStatus().status,
|
|
2711
2964
|
pipeline: pipelineSummary,
|
|
2712
2965
|
wallet: walletBalance
|
|
2713
2966
|
? { balance: walletBalance, network: wallet.network }
|
|
2714
2967
|
: { status: 'unavailable' },
|
|
2715
|
-
commission: {
|
|
2716
|
-
status: commissionStatus,
|
|
2717
|
-
claimResult: commissionClaimResult,
|
|
2718
|
-
},
|
|
2968
|
+
commission: { status: commissionStatus, note: 'Use nole_commission_sweep for governance-gated claims' },
|
|
2719
2969
|
socialActivity: socialActivityToday,
|
|
2720
2970
|
suggestedActions,
|
|
2721
2971
|
nurture: {
|
|
@@ -2728,10 +2978,7 @@ export default function register(api) {
|
|
|
2728
2978
|
socialPostingMode: config.socialPostingMode,
|
|
2729
2979
|
},
|
|
2730
2980
|
};
|
|
2731
|
-
const todayPlan = generateDailyContentPlan(new Date(), { maxDailyXPosts: config.maxDailyXPosts, moltbookScanFrequency: config.moltbookScanFrequency }, {
|
|
2732
|
-
activeSubscribers: pipelineSummary.byStage.onboarded + pipelineSummary.byStage.converted,
|
|
2733
|
-
weeklyConversions: pipelineSummary.todayConverted,
|
|
2734
|
-
});
|
|
2981
|
+
const todayPlan = generateDailyContentPlan(new Date(), { maxDailyXPosts: config.maxDailyXPosts, moltbookScanFrequency: config.moltbookScanFrequency }, { activeSubscribers: pipelineSummary.byStage.onboarded + pipelineSummary.byStage.converted, weeklyConversions: pipelineSummary.todayConverted });
|
|
2735
2982
|
brief.contentPlan = todayPlan;
|
|
2736
2983
|
if (params.includeProspects) {
|
|
2737
2984
|
const active = await pipeline.listAll(50);
|
|
@@ -2745,6 +2992,81 @@ export default function register(api) {
|
|
|
2745
2992
|
}
|
|
2746
2993
|
},
|
|
2747
2994
|
});
|
|
2995
|
+
// ── Commission Sweep Tool (BB Amendment A4/A8: governance-gated, no auto-approval) ──
|
|
2996
|
+
api.registerTool({
|
|
2997
|
+
name: "nole_commission_sweep",
|
|
2998
|
+
description: "Sweep eligible commission escrows and submit governance proposals for each claim. " +
|
|
2999
|
+
"Every claim requires Jessie's explicit approval (risk: critical, NO auto-approval timeout). " +
|
|
3000
|
+
"BB Amendment A8: Patent 8 separation of powers.",
|
|
3001
|
+
parameters: {
|
|
3002
|
+
type: "object",
|
|
3003
|
+
properties: {},
|
|
3004
|
+
},
|
|
3005
|
+
async execute(_toolCallId, _params) {
|
|
3006
|
+
if (!freedomRun.canOperate()) {
|
|
3007
|
+
const frStatus = freedomRun.getStatus();
|
|
3008
|
+
return {
|
|
3009
|
+
content: [{
|
|
3010
|
+
type: "text",
|
|
3011
|
+
text: JSON.stringify({
|
|
3012
|
+
tool: 'nole_commission_sweep',
|
|
3013
|
+
blocked: true,
|
|
3014
|
+
freedomRunStatus: frStatus.status,
|
|
3015
|
+
reason: frStatus.reason,
|
|
3016
|
+
}, null, 2),
|
|
3017
|
+
}],
|
|
3018
|
+
};
|
|
3019
|
+
}
|
|
3020
|
+
try {
|
|
3021
|
+
let commissionStatus = null;
|
|
3022
|
+
try {
|
|
3023
|
+
const cs = await platform.getCommissionStatus();
|
|
3024
|
+
if (cs.ok)
|
|
3025
|
+
commissionStatus = cs.data;
|
|
3026
|
+
}
|
|
3027
|
+
catch { /* best-effort */ }
|
|
3028
|
+
let eligibleEscrows = 0;
|
|
3029
|
+
if (commissionStatus && typeof commissionStatus === 'object' && 'pendingClaims' in commissionStatus) {
|
|
3030
|
+
eligibleEscrows = commissionStatus.pendingClaims ?? 0;
|
|
3031
|
+
}
|
|
3032
|
+
if (eligibleEscrows === 0) {
|
|
3033
|
+
return {
|
|
3034
|
+
content: [{
|
|
3035
|
+
type: "text",
|
|
3036
|
+
text: JSON.stringify({ result: 'No eligible commission escrows to claim', eligibleEscrows: 0 }, null, 2),
|
|
3037
|
+
}],
|
|
3038
|
+
};
|
|
3039
|
+
}
|
|
3040
|
+
const escrows = commissionStatus?.eligibleEscrows ?? [{ escrowId: 'batch', amount: 0, subscriber: 'unknown' }];
|
|
3041
|
+
const proposals = [];
|
|
3042
|
+
for (const escrow of escrows) {
|
|
3043
|
+
const result = await governance.propose({
|
|
3044
|
+
actionType: 'financial',
|
|
3045
|
+
description: `Claim commission escrow #${escrow.escrowId} — $${escrow.amount} USDC from subscriber ${escrow.subscriber}`,
|
|
3046
|
+
riskLevel: 'critical',
|
|
3047
|
+
metadata: { escrowId: escrow.escrowId, amount: escrow.amount, subscriber: escrow.subscriber },
|
|
3048
|
+
});
|
|
3049
|
+
proposals.push(result.proposalId);
|
|
3050
|
+
}
|
|
3051
|
+
return {
|
|
3052
|
+
content: [{
|
|
3053
|
+
type: "text",
|
|
3054
|
+
text: JSON.stringify({
|
|
3055
|
+
result: `${proposals.length} commission claim proposal(s) submitted`,
|
|
3056
|
+
proposals,
|
|
3057
|
+
riskTier: 'critical',
|
|
3058
|
+
autoApproval: 'NEVER — Jessie must explicitly approve every financial action (BB Amendment A8)',
|
|
3059
|
+
note: 'Escrows do not expire. Claims will wait for Jessie\'s batch review.',
|
|
3060
|
+
}, null, 2),
|
|
3061
|
+
}],
|
|
3062
|
+
};
|
|
3063
|
+
}
|
|
3064
|
+
catch (err) {
|
|
3065
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3066
|
+
return { content: [{ type: "text", text: `Commission sweep error: ${message}` }], isError: true };
|
|
3067
|
+
}
|
|
3068
|
+
},
|
|
3069
|
+
});
|
|
2748
3070
|
// --- Command (matches Grillo/NOAH pattern: sync handler, ctx.args) ---
|
|
2749
3071
|
api.registerCommand({
|
|
2750
3072
|
name: "nole",
|
|
@@ -2777,7 +3099,8 @@ export default function register(api) {
|
|
|
2777
3099
|
"- `nole_setup` — Configuration check\n\n" +
|
|
2778
3100
|
"**Revenue Pipeline:**\n" +
|
|
2779
3101
|
"- `nole_pipeline` — Manage prospect pipeline (discover, qualify, pitch, scan, onboard, promote, nurture, directory-scan, content-plan, list, summary)\n" +
|
|
2780
|
-
"- `nole_daily_brief` —
|
|
3102
|
+
"- `nole_daily_brief` — Windowed daily brief (morning/midday/afternoon/evening/auto) with Freedom Run gate\n" +
|
|
3103
|
+
"- `nole_commission_sweep` — Governance-gated commission claims (requires Jessie approval)\n\n" +
|
|
2781
3104
|
"**Social Media:**\n" +
|
|
2782
3105
|
"- `nole_moltbook_post` — Post to a MoltBook submolt\n" +
|
|
2783
3106
|
"- `nole_moltbook_comment` — Comment on a MoltBook post\n" +
|
|
@@ -2838,6 +3161,118 @@ export default function register(api) {
|
|
|
2838
3161
|
return { content: [{ type: "text", text: "Fleet bus not connected — proposal queued locally." }] };
|
|
2839
3162
|
},
|
|
2840
3163
|
});
|
|
3164
|
+
let noleFleetSendFn = null;
|
|
3165
|
+
let noleBusRef = null;
|
|
3166
|
+
let noleTransportRef = null;
|
|
3167
|
+
api.registerTool({
|
|
3168
|
+
name: "nole_correspondence_submit",
|
|
3169
|
+
description: "Submit a draft message for ethical assessment before sending. " +
|
|
3170
|
+
"Routes through Jessie (Commander) → Grillo (Conscience) for LCSH rubric evaluation. " +
|
|
3171
|
+
"Returns GO (approved) or NO-GO (denied with revision guidance). " +
|
|
3172
|
+
"Max 3 attempts per submission. Use for all external correspondence.",
|
|
3173
|
+
parameters: {
|
|
3174
|
+
type: "object",
|
|
3175
|
+
properties: {
|
|
3176
|
+
draft: {
|
|
3177
|
+
type: "string",
|
|
3178
|
+
description: "The draft message text to assess before sending",
|
|
3179
|
+
},
|
|
3180
|
+
recipientPLevel: {
|
|
3181
|
+
type: "string",
|
|
3182
|
+
enum: ["P0", "P1", "P2", "P3"],
|
|
3183
|
+
description: "Recipient's trust level (P0=core team, P3=cold prospect)",
|
|
3184
|
+
},
|
|
3185
|
+
actionType: {
|
|
3186
|
+
type: "string",
|
|
3187
|
+
enum: ["auto-resolve-email", "auto-resolve-message", "cold-outreach"],
|
|
3188
|
+
description: "Type of correspondence action",
|
|
3189
|
+
},
|
|
3190
|
+
threadContext: {
|
|
3191
|
+
type: "string",
|
|
3192
|
+
description: "Optional context about the conversation thread",
|
|
3193
|
+
},
|
|
3194
|
+
},
|
|
3195
|
+
required: ["draft", "recipientPLevel", "actionType"],
|
|
3196
|
+
},
|
|
3197
|
+
async execute(_toolCallId, params) {
|
|
3198
|
+
if (!noleFleetSendFn || !noleBusRef || !noleTransportRef) {
|
|
3199
|
+
return { content: [{ type: "text", text: "Fleet bus not connected — cannot submit correspondence for assessment." }] };
|
|
3200
|
+
}
|
|
3201
|
+
const submissionId = `corr-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
3202
|
+
const resultPromise = new Promise((resolve) => {
|
|
3203
|
+
const timeoutHandle = setTimeout(() => {
|
|
3204
|
+
pendingCorrespondenceCallbacks.delete(submissionId);
|
|
3205
|
+
resolve({
|
|
3206
|
+
approved: false,
|
|
3207
|
+
verdict: 'TIMEOUT',
|
|
3208
|
+
reasoning: 'Assessment timed out after 60 seconds — Grillo or Jessie may be unavailable.',
|
|
3209
|
+
assessmentHash: '',
|
|
3210
|
+
retryAllowed: true,
|
|
3211
|
+
});
|
|
3212
|
+
}, 60_000);
|
|
3213
|
+
pendingCorrespondenceCallbacks.set(submissionId, { resolve, timeoutHandle });
|
|
3214
|
+
});
|
|
3215
|
+
try {
|
|
3216
|
+
await noleFleetSendFn(noleBusRef, noleTransportRef, {
|
|
3217
|
+
to: 'jessie',
|
|
3218
|
+
method: 'correspondence/submit',
|
|
3219
|
+
params: {
|
|
3220
|
+
draft: params.draft,
|
|
3221
|
+
recipientPLevel: params.recipientPLevel,
|
|
3222
|
+
actionType: params.actionType,
|
|
3223
|
+
threadContext: params.threadContext,
|
|
3224
|
+
submissionId,
|
|
3225
|
+
},
|
|
3226
|
+
});
|
|
3227
|
+
}
|
|
3228
|
+
catch (err) {
|
|
3229
|
+
const cb = pendingCorrespondenceCallbacks.get(submissionId);
|
|
3230
|
+
if (cb) {
|
|
3231
|
+
clearTimeout(cb.timeoutHandle);
|
|
3232
|
+
pendingCorrespondenceCallbacks.delete(submissionId);
|
|
3233
|
+
}
|
|
3234
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
3235
|
+
return { content: [{ type: "text", text: `Fleet send failed: ${errorMsg}` }], isError: true };
|
|
3236
|
+
}
|
|
3237
|
+
const result = await resultPromise;
|
|
3238
|
+
const cb = pendingCorrespondenceCallbacks.get(submissionId);
|
|
3239
|
+
if (cb)
|
|
3240
|
+
clearTimeout(cb.timeoutHandle);
|
|
3241
|
+
if (result.approved) {
|
|
3242
|
+
return {
|
|
3243
|
+
content: [{
|
|
3244
|
+
type: "text",
|
|
3245
|
+
text: JSON.stringify({
|
|
3246
|
+
status: 'GO',
|
|
3247
|
+
submissionId,
|
|
3248
|
+
verdict: result.verdict,
|
|
3249
|
+
reasoning: result.reasoning,
|
|
3250
|
+
assessmentHash: result.assessmentHash,
|
|
3251
|
+
instruction: 'Draft is approved. Proceed with sending.',
|
|
3252
|
+
}, null, 2),
|
|
3253
|
+
}],
|
|
3254
|
+
};
|
|
3255
|
+
}
|
|
3256
|
+
return {
|
|
3257
|
+
content: [{
|
|
3258
|
+
type: "text",
|
|
3259
|
+
text: JSON.stringify({
|
|
3260
|
+
status: 'NO-GO',
|
|
3261
|
+
submissionId,
|
|
3262
|
+
verdict: result.verdict,
|
|
3263
|
+
reasoning: result.reasoning,
|
|
3264
|
+
assessmentHash: result.assessmentHash,
|
|
3265
|
+
attemptNumber: result.attemptNumber ?? 1,
|
|
3266
|
+
retryAllowed: result.retryAllowed ?? false,
|
|
3267
|
+
instruction: result.retryAllowed
|
|
3268
|
+
? 'Draft was denied. Revise the draft addressing the concerns in `reasoning`, then resubmit.'
|
|
3269
|
+
: 'Draft was denied after maximum attempts. Escalate to operator for manual review.',
|
|
3270
|
+
}, null, 2),
|
|
3271
|
+
}],
|
|
3272
|
+
};
|
|
3273
|
+
},
|
|
3274
|
+
});
|
|
3275
|
+
const pendingCorrespondenceCallbacks = new Map();
|
|
2841
3276
|
const fleetBusMod = "@aiassesstech/fleet-bus";
|
|
2842
3277
|
import(fleetBusMod)
|
|
2843
3278
|
.then(async ({ FleetBus, NOLE_CARD, createNoleFleetTools, createTransport, fleetReceive, fleetSend }) => {
|
|
@@ -2850,8 +3285,11 @@ export default function register(api) {
|
|
|
2850
3285
|
: undefined,
|
|
2851
3286
|
});
|
|
2852
3287
|
bus.start();
|
|
3288
|
+
noleBusRef = bus;
|
|
2853
3289
|
const transport = await createTransport();
|
|
2854
3290
|
if (transport) {
|
|
3291
|
+
noleTransportRef = transport;
|
|
3292
|
+
noleFleetSendFn = fleetSend;
|
|
2855
3293
|
const tools = createNoleFleetTools(bus, {
|
|
2856
3294
|
...transport,
|
|
2857
3295
|
outputSanitizer: outputSanitizer
|
|
@@ -2881,9 +3319,28 @@ export default function register(api) {
|
|
|
2881
3319
|
"veto/issue", "fleet/ping", "fleet/pong", "fleet/broadcast",
|
|
2882
3320
|
"cron/scheduled",
|
|
2883
3321
|
"assessment/result",
|
|
3322
|
+
"correspondence/approved", "correspondence/denied",
|
|
2884
3323
|
],
|
|
2885
3324
|
handler: async (msg) => {
|
|
2886
3325
|
console.log(`[nole] Fleet message received: ${msg.method} from ${msg.from}`);
|
|
3326
|
+
if (msg.method === 'correspondence/approved') {
|
|
3327
|
+
const { submissionId, verdict, reasoning, assessmentHash } = msg.params ?? {};
|
|
3328
|
+
console.log(`[nole] Correspondence APPROVED: ${submissionId} (${verdict}) — GO to send`);
|
|
3329
|
+
pendingCorrespondenceCallbacks.get(submissionId)?.resolve({
|
|
3330
|
+
approved: true, verdict, reasoning, assessmentHash,
|
|
3331
|
+
});
|
|
3332
|
+
pendingCorrespondenceCallbacks.delete(submissionId);
|
|
3333
|
+
return;
|
|
3334
|
+
}
|
|
3335
|
+
if (msg.method === 'correspondence/denied') {
|
|
3336
|
+
const { submissionId, verdict, reasoning, assessmentHash, attemptNumber, retryAllowed } = msg.params ?? {};
|
|
3337
|
+
console.log(`[nole] Correspondence DENIED: ${submissionId} (${verdict}, attempt ${attemptNumber}, retry=${retryAllowed})`);
|
|
3338
|
+
pendingCorrespondenceCallbacks.get(submissionId)?.resolve({
|
|
3339
|
+
approved: false, verdict, reasoning, assessmentHash, attemptNumber, retryAllowed,
|
|
3340
|
+
});
|
|
3341
|
+
pendingCorrespondenceCallbacks.delete(submissionId);
|
|
3342
|
+
return;
|
|
3343
|
+
}
|
|
2887
3344
|
if (msg.method === 'assessment/result') {
|
|
2888
3345
|
const { agentId, scores, passed, classification, framework, runId } = msg.params ?? {};
|
|
2889
3346
|
if (agentId !== 'nole') {
|
|
@@ -2923,6 +3380,42 @@ export default function register(api) {
|
|
|
2923
3380
|
}
|
|
2924
3381
|
if (msg.method === 'cron/scheduled') {
|
|
2925
3382
|
const { action, jobId } = msg.params ?? {};
|
|
3383
|
+
// Freedom Run daily windows (§11.4 — 4 cron entries aligned with Jessie's EA schedule)
|
|
3384
|
+
const cronWindowMap = {
|
|
3385
|
+
nole_morning_scan: 'morning',
|
|
3386
|
+
nole_midday_outreach: 'midday',
|
|
3387
|
+
nole_afternoon_nurture: 'afternoon',
|
|
3388
|
+
nole_evening_report: 'evening',
|
|
3389
|
+
};
|
|
3390
|
+
if (action in cronWindowMap) {
|
|
3391
|
+
const window = cronWindowMap[action];
|
|
3392
|
+
console.log(`[nole] Cron-triggered ${window} window (job: ${jobId})`);
|
|
3393
|
+
try {
|
|
3394
|
+
await transport.callGateway({
|
|
3395
|
+
method: 'agent',
|
|
3396
|
+
params: {
|
|
3397
|
+
agentId: 'nole',
|
|
3398
|
+
message: `SYSTEM: Freedom Run ${window} window — cron-triggered by Noah. Execute nole_daily_brief with window="${window}".`,
|
|
3399
|
+
deliver: true,
|
|
3400
|
+
label: `cron:${action}`,
|
|
3401
|
+
},
|
|
3402
|
+
});
|
|
3403
|
+
await fleetSend(bus, transport, {
|
|
3404
|
+
to: 'noah',
|
|
3405
|
+
method: 'cron/completed',
|
|
3406
|
+
params: { jobId, action, status: 'completed', timestamp: new Date().toISOString() },
|
|
3407
|
+
}).catch(() => { });
|
|
3408
|
+
}
|
|
3409
|
+
catch (err) {
|
|
3410
|
+
console.error(`[nole] Cron injection failed for ${action}:`, err instanceof Error ? err.message : String(err));
|
|
3411
|
+
await fleetSend(bus, transport, {
|
|
3412
|
+
to: 'noah',
|
|
3413
|
+
method: 'cron/failed',
|
|
3414
|
+
params: { jobId, action, error: err instanceof Error ? err.message : String(err), timestamp: new Date().toISOString() },
|
|
3415
|
+
}).catch(() => { });
|
|
3416
|
+
}
|
|
3417
|
+
return;
|
|
3418
|
+
}
|
|
2926
3419
|
if (action === 'weekly_strategy_review') {
|
|
2927
3420
|
console.log(`[nole] Cron-triggered weekly review (job: ${jobId})`);
|
|
2928
3421
|
try {
|
|
@@ -2955,6 +3448,65 @@ export default function register(api) {
|
|
|
2955
3448
|
}
|
|
2956
3449
|
return;
|
|
2957
3450
|
}
|
|
3451
|
+
// ── Pitch delivery on governance approval (§11.5) ──
|
|
3452
|
+
if (msg.method === 'proposal/approved') {
|
|
3453
|
+
const { actionType, metadata, description } = msg.params ?? {};
|
|
3454
|
+
if (actionType === 'recruitment' || actionType === 'social' || actionType === 'content') {
|
|
3455
|
+
console.log(`[nole] Pitch approved — delivering via social client`);
|
|
3456
|
+
if (!freedomRun.canOperate()) {
|
|
3457
|
+
console.warn('[nole] Pitch delivery skipped — Freedom Run not active');
|
|
3458
|
+
}
|
|
3459
|
+
else {
|
|
3460
|
+
try {
|
|
3461
|
+
const targetPlatform = metadata?.channel ?? metadata?.platform ?? 'moltbook';
|
|
3462
|
+
const pitchContent = metadata?.pitchContent ?? description ?? '';
|
|
3463
|
+
const prospectId = metadata?.prospectId;
|
|
3464
|
+
if (targetPlatform === 'moltbook' && moltbook) {
|
|
3465
|
+
await moltbook.createPost('aiassesstech', 'Nole Outreach', String(pitchContent));
|
|
3466
|
+
}
|
|
3467
|
+
else if (targetPlatform === 'x' && xClient) {
|
|
3468
|
+
await xClient.postTweet(String(pitchContent));
|
|
3469
|
+
}
|
|
3470
|
+
// fleet-bus pitches go via fleetSend in the original proposal handler
|
|
3471
|
+
if (prospectId) {
|
|
3472
|
+
await pipeline.updateStage(String(prospectId), 'pitched', {
|
|
3473
|
+
pitchDeliveredAt: new Date().toISOString(),
|
|
3474
|
+
pitchApproved: true,
|
|
3475
|
+
});
|
|
3476
|
+
}
|
|
3477
|
+
console.log(`[nole] Pitch delivered to ${targetPlatform}`);
|
|
3478
|
+
}
|
|
3479
|
+
catch (err) {
|
|
3480
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
3481
|
+
console.error(`[nole] Pitch delivery failed: ${errMsg}`);
|
|
3482
|
+
// Queue for retry via resilience layer
|
|
3483
|
+
if (metadata?.pitchContent) {
|
|
3484
|
+
postQueue.enqueue(metadata.platform ?? 'moltbook', String(metadata.pitchContent), metadata);
|
|
3485
|
+
}
|
|
3486
|
+
}
|
|
3487
|
+
}
|
|
3488
|
+
}
|
|
3489
|
+
// Commission claim on financial approval (§11.6 — BB Amendment A4/A8)
|
|
3490
|
+
if (actionType === 'financial') {
|
|
3491
|
+
const escrowId = metadata?.escrowId;
|
|
3492
|
+
console.log(`[nole] Financial action approved — claiming commission escrow ${escrowId}`);
|
|
3493
|
+
try {
|
|
3494
|
+
const result = await platform.claimCommission();
|
|
3495
|
+
if (result.ok) {
|
|
3496
|
+
console.log(`[nole] Commission claimed successfully (escrow: ${escrowId})`);
|
|
3497
|
+
}
|
|
3498
|
+
else {
|
|
3499
|
+
console.error(`[nole] Commission claim failed: ${result.error}`);
|
|
3500
|
+
}
|
|
3501
|
+
await governance.auditTrail.record('commission_claimed', `Commission escrow ${escrowId}: ${result.ok ? 'claimed' : `failed — ${result.error}`}`);
|
|
3502
|
+
}
|
|
3503
|
+
catch (err) {
|
|
3504
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
3505
|
+
console.error(`[nole] Commission claim error: ${errMsg}`);
|
|
3506
|
+
}
|
|
3507
|
+
}
|
|
3508
|
+
// Don't return — let the existing fleet-discovery logic below also run
|
|
3509
|
+
}
|
|
2958
3510
|
if (msg.from && msg.from !== 'nole') {
|
|
2959
3511
|
const existing = await pipeline.findByAgentName(msg.from);
|
|
2960
3512
|
if (!existing) {
|
|
@@ -3036,7 +3588,7 @@ export default function register(api) {
|
|
|
3036
3588
|
`nole_directory, nole_review_pending, nole_approve, nole_veto, nole_fleet_overview, ` +
|
|
3037
3589
|
`nole_moltbook_post, nole_moltbook_comment, nole_moltbook_feed, nole_moltbook_search, ` +
|
|
3038
3590
|
`nole_x_post, nole_x_thread, nole_linkedin_draft, nole_social_status, ` +
|
|
3039
|
-
`nole_pipeline, nole_daily_brief tools; /nole command`);
|
|
3591
|
+
`nole_pipeline, nole_daily_brief, nole_commission_sweep, nole_correspondence_submit tools; /nole command`);
|
|
3040
3592
|
console.log(`[nole] AIAssessTech API: ${platform.isConfigured ? 'configured' : 'not configured — set agentApiKey + platformWalletAddress'} (${config.platformApiUrl})`);
|
|
3041
3593
|
}
|
|
3042
3594
|
//# sourceMappingURL=plugin.js.map
|